PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/SourceServerSharp/src/QQn.SourceServerIndexer/Providers/SubversionResolver.cs

#
C# | 428 lines | 302 code | 76 blank | 50 comment | 47 complexity | a38d22c7dbbf0e3d6e10c2c20328e428 MD5 | raw file
  1. // **************************************************************************
  2. // * $Id: SubversionResolver.cs 40 2006-12-17 12:54:18Z bhuijben $
  3. // * $HeadURL: https://sourceserversharp.googlecode.com/svn/trunk/src/QQn.SourceServerIndexer/Providers/SubversionResolver.cs $
  4. // **************************************************************************
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Text;
  8. using QQn.SourceServerSharp.Framework;
  9. using System.IO;
  10. using System.Diagnostics;
  11. using Microsoft.Build.Utilities;
  12. using System.Xml;
  13. using System.Xml.XPath;
  14. namespace QQn.SourceServerSharp.Providers
  15. {
  16. /// <summary>
  17. /// Implements the <see cref="SourceProvider"/> class for subversion (http://subversion.tigris.org/)
  18. /// </summary>
  19. public class SubversionResolver : SourceResolver, ISourceProviderDetector
  20. {
  21. string _svnExePath;
  22. /// <summary>
  23. ///
  24. /// </summary>
  25. public SubversionResolver(IndexerState state)
  26. : base(state, "Subversion")
  27. {
  28. //GC.KeepAlive(null);
  29. }
  30. #region ### Availability
  31. /// <summary>
  32. ///
  33. /// </summary>
  34. public override bool Available
  35. {
  36. get { return (SvnExePath != null); }
  37. }
  38. bool _searchedPath;
  39. /// <summary>
  40. ///
  41. /// </summary>
  42. protected string SvnExePath
  43. {
  44. get
  45. {
  46. if(_searchedPath || !string.IsNullOrEmpty(_svnExePath))
  47. return _svnExePath;
  48. string path = Environment.GetEnvironmentVariable("PATH");
  49. if(path == null)
  50. path = "";
  51. else
  52. path = path.ToUpperInvariant();
  53. string[] pathItems = path.Split(Path.PathSeparator);
  54. // First try to find some directory with subversion in its name (probably ok for 99% of the cases)
  55. foreach(string item in pathItems)
  56. {
  57. if(item.Contains("SUBVERSION"))
  58. {
  59. string file = Path.GetFullPath(Path.Combine(item.Trim(), "SVN.EXE"));
  60. if(File.Exists(file))
  61. return _svnExePath = file;
  62. }
  63. }
  64. // Search whole path
  65. _searchedPath = true;
  66. return _svnExePath = SssUtils.FindExecutable("svn.exe");
  67. }
  68. }
  69. #region #### ISourceProviderDetector Members
  70. /// <summary>
  71. /// SourceIndex detector which tries to find valid providers
  72. /// </summary>
  73. /// <param name="state"></param>
  74. /// <returns>true if one or more files might be managed in subversion, otherwise false</returns>
  75. public bool CanProvideSources(QQn.SourceServerSharp.Framework.IndexerState state)
  76. {
  77. SortedList<string, string> directories = new SortedList<string,string>(StringComparer.InvariantCultureIgnoreCase);
  78. foreach(SourceFile file in state.SourceFiles.Values)
  79. {
  80. string dir = file.File.DirectoryName;
  81. if (directories.ContainsKey(dir))
  82. continue;
  83. directories.Add(dir, dir);
  84. if (Directory.Exists(Path.Combine(dir, ".svn")))
  85. return true;
  86. else if (Directory.Exists(Path.Combine(dir, "_svn")))
  87. return true; // Might not work; requires environment variable
  88. }
  89. return false;
  90. }
  91. #endregion #### ISourceProviderDetector Members
  92. #endregion ### Availability
  93. static bool ContainsAt(string hayStack, int index, string needle)
  94. {
  95. if (hayStack == null)
  96. throw new ArgumentNullException("hayStack");
  97. else if (index < 0)
  98. throw new ArgumentException("offset out of range", "index");
  99. else if (string.IsNullOrEmpty(needle))
  100. throw new ArgumentNullException("needle");
  101. if (hayStack.Length < index + needle.Length)
  102. return false;
  103. return 0 == string.Compare(hayStack, index, needle, 0, needle.Length);
  104. }
  105. /// <summary>
  106. ///
  107. /// </summary>
  108. /// <returns></returns>
  109. public override bool ResolveFiles()
  110. {
  111. ProcessStartInfo psi = new ProcessStartInfo(SvnExePath);
  112. psi.UseShellExecute = false;
  113. psi.RedirectStandardOutput = true;
  114. psi.RedirectStandardError = true;
  115. psi.RedirectStandardInput = true;
  116. CommandLineBuilder cb = new CommandLineBuilder();
  117. cb.AppendSwitch("--non-interactive");
  118. //cb.AppendSwitch("--verbose");
  119. cb.AppendSwitch("--non-recursive");
  120. cb.AppendSwitch("--xml");
  121. cb.AppendSwitch("status");
  122. SortedList<string, SourceFile> files = new SortedList<string, SourceFile>(StringComparer.InvariantCultureIgnoreCase);
  123. SortedList<string, bool> volumes = new SortedList<string, bool>(StringComparer.InvariantCultureIgnoreCase);
  124. foreach (KeyValuePair<string, SourceFile> file in State.SourceFiles)
  125. {
  126. if (file.Value.IsResolved)
  127. continue;
  128. // Check if the volume exists; if not 'svn status' returns "svn: Error resolving case of 'q:\q'"
  129. string root = Path.GetPathRoot(file.Key);
  130. bool rootExists;
  131. if(!volumes.TryGetValue(root, out rootExists))
  132. {
  133. rootExists = Directory.Exists(root);
  134. volumes.Add(root, rootExists);
  135. }
  136. if(!rootExists)
  137. continue;
  138. files.Add(file.Key, file.Value);
  139. cb.AppendFileNameIfNotNull(file.Key);
  140. }
  141. psi.Arguments = cb.ToString();
  142. using (Process p = Process.Start(psi))
  143. {
  144. try
  145. {
  146. p.StandardInput.Close();
  147. p.ErrorDataReceived += new DataReceivedEventHandler(svnStatus_ErrorDataReceived);
  148. p.OutputDataReceived += new DataReceivedEventHandler(svnStatus_OutputDataReceived);
  149. _svnStatusOutput = new StringBuilder();
  150. _receivedError = false;
  151. p.BeginErrorReadLine();
  152. p.BeginOutputReadLine();
  153. p.WaitForExit();
  154. string output = _svnStatusOutput.ToString();
  155. _svnStatusOutput = null;
  156. //string errs = p.StandardError.ReadToEnd();
  157. if (_receivedError)
  158. {
  159. XmlDocument doc = new XmlDocument();
  160. int nStart = 0;
  161. int i;
  162. while (0 <= (i = output.IndexOf("<target", nStart)))
  163. {
  164. int nNext = output.IndexOf("<", i + 5);
  165. if (nNext < 0 ||
  166. ContainsAt(output, nNext, "<target") ||
  167. (ContainsAt(output, nNext, "</") && !ContainsAt(output, nNext + 2, "target")))
  168. {
  169. int nClose = output.IndexOf(">", i + 5);
  170. if (nClose >= 0)
  171. {
  172. doc.LoadXml(output.Substring(i, nClose - i) + " />");
  173. string path = State.NormalizePath(doc.DocumentElement.GetAttribute("path"));
  174. files.Remove(path);
  175. }
  176. }
  177. if (nNext >= 0)
  178. nStart = nNext;
  179. else
  180. break;
  181. }
  182. }
  183. p.WaitForExit();
  184. }
  185. catch (Exception e)
  186. {
  187. throw new InvalidOperationException(string.Format("Error executing '{0}' with '{1}'", psi.FileName, psi.Arguments), e);
  188. }
  189. }
  190. cb = new CommandLineBuilder();
  191. cb.AppendSwitch("--non-interactive");
  192. cb.AppendSwitch("--xml");
  193. cb.AppendSwitch("info");
  194. foreach (SourceFile file in files.Values)
  195. {
  196. cb.AppendFileNameIfNotNull(file.FullName);
  197. }
  198. psi.Arguments = cb.ToString();
  199. psi.RedirectStandardError = false;
  200. using (Process p = Process.Start(psi))
  201. {
  202. p.StandardInput.Close();
  203. string output = p.StandardOutput.ReadToEnd().TrimEnd();
  204. if (!output.EndsWith("</info>"))
  205. output += "</info>";
  206. XPathDocument doc = new XPathDocument(new StringReader(output));
  207. XPathNavigator nav = doc.CreateNavigator();
  208. try
  209. {
  210. foreach (XPathNavigator i in nav.Select("/info/entry[@path and url and repository/root and commit/@revision]"))
  211. {
  212. SourceFile file;
  213. string path = State.NormalizePath(i.GetAttribute("path", ""));
  214. if (!State.SourceFiles.TryGetValue(path, out file))
  215. continue;
  216. if (file.IsResolved)
  217. continue; // No need to resolve it again
  218. XPathNavigator urlNav = i.SelectSingleNode("url");
  219. XPathNavigator repositoryRootNav = i.SelectSingleNode("repository/root");
  220. XPathNavigator commit = i.SelectSingleNode("commit");
  221. if (urlNav == null || repositoryRootNav == null || commit == null)
  222. continue; // Not enough information to provide reference
  223. string itemPath = urlNav.Value;
  224. string reposRoot = repositoryRootNav.Value;
  225. if (!reposRoot.EndsWith("/"))
  226. reposRoot += '/';
  227. if (!itemPath.StartsWith(reposRoot, StringComparison.InvariantCultureIgnoreCase))
  228. continue;
  229. else
  230. itemPath = itemPath.Substring(reposRoot.Length);
  231. string commitRev = commit.GetAttribute("revision", "");
  232. string wcRev = i.GetAttribute("revision", "");
  233. file.SourceReference = new SubversionSourceReference(this, file, new Uri(reposRoot), new Uri(itemPath, UriKind.Relative), int.Parse(commitRev), int.Parse(wcRev));
  234. }
  235. }
  236. catch (XmlException e)
  237. {
  238. throw new SourceIndexToolException("svn", "Received invalid xml from subversion", e);
  239. }
  240. p.WaitForExit();
  241. }
  242. return true;
  243. }
  244. StringBuilder _svnStatusOutput;
  245. bool _receivedError;
  246. void svnStatus_OutputDataReceived(object sender, DataReceivedEventArgs e)
  247. {
  248. if(e.Data != null)
  249. _svnStatusOutput.Append(e.Data);
  250. }
  251. void svnStatus_ErrorDataReceived(object sender, DataReceivedEventArgs e)
  252. {
  253. if (!string.IsNullOrEmpty(e.Data))
  254. _receivedError = true;
  255. }
  256. /// <summary>
  257. ///
  258. /// </summary>
  259. /// <param name="writer"></param>
  260. public override void WriteEnvironment(StreamWriter writer)
  261. {
  262. writer.Write(Id);
  263. writer.WriteLine(@"__TRG=%targ%\%var7%%fnbksl%(%var4%)\%var5%\%fnfile%(%var4%)");
  264. writer.Write(Id);
  265. writer.Write("__CMD=svn.exe export \"%var3%%var4%@%var6%\" \"%");
  266. writer.Write(Id);
  267. writer.WriteLine("__TRG%\" --non-interactive --quiet");
  268. }
  269. /// <summary>
  270. ///
  271. /// </summary>
  272. public override int SourceEntryVariableCount
  273. {
  274. get { return 5; }
  275. }
  276. }
  277. /// <summary>
  278. ///
  279. /// </summary>
  280. public class SubversionSourceReference : SourceReference
  281. {
  282. readonly Uri _reposRoot;
  283. readonly Uri _itemPath;
  284. readonly int _commitRev;
  285. readonly int _wcRev;
  286. /// <summary>
  287. ///
  288. /// </summary>
  289. /// <param name="resolver"></param>
  290. /// <param name="sourceFile"></param>
  291. /// <param name="reposRoot"></param>
  292. /// <param name="itemPath"></param>
  293. /// <param name="commitRev"></param>
  294. /// <param name="wcRev"></param>
  295. public SubversionSourceReference(SubversionResolver resolver, SourceFile sourceFile, Uri reposRoot, Uri itemPath, int commitRev, int wcRev)
  296. : base(resolver, sourceFile)
  297. {
  298. if (reposRoot == null)
  299. throw new ArgumentNullException("reposRoot");
  300. else if (itemPath == null)
  301. throw new ArgumentNullException("itemPath");
  302. _reposRoot = reposRoot;
  303. _itemPath = itemPath;
  304. _commitRev = commitRev;
  305. _wcRev = wcRev;
  306. }
  307. static string ReposSubDir(Uri reposUri)
  308. {
  309. StringBuilder sb = new StringBuilder();
  310. sb.Append("svn-");
  311. sb.Append(reposUri.Scheme);
  312. sb.Append("\\");
  313. if(!string.IsNullOrEmpty(reposUri.Host))
  314. {
  315. foreach(char c in reposUri.Host)
  316. if(char.IsLetterOrDigit(c) || (".-".IndexOf(c) >= 0))
  317. sb.Append(c);
  318. else
  319. sb.Append('_');
  320. if(reposUri.Port >= 1)
  321. sb.AppendFormat("_{0}", reposUri.Port);
  322. }
  323. if(!string.IsNullOrEmpty(reposUri.AbsolutePath))
  324. {
  325. sb.Append(reposUri.AbsolutePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
  326. }
  327. if(sb[sb.Length-1] != Path.DirectorySeparatorChar)
  328. sb.Append(Path.DirectorySeparatorChar);
  329. return sb.ToString();
  330. }
  331. /// <summary>
  332. /// Gets a list of entries for the sourcefiles
  333. /// </summary>
  334. /// <returns></returns>
  335. public override string[] GetSourceEntries()
  336. {
  337. return new string[]
  338. {
  339. _reposRoot.ToString(),
  340. _itemPath.ToString(),
  341. _commitRev.ToString(),
  342. _wcRev.ToString(),
  343. ReposSubDir(_reposRoot)
  344. };
  345. }
  346. }
  347. }