context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.IO;
  
  using System.Runtime.InteropServices;
  using MonoDevelop.Core;
  using System.Text;
  
  using svn_revnum_t = System.IntPtr;
  using size_t = System.Int32;
  using off_t = System.Int64;
  using MonoDevelop.Projects.Text;
  using System.Threading;
  using System.Linq;
  using MonoDevelop.Ide;
  using System.Diagnostics;
  
  namespace MonoDevelop.VersionControl.Subversion.Unix
  {
  	sealed class SvnClient : SubversionVersionControl
  	{
  		static LibApr apr;
  		static readonly Lazy<bool> isInstalled;
  		static LibSvnClient svn;
  		static readonly Lazy<bool> pre_1_7;
  
  		internal static LibApr Apr {
  			get {
  				if (apr == null)
  					CheckInstalled ();
  				return apr;
  			}
  		}
  
  		internal static LibSvnClient Svn {
  			get {
  				if (svn == null)
  					CheckInstalled ();
  				return svn;
  			}
  		}
  
  		internal static void CheckError (IntPtr error)
  		{
  			CheckError (error, null);
  		}
  
  		public static SubversionException CheckErrorNoThrow (IntPtr error, int? allowedError)
  		{
  			string msg = null;
  			int errorCode = 0; // Innermost error-code.
  			while (error != IntPtr.Zero) {
  				LibSvnClient.svn_error_t error_t = (LibSvnClient.svn_error_t)Marshal.PtrToStructure (error, typeof(LibSvnClient.svn_error_t));
  				if (allowedError.HasValue && error_t.apr_err == allowedError.Value)
  					return null;
  
  				errorCode = error_t.apr_err;
  				if (msg != null)
  					msg += "\n" + GetErrorMessage (error_t);
  				else
  					msg = GetErrorMessage (error_t);
  				error = error_t.svn_error_t_child;
  
  				if (msg == null)
  					msg = GettextCatalog.GetString ("Unknown error");
  			}
  			return msg != null ? new SubversionException (msg, errorCode) : null;
  		}
  
  		public static void CheckError (IntPtr error, int? allowedError)
  		{
  			SubversionException ex = CheckErrorNoThrow (error, allowedError);
  			if (ex != null)
  				throw ex;
  		}
  		
  		static string GetErrorMessage (LibSvnClient.svn_error_t error)
  		{
  			if (error.message != null)
  				return error.message;
  			else {
  				byte[] buf = new byte [300];
  				// Caller will handle the lock.
  				Svn.strerror (error.apr_err, buf, buf.Length);
  				return Encoding.UTF8.GetString (buf);
  			}
  		}
  
  		internal static IntPtr newpool (IntPtr parent)
  		{
  			IntPtr p;
  
  			// Caller will handle the lock.
  			Apr.pool_create_ex (out p, parent, IntPtr.Zero, IntPtr.Zero);
  			if (p == IntPtr.Zero)
  				throw new InvalidOperationException ("Could not create an APR pool.");
  			return p;
  		}
  
  		internal static void destroypool (IntPtr pool)
  		{
  			// Caller will handle lock.
  			if (pool != IntPtr.Zero)
  				Apr.pool_destroy (pool);
  		}
  		
  		public static string NormalizePath (string pathOrUrl, IntPtr localpool)
  		{
  			if (pathOrUrl == null)
  				return null;
  			// Caller will handle the lock.
  			IntPtr res = Svn.path_internal_style (pathOrUrl, localpool);
  			return Marshal.PtrToStringAnsi (res);
  		}
  
  		internal static bool CheckInstalled ()
  		{
  			// libsvn_client may be linked to libapr-0 or libapr-1, and we need to bind the LibApr class
  			// to the same library. The following code detects the required libapr version and loads it. 
  			int aprver = GetLoadAprLib (-1);
  			svn = LibSvnClient.GetLib ();
  			if (svn == null) {
  				LoggingService.LogWarning ("Subversion addin could not load libsvn_client, so it will be disabled.");
  				return false;
  			}
  			aprver = GetLoadAprLib (aprver);
  			if (aprver != -1)
  				LoggingService.LogDebug ("Subversion addin detected libapr-" + aprver);
  			apr = LibApr.GetLib (aprver);
  			if (apr == null) {
  				svn = null;
  				LoggingService.LogInfo ("Subversion addin could not load libapr, so it will be disabled.");
  				return false;
  			}
  			return true;
  		}
  
  		public override string Version {
  			get {
  				try {
  					return GetVersion ();
  				} catch (Exception e) {
  					LoggingService.LogError ("Failed to query Subversion version info", e);
  					return base.Version;
  				}
  			}
  		}
  
  		internal static bool CheckVersion ()
  		{
  			return GetVersion ().StartsWith ("1.6", StringComparison.Ordinal);
  		}
  
  		public static string GetVersion ()
  		{
  			IntPtr ptr;
  			lock (Svn)
  				ptr = Svn.client_version ();
  			LibSvnClient.svn_version_t ver = (LibSvnClient.svn_version_t)Marshal.PtrToStructure (ptr, typeof(LibSvnClient.svn_version_t));
  			return ver.major + "." + ver.minor + "." + ver.patch;
  		}
  
  		static int GetLoadAprLib (int oldVersion)
  		{
  			// Get the version of the loaded libapr
  			string file = "/proc/" + System.Diagnostics.Process.GetCurrentProcess ().Id + "/maps";
  			try {
  				int newv = oldVersion;
  				if (File.Exists (file)) {
  					string txt = File.ReadAllText (file);
  					if (txt.IndexOf ("libapr-0", StringComparison.Ordinal) != -1 && oldVersion != 0)
  						newv = 0;
  					if (txt.IndexOf ("libapr-1", StringComparison.Ordinal) != -1 && oldVersion != 1)
  						newv = 1;
  				}
  				return newv;
  			} catch {
  				// Ignore
  				return oldVersion;
  			}
  		}
  
  		public override bool IsInstalled {
  			get { return isInstalled.Value; }
  		}
  
  		public static bool Pre_1_7 {
  			get { return pre_1_7.Value; }
  		}
  
  		static SvnClient ()
  		{
  			isInstalled = new Lazy<bool> (CheckInstalled);
  			pre_1_7 = new Lazy<bool> (CheckVersion);
  		}
  
  		public override SubversionBackend CreateBackend ()
  		{
  			return new UnixSvnBackend ();
  		}
  
  		static bool FallbackProbeDirectoryDotSvn (FilePath path)
  		{
  			while (!path.IsNullOrEmpty) {
  				if (Directory.Exists (path.Combine (".svn")))
  					return true;
  				path = path.ParentDirectory;
  			}
  			return false;
  		}
  
  		public override string GetDirectoryDotSvn (FilePath path)
  		{
  			if (path.IsNullOrEmpty)
  				return string.Empty;
  			
  			if (Pre_1_7)
  				return base.GetDirectoryDotSvn (path);
  
  			UnixSvnBackend backend = CreateBackend () as UnixSvnBackend;
  			if (backend == null)
  				return String.Empty;
  
  			return backend.GetDirectoryDotSvnInternal (path);
  		}
  	}
  
  	sealed class UnixSvnBackend : SubversionBackend
  	{
  		static LibApr apr {
  			get {
  				return SvnClient.Apr;
  			}
  		}
  		
  		static LibSvnClient svn {
  			get {
  				return SvnClient.Svn;
  			}
  		}
  
  		static void CheckError (IntPtr error)
  		{
  			SvnClient.CheckError (error);
  		}
  
  		static void CheckError (IntPtr error, int? allowedError)
  		{
  			SvnClient.CheckError (error, allowedError);
  		}
  
  		static SubversionException CheckErrorNoThrow (IntPtr error, int? allowedError)
  		{
  			return SvnClient.CheckErrorNoThrow (error, allowedError);
  		}
  
  		static IntPtr newpool (IntPtr parent)
  		{
  			return SvnClient.newpool (parent);
  		}
  
  		static void destroypool (IntPtr pool)
  		{
  			SvnClient.destroypool (pool);
  		}
  
  		bool disposed;
  		readonly IntPtr auth_baton;
  		readonly IntPtr pool;
  		readonly IntPtr ctx;
  
  		ProgressMonitor updatemonitor;
  		string commitmessage;
  
  		ArrayList lockFileList;
  		LibSvnClient.NotifyLockState requiredLockState;
  
  		// retain this so the delegates aren't GC'ed
  		readonly LibSvnClient.svn_cancel_func_t cancel_func;
  		readonly LibSvnClient.svn_ra_progress_notify_func_t progress_func;
  		readonly LibSvnClient.svn_wc_notify_func2_t notify_func;
  		readonly LibSvnClient.svn_client_get_commit_log_t log_func;
  		readonly IntPtr config_hash;
  		readonly IntPtr wc_ctx;
  
  		static bool IsBinary (byte[] buffer, long length)
  		{
  			length = (int)Math.Min (50 * 1024, length);
  			for (int i = 0; i < length; i ++)
  				if (buffer [i] == 0)
  					return true;
  			return false;
  		}
  
  		public UnixSvnBackend ()
  		{
  			lock (svn) {
  				// Allocate the APR pool and the SVN client context.
  				pool = newpool (IntPtr.Zero);
  
  				// Make sure the config directory is properly created.
  				// If the config directory and specifically the subdirectories
  				// for the authentication providers don't exist, authentication
  				// data won't be saved and no error is given.
  				svn.config_ensure (null, pool);
  
  				// Load user and system configuration
  				svn.config_get_config (ref config_hash, null, pool);
  
  				if (svn.client_create_context (out ctx, pool) != IntPtr.Zero)
  					throw new InvalidOperationException ("Could not create a Subversion client context.");
  			
  				// Set the callbacks on the client context structure.
  				notify_func = new LibSvnClient.svn_wc_notify_func2_t (svn_wc_notify_func_t_impl);
  				Marshal.WriteIntPtr (ctx,
  				                     (int)Marshal.OffsetOf (typeof(LibSvnClient.svn_client_ctx_t), "NotifyFunc2"),
  				                     Marshal.GetFunctionPointerForDelegate (notify_func));
  				log_func = new LibSvnClient.svn_client_get_commit_log_t (svn_client_get_commit_log_impl);
  				Marshal.WriteIntPtr (ctx,
  				                     (int)Marshal.OffsetOf (typeof(LibSvnClient.svn_client_ctx_t), "LogMsgFunc"),
  				                     Marshal.GetFunctionPointerForDelegate (log_func));
  				progress_func = new LibSvnClient.svn_ra_progress_notify_func_t (svn_ra_progress_notify_func_t_impl);
  				Marshal.WriteIntPtr (ctx,
  				                     (int)Marshal.OffsetOf (typeof(LibSvnClient.svn_client_ctx_t), "progress_func"),
  				                     Marshal.GetFunctionPointerForDelegate (progress_func));
  				cancel_func = new LibSvnClient.svn_cancel_func_t (svn_cancel_func_t_impl);
  				Marshal.WriteIntPtr (ctx,
  				                     (int)Marshal.OffsetOf (typeof(LibSvnClient.svn_client_ctx_t), "cancel_func"),
  				                     Marshal.GetFunctionPointerForDelegate (cancel_func));
  
  				Marshal.WriteIntPtr (ctx,
  				                     (int)Marshal.OffsetOf (typeof(LibSvnClient.svn_client_ctx_t), "config"),
  				                     config_hash);
  
  				if (!SvnClient.Pre_1_7) {
  					IntPtr scratch = newpool (IntPtr.Zero);
  					svn.wc_context_create (out wc_ctx, IntPtr.Zero, pool, scratch);
  					Marshal.WriteIntPtr (ctx,
  					                     (int)Marshal.OffsetOf (typeof(LibSvnClient.svn_client_ctx_t), "wc_ctx"),
  					                     wc_ctx);
  					apr.pool_destroy (scratch);
  				}
  
  				IntPtr providers = apr.array_make (pool, 16, IntPtr.Size);
  				IntPtr item;
  			
  				// The main disk-caching auth providers, for both
  				// 'username/password' creds and 'username' creds.
  			
  				item = apr.array_push (providers);
  				svn.client_get_simple_provider (item, pool);
  
  				item = apr.array_push (providers);
  				svn.client_get_username_provider (item, pool);
  			
  				// The server-cert, client-cert, and client-cert-password providers
  			
  				item = apr.array_push (providers);
  				svn.client_get_ssl_server_trust_file_provider (item, pool);
  			
  				item = apr.array_push (providers);
  				svn.client_get_ssl_client_cert_file_provider (item, pool);
  			
  				item = apr.array_push (providers);
  				svn.client_get_ssl_client_cert_pw_file_provider (item, pool);
  
  				// Two basic prompt providers: username/password, and just username.
  
  				item = apr.array_push (providers);
  				svn.client_get_simple_prompt_provider (item, OnAuthSimplePromptCallback, IntPtr.Zero, 2, pool);
  			
  				item = apr.array_push (providers);
  				svn.client_get_username_prompt_provider (item, OnAuthUsernamePromptCallback, IntPtr.Zero, 2, pool);
  			
  				// Three ssl prompt providers, for server-certs, client-certs,
  				// and client-cert-passphrases.
  			
  				item = apr.array_push (providers);
  				svn.client_get_ssl_server_trust_prompt_provider (item, OnAuthSslServerTrustPromptCallback, IntPtr.Zero, pool);
  			
  				item = apr.array_push (providers);
  				svn.client_get_ssl_client_cert_prompt_provider (item, OnAuthSslClientCertPromptCallback, IntPtr.Zero, 2, pool);
  			
  				item = apr.array_push (providers);
  				svn.client_get_ssl_client_cert_pw_prompt_provider (item, OnAuthSslClientCertPwPromptCallback, IntPtr.Zero, 2, pool);
  
  				// Create the authentication baton			
  				svn.auth_open (out auth_baton, providers, pool);
  
  				Marshal.WriteIntPtr (ctx,
  				                     (int)Marshal.OffsetOf (typeof(LibSvnClient.svn_client_ctx_t), "auth_baton"),
  				                     auth_baton);
  			}
  		}
  		
  		public void Dispose ()
  		{
  			lock (svn) {
  				if (!disposed) {
  					if (apr != null)
  						apr.pool_destroy (pool);
  					disposed = true;
  				}
  			}
  		}
  		
  		~UnixSvnBackend ()
  		{
  			Dispose ();
  		}
  		
  		static IntPtr GetCancelError ()
  		{
  			// Subversion destroys the error pool to dispose the error object,
  			// so we need to use a non-shared pool.
  			IntPtr localpool = newpool (IntPtr.Zero);
  			var error = new LibSvnClient.svn_error_t {
  				apr_err = LibApr.APR_OS_START_USEERR,
  				message = GettextCatalog.GetString ("Operation cancelled."),
  				pool = localpool,
  			};
  
  			return apr.pcalloc (localpool, error);
  		}
  
  		static readonly LibSvnClient.svn_auth_simple_prompt_func_t OnAuthSimplePromptCallback = OnAuthSimplePrompt;
  		static IntPtr OnAuthSimplePrompt (ref IntPtr cred, IntPtr baton, string realm, string user_name, bool may_save, IntPtr pool)
  		{
  			LibSvnClient.svn_auth_cred_simple_t data = new LibSvnClient.svn_auth_cred_simple_t ();
  			data.username = user_name;
  			bool ms;
  			if (SimpleAuthenticationPrompt (realm, may_save, ref data.username, out data.password, out ms)) {
  				data.may_save = ms;
  				cred = apr.pcalloc (pool, data);
  				return IntPtr.Zero;
  			} else {
  				data.password = "";
  				data.username = "";
  				data.may_save = false;
  				cred = apr.pcalloc (pool, data);
  				return GetCancelError ();
  			}
  		}
  
  		static readonly LibSvnClient.svn_auth_username_prompt_func_t OnAuthUsernamePromptCallback = OnAuthUsernamePrompt;
  		static IntPtr OnAuthUsernamePrompt (ref IntPtr cred, IntPtr baton, string realm, bool may_save, IntPtr pool)
  		{
  			LibSvnClient.svn_auth_cred_username_t data = new LibSvnClient.svn_auth_cred_username_t {
  				username = "",
  			};
  			bool ms;
  			if (UserNameAuthenticationPrompt (realm, may_save, ref data.username, out ms)) {
  				data.may_save = ms;
  				cred = apr.pcalloc (pool, data);
  				return IntPtr.Zero;
  			} else {
  				data.username = "";
  				data.may_save = false;
  				cred = apr.pcalloc (pool, data);
  				return GetCancelError ();
  			}
  		}
  
  		static readonly LibSvnClient.svn_auth_ssl_server_trust_prompt_func_t OnAuthSslServerTrustPromptCallback = OnAuthSslServerTrustPrompt;
  		static IntPtr OnAuthSslServerTrustPrompt (ref IntPtr cred, IntPtr baton, string realm, UInt32 failures, ref LibSvnClient.svn_auth_ssl_server_cert_info_t cert_info, bool may_save, IntPtr pool)
  		{
  			var data = new LibSvnClient.svn_auth_cred_ssl_server_trust_t ();
  
  			var ci = new CertficateInfo {
  				AsciiCert = cert_info.ascii_cert,
  				Fingerprint = cert_info.fingerprint,
  				HostName = cert_info.hostname,
  				IssuerName = cert_info.issuer_dname,
  				ValidFrom = cert_info.valid_from,
  				ValidUntil = cert_info.valid_until,
  			};
  
  			SslFailure accepted_failures;
  			bool ms;
  			if (SslServerTrustAuthenticationPrompt (realm, (SslFailure) failures, may_save, ci, out accepted_failures, out ms) && accepted_failures != SslFailure.None) {
  				data.may_save = ms ;
  				data.accepted_failures = (uint) accepted_failures;
  				cred = apr.pcalloc (pool, data);
  				return IntPtr.Zero;
  			} else {
  				data.accepted_failures = 0;
  				data.may_save = false;
  				cred = apr.pcalloc (pool, data);
  				return GetCancelError ();
  			}
  		}
  
  		static readonly LibSvnClient.svn_auth_ssl_client_cert_prompt_func_t OnAuthSslClientCertPromptCallback = OnAuthSslClientCertPrompt;
  		static IntPtr OnAuthSslClientCertPrompt (ref IntPtr cred, IntPtr baton, string realm, bool may_save, IntPtr pool)
  		{
  			var data = new LibSvnClient.svn_auth_cred_ssl_client_cert_t ();
  			bool ms;
  			if (SslClientCertAuthenticationPrompt (realm, may_save, out data.cert_file, out ms)) {
  				data.may_save = ms;
  				cred = apr.pcalloc (pool, data);
  				return IntPtr.Zero;
  			} else {
  				data.cert_file = "";
  				data.may_save = false;
  				cred = apr.pcalloc (pool, data);
  				return GetCancelError ();
  			}
  		}
  
  		static readonly LibSvnClient.svn_auth_ssl_client_cert_pw_prompt_func_t OnAuthSslClientCertPwPromptCallback = OnAuthSslClientCertPwPrompt;
  		static IntPtr OnAuthSslClientCertPwPrompt (ref IntPtr cred, IntPtr baton, string realm, bool may_save, IntPtr pool)
  		{
  			var data = new LibSvnClient.svn_auth_cred_ssl_client_cert_pw_t ();
  			bool ms;
  			if (SslClientCertPwAuthenticationPrompt (realm, may_save, out data.password, out ms)) {
  				data.may_save = ms;
  				cred = apr.pcalloc (pool, data);
  				return IntPtr.Zero;
  			} else {
  				data.password = "";
  				data.may_save = false;
  				cred = apr.pcalloc (pool, data);
  				return GetCancelError ();
  			}
  		}
  		
  		// Wrappers for native interop
  		
  		public override string GetVersion ()
  		{
  			return SvnClient.GetVersion ();
  		}
  
  		public override IEnumerable<DirectoryEntry> List (FilePath path, bool recurse, SvnRevision rev)
  		{
  			return ListUrl (path, recurse, rev);
  		}
  		
  		static string NormalizePath (string pathOrUrl, IntPtr localpool)
  		{
  			return SvnClient.NormalizePath (pathOrUrl, localpool);
  		}
  
  		static IntPtr NormalizePaths (IntPtr pool, params FilePath[] paths)
  		{
  			IntPtr array = apr.array_make (pool, 0, IntPtr.Size);
  			foreach (string path in paths) {
  				string pathorurl = NormalizePath (path, pool);
  				IntPtr item = apr.array_push (array);
  				Marshal.WriteIntPtr (item, apr.pstrdup (pool, pathorurl));
  			}
  			return array;
  		}
  		
  		public override IEnumerable<DirectoryEntry> ListUrl (string url, bool recurse, SvnRevision rev)
  		{
  			if (url == null)
  				throw new ArgumentNullException ();
  
  			LibSvnClient.Rev revision = (LibSvnClient.Rev) rev;
  			IntPtr localpool = IntPtr.Zero;
  			List<DirectoryEntry> items = new List<DirectoryEntry> ();
  
  			try {
  				IntPtr hash;
  
  				localpool = TryStartOperation (null);
  				url = NormalizePath (url, localpool);
  				
  				CheckError (svn.client_ls (out hash, url, ref revision,
  				                           recurse, ctx, localpool));
  				
  				IntPtr item = apr.hash_first (localpool, hash);
  				LibSvnClient.svn_dirent_t ent;
  				string name;
  				while (apr.hash_iterate<LibSvnClient.svn_dirent_t>(ref item, out ent, out name)) {
  					var dent = new DirectoryEntry {
  						Name = name,
  						IsDirectory = ent.kind == LibSvnClient.svn_node_kind_t.Dir,
  						Size = ent.size,
  						HasProps = ent.has_props,
  						CreatedRevision = (int) ent.created_rev,
  						Time = new DateTime (1970, 1, 1).AddTicks(ent.time * 10),
  						LastAuthor = ent.last_author,
  					};
  					items.Add (dent);
  				}
  			} finally {
  				TryEndOperation (localpool);
  			}
  			
  			return items;
  		}
  
  		public override IEnumerable<VersionInfo> Status (Repository repo, FilePath path, SvnRevision rev, bool descendDirs, bool changedItemsOnly, bool remoteStatus)
  		{
  			if (path == FilePath.Null)
  				throw new ArgumentNullException ();
  		
  			LibSvnClient.Rev revision = (LibSvnClient.Rev) rev;
  			ArrayList ret = new ArrayList ();
  			
  			StatusCollector collector = new StatusCollector (ret);
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (null);
  				string pathorurl = NormalizePath (path, localpool);
  				CheckError (svn.client_status (IntPtr.Zero, pathorurl, ref revision,
  				                               collector.Func,
  				                               IntPtr.Zero, descendDirs, 
  				                               !changedItemsOnly, 
  				                               remoteStatus,
  				                               false,
  				                               false,
  				                               ctx, localpool));
  			} catch (SubversionException e) {
  				// SVN_ERR_WC_NOT_WORKING_COPY and SVN_ERR_WC_NOT_FILE.
  				if (e.ErrorCode != 155007 && e.ErrorCode != 155008)
  					throw;
  			} finally {
  				TryEndOperation (localpool);
  			}
  
  			List<VersionInfo> nodes = new List<VersionInfo>();
  			foreach (LibSvnClient.StatusEnt ent in ret)
  				nodes.Add (CreateNode (ent, repo));
  			return nodes;
  		}
  
  		public override IEnumerable<SvnRevision> Log (Repository repo, FilePath path, SvnRevision revStart, SvnRevision revEnd)
  		{
  			if (path == FilePath.Null)
  				throw new ArgumentNullException ();
  			
  			LibSvnClient.Rev revisionStart = (LibSvnClient.Rev) revStart;
  			LibSvnClient.Rev revisionEnd = (LibSvnClient.Rev) revEnd;
  
  			List<SvnRevision> ret = new List<SvnRevision> ();
  			IntPtr strptr = IntPtr.Zero;
  
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (null);
  				IntPtr array = apr.array_make (localpool, 0, IntPtr.Size);
  				IntPtr first = apr.array_push (array);
  				string pathorurl = NormalizePath (path, localpool);
  				strptr = Marshal.StringToHGlobalAnsi (pathorurl);
  				Marshal.WriteIntPtr (first, strptr);
  				
  				LogCollector collector = new LogCollector ((SubversionRepository)repo, ret, ctx);
  				
  				CheckError (svn.client_log (array, ref revisionStart, ref revisionEnd, true, false,
  				                            collector.Func,
  				                            IntPtr.Zero, ctx, localpool));
  			} finally {
  				if (strptr != IntPtr.Zero)
  					Marshal.FreeHGlobal (strptr);
  				TryEndOperation (localpool);
  			}
  			
  			return ret;
  		}
  		
  		public override Annotation[] GetAnnotations (Repository repo, FilePath file, SvnRevision revStart, SvnRevision revEnd)
  		{
  			if (file == FilePath.Null)
  				throw new ArgumentNullException ();
  				
  			LibSvnClient.Rev revisionStart = (LibSvnClient.Rev) revStart;
  			LibSvnClient.Rev revisionEnd = (LibSvnClient.Rev) revEnd;
  
  			MemoryStream data = new MemoryStream ();
  			int numAnnotations = 0;
  			Cat (file, SvnRevision.Base, data);
  
  			using (StreamReader reader = new StreamReader (data)) {
  				reader.BaseStream.Seek (0, SeekOrigin.Begin);
  				while (reader.ReadLine () != null)
  					numAnnotations++;
  			}
  
  			Annotation[] annotations = new Annotation [numAnnotations];
  			AnnotationCollector collector = new AnnotationCollector (annotations, repo);
  
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (null);
  				string path = NormalizePath (file.FullPath, localpool);
  				CheckError (svn.client_blame (path, ref revisionStart, ref revisionEnd, collector.Func, IntPtr.Zero, ctx, localpool));
  			} finally {
  				TryEndOperation (localpool);
  			}
  			
  			return annotations;
  		}
  
  		public override string GetTextAtRevision (string repositoryPath, Revision revision, string rootPath)
  		{
  			MemoryStream memstream = new MemoryStream ();
  			try {
  				Cat (repositoryPath, (SvnRevision) revision, memstream);
  			} catch (SubversionException e) {
  				// File got added/removed at some point.
  				// SVN_ERR_FS_NOT_FOUND
  				if (e.ErrorCode == 160013)
  					return "";
  				// We tried on a directory
  				// SVN_ERR_FS_NOT_FILE
  				if (e.ErrorCode == 160017)
  					return "";
  				throw;
  			}
  
  			var buffer = memstream.GetBuffer ();
  			try {
  				if (IsBinary (buffer, memstream.Length))
  					return null;
  				return Encoding.UTF8.GetString (buffer, 0, (int) memstream.Length);
  			} catch {
  				return "";
  			}
  		}
  		
  		public void Cat (string pathorurl, SvnRevision rev, Stream stream)
  		{
  			if (pathorurl == null || stream == null)
  				throw new ArgumentNullException ();
  		
  			LibSvnClient.Rev revision = (LibSvnClient.Rev) rev;
  			
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (null);
  				pathorurl = NormalizePath (pathorurl, localpool);
  				StreamCollector collector = new StreamCollector (stream);
  				IntPtr svnstream = svn.stream_create (IntPtr.Zero, localpool);
  				svn.stream_set_write (svnstream, collector.Func);
  				// Setting peg_revision to revision.
  				// Otherwise, it will use Head as peg and it will throw exceptions.
  				CheckError (svn.client_cat2 (svnstream, pathorurl, ref revision, ref revision, ctx, localpool), 195007);
  			} finally {
  				TryEndOperation (localpool);
  			}
  		}
  
  		public override void Update (FilePath path, bool recurse, ProgressMonitor monitor)
  		{
  			if (path == FilePath.Null || monitor == null)
  				throw new ArgumentNullException();
  			
  			LibSvnClient.Rev rev = LibSvnClient.Rev.Head;
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (monitor);
  				string pathorurl = NormalizePath (path, localpool);
  				IntPtr result = Marshal.AllocHGlobal (IntPtr.Size);
  				CheckError (svn.client_update (result, pathorurl, ref rev, recurse, ctx, localpool));
  				Marshal.FreeHGlobal (result);
  			} finally {
  				TryEndOperation (localpool);
  			}
  		}
  
  		public override void Revert (FilePath[] paths, bool recurse, ProgressMonitor monitor)
  		{
  			if (paths == null || monitor == null)
  				throw new ArgumentNullException();
  
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (monitor);
  				IntPtr array = NormalizePaths (localpool, paths);
  				CheckError (svn.client_revert (array, recurse, ctx, localpool));
  			} finally {
  				TryEndOperation (localpool);
  			}
  		}
  
  		public override void Add (FilePath path, bool recurse, ProgressMonitor monitor)
  		{
  			if (path == FilePath.Null || monitor == null)
  				throw new ArgumentNullException ();
  
  			nb = new notify_baton ();
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (monitor);
  				string pathorurl = NormalizePath (path, localpool);
  				CheckError (svn.client_add3 (pathorurl, recurse, true, false, ctx, localpool));
  			} finally {
  				TryEndOperation (localpool);
  			}
  		}
  
  		public override void Checkout (string url, FilePath path, Revision revision, bool recurse, ProgressMonitor monitor)
  		{
  			if (url == null || monitor == null)
  				throw new ArgumentNullException ();
  			
  			if (revision == null)
  				revision = SvnRevision.Head;
  			var rev = (LibSvnClient.Rev) revision;
  			
  			nb = new notify_baton ();
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (monitor);
  				// Using Uri here because the normalization method doesn't remove the redundant port number when using https
  				url = NormalizePath (new Uri(url).ToString(), localpool);
  				string npath = NormalizePath (path, localpool);
  				CheckError (svn.client_checkout (IntPtr.Zero, url, npath, ref rev, recurse, ctx, localpool));
  			} catch (SubversionException e) {
  				if (e.ErrorCode != 200015)
  					throw;
  
  				if (Directory.Exists (path.ParentDirectory))
  					FileService.DeleteDirectory (path.ParentDirectory);
  			} finally {
  				TryEndOperation (localpool);
  			}
  		}
  
  		public override void Commit (FilePath[] paths, string message, ProgressMonitor monitor)
  		{
  			if (paths == null || message == null || monitor == null)
  				throw new ArgumentNullException();
  
  			nb = new notify_baton ();
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (monitor);
  				IntPtr array = NormalizePaths (localpool, paths);
  				IntPtr commit_info = IntPtr.Zero;
  				commitmessage = message;
  				
  				CheckError (svn.client_commit (ref commit_info, array, false, ctx, localpool));
  				unsafe {
  					if (commit_info != IntPtr.Zero) {
  						monitor.Log.WriteLine ();
  						monitor.Log.WriteLine (GettextCatalog.GetString ("Revision: {0}", ((LibSvnClient.svn_client_commit_info_t *) commit_info.ToPointer())->revision));
  					}
  				}
  			} finally {
  				commitmessage = null;
  				TryEndOperation (localpool);
  			}
  		}
  
  		public override void Mkdir (string[] paths, string message, ProgressMonitor monitor) 
  		{
  			if (paths == null || monitor == null)
  				throw new ArgumentNullException ();
  
  			nb = new notify_baton ();
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (monitor);
  				IntPtr array = NormalizePaths (localpool, paths.Select (p => (FilePath)p).ToArray ());
  				
  				commitmessage = message;
  
  				IntPtr commit_info = IntPtr.Zero;
  				CheckError (svn.client_mkdir2 (ref commit_info, array, ctx, localpool));
  			} finally {	
  				commitmessage = null;
  				TryEndOperation (localpool);
  			}
  		}
  
  		public override void Delete (FilePath path, bool force, ProgressMonitor monitor)
  		{
  			if (path == FilePath.Null || monitor == null)
  				throw new ArgumentNullException ();
  
  			nb = new notify_baton ();
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (monitor);
  				IntPtr array = NormalizePaths (localpool, path);
  				IntPtr commit_info = IntPtr.Zero;
  				CheckError (svn.client_delete (ref commit_info, array, force, ctx, localpool));
  			} finally {
  				commitmessage = null;
  				TryEndOperation (localpool);
  			}
  		}
  
  		public override void Move (FilePath srcPath, FilePath destPath, SvnRevision rev, bool force, ProgressMonitor monitor)
  		{
  			if (srcPath == FilePath.Null || destPath == FilePath.Null || monitor == null)
  				throw new ArgumentNullException ();
  			
  			LibSvnClient.Rev revision = (LibSvnClient.Rev) rev;
  
  			nb = new notify_baton ();
  			IntPtr commit_info = IntPtr.Zero;
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (monitor);
  				string nsrcPath = NormalizePath (srcPath, localpool);
  				string ndestPath = NormalizePath (destPath, localpool);
  				CheckError (svn.client_move (ref commit_info, nsrcPath, ref revision,
  				                             ndestPath, force, ctx, localpool));
  			} finally {
  				TryEndOperation (localpool);
  			}
  		}
  
  		public override void Lock (ProgressMonitor monitor, string comment, bool stealLock, params FilePath[] paths)
  		{
  			nb = new notify_baton ();
  			IntPtr localpool = IntPtr.Zero;
  			try {
  				localpool = TryStartOperation (monitor);
  				IntPtr array = NormalizePaths (localpool, paths);
  				lockFileList = new ArrayList ();
  				requiredLockState = LibSvnClient.NotifyLockState.Locked;
  				
  				CheckError (svn.client_lock (array, comment, stealLock, ctx, localpool));
  				if (paths.Length != lockFileList.Count)
  					throw new SubversionException ("Lock operation failed.");
  			} finally {
  				lockFileList = null;
  				TryEndOperation (localpool);
  			}
  		}
  		
  		public override void Unlock (ProgressMonitor monitor, bool breakLock, params FilePath[] paths)
  		{
  			nb = new notify_baton ();
  			IntPtr localpool = IntPtr.Zero;
  			try {
  
gt: 				localpool = TryStartOperation (monitor);