diff --git a/.gitignore b/.gitignore index b1931b11..fd52ad52 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,4 @@ Reports test-results dist build -SharedAssemblyInfo.cs -VersionAssemblyInfo.cs \ No newline at end of file +VersionAssemblyInfo.cs diff --git a/Git/Program.cs b/Git/Program.cs index a9d29ccb..965f0e1c 100644 --- a/Git/Program.cs +++ b/Git/Program.cs @@ -340,7 +340,7 @@ static public void Exit(int exit_code) Git.DefaultRepository.Close(); #if DEBUG - Console.WriteLine("\n\nrunning in DEBUG mode, press any key to exit."); + Console.WriteLine("\n\nrunning in DEBUG mode, press [ENTER] to exit."); Console.In.ReadLine(); #endif Environment.Exit(exit_code); diff --git a/GitSharp.Core/GitSharp.Core.csproj b/GitSharp.Core/GitSharp.Core.csproj index d6ff4e9c..b67deda5 100644 --- a/GitSharp.Core/GitSharp.Core.csproj +++ b/GitSharp.Core/GitSharp.Core.csproj @@ -51,6 +51,9 @@ + + Properties\SharedAssemblyInfo.cs + diff --git a/GitSharp.Core/Transport/TransportLocal.cs b/GitSharp.Core/Transport/TransportLocal.cs index e7153c36..a3c071a7 100644 --- a/GitSharp.Core/Transport/TransportLocal.cs +++ b/GitSharp.Core/Transport/TransportLocal.cs @@ -2,6 +2,7 @@ * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2011, Dominique van de Vorle * * All rights reserved. * @@ -38,17 +39,31 @@ */ using System; +using System.Diagnostics; using System.IO; +using System.Threading; +using GitSharp.Core.Exceptions; using GitSharp.Core.Util; +using Tamir.Streams; + namespace GitSharp.Core.Transport { public class TransportLocal : Transport, IPackTransport { private const string PWD = "."; + protected readonly DirectoryInfo remoteGitDir; - public TransportLocal(Repository local, URIish uri) : base(local, uri) + public TransportLocal(Repository local, URIish uri) + : base(local, uri) { + string dir = FS.resolve(new DirectoryInfo(PWD), uri.Path).FullName; + if(Directory.Exists(Path.Combine(dir, Constants.DOT_GIT))) + { + dir = Path.Combine(dir, Constants.DOT_GIT); + } + + remoteGitDir = new DirectoryInfo(dir); } public static bool canHandle(URIish uri) @@ -71,17 +86,356 @@ public static bool canHandle(URIish uri) public override IFetchConnection openFetch() { - throw new NotImplementedException(); + if (OptionUploadPack.Equals("git-upload-pack") || OptionUploadPack.Equals("git upload-pack")) + { + return new InternalLocalFetchConnection(this); + } + return new ForkLocalFetchConnection(this); } public override IPushConnection openPush() { - throw new NotImplementedException(); + if (OptionReceivePack.Equals("git-receive-pack") || OptionReceivePack.Equals("git receive-pack")) + { + return new InternalLocalPushConnection(this); + } + return new ForkLocalPushConnection(this); } public override void close() { - throw new NotImplementedException(); + // Resources must be established per-connection. } + + protected Process Spawn(string cmd) + { + try + { + ProcessStartInfo psi = new ProcessStartInfo(); + + if (cmd.StartsWith("git-")) + { + psi.FileName = "git"; + psi.Arguments = cmd.Substring(4); + } + else + { + int gitspace = cmd.IndexOf("git "); + if (gitspace >= 0) { + psi.FileName = cmd.Substring(0, gitspace + 3); + psi.Arguments = cmd.Substring(gitspace + 4); + } + else + { + psi.FileName = cmd; + } + } + + if(psi.Arguments.Equals(String.Empty)) + { + psi.Arguments = PWD; + } + else + { + psi.Arguments += " " + PWD; + } + + var process = new Process() { StartInfo = psi}; + process.Start(); + return process; + } + catch (IOException ex) + { + throw new TransportException(Uri, ex.Message, ex); + } + } + + #region Nested types + + private class InternalLocalFetchConnection : BasePackFetchConnection + { + private Thread worker; + private readonly PipedInputStream in_r; + private readonly PipedOutputStream in_w; + + public InternalLocalFetchConnection( TransportLocal transport) + : base( transport ) + { + + Repository dst; + try + { + dst = new Repository(transport.remoteGitDir); + } + catch (IOException) + { + throw new TransportException(uri, "Not a Git directory"); + } + + PipedInputStream out_r; + PipedOutputStream out_w; + try + { + in_r = new PipedInputStream(); + in_w = new PipedOutputStream(in_r); + + out_r = new PipedInputStream(); + out_w = new PipedOutputStream(out_r); + } + catch (IOException ex) + { + dst.Close(); + throw new TransportException(uri, "Cannot connect pipes", ex); + } + + worker = new Thread( () => + { + try + { + UploadPack rp = new UploadPack(dst); + rp.Upload(out_r, in_w, null); + } + catch (IOException ex) + { + // Client side of the pipes should report the problem. + ex.printStackTrace(); + } + catch (Exception ex) + { + // Clients side will notice we went away, and report. + ex.printStackTrace(); + } + finally + { + try + { + out_r.close(); + } + catch (IOException) + { + // Ignore close failure, we probably crashed above. + } + + try + { + in_w.close(); + } catch (IOException) + { + // Ignore close failure, we probably crashed above. + } + + dst.Close(); + } + + }); + worker.Name = "JGit-Upload-Pack"; + worker.Start(); + + init(in_r, out_w); + readAdvertisedRefs(); + } + + + override public void Close() + { + base.Close(); + + if (worker != null) + { + try + { + worker.Join(); + } + catch ( ThreadInterruptedException) + { + // Stop waiting and return anyway. + } + finally + { + worker = null; + } + } + } + } + + private class InternalLocalPushConnection : BasePackPushConnection + { + private Thread worker; + + public InternalLocalPushConnection(TransportLocal transport) + : base(transport) + { + Repository dst; + try + { + dst = new Repository(transport.remoteGitDir); + } + catch (IOException) + { + throw new TransportException(uri, "Not a Git directory"); + } + + PipedInputStream in_r; + PipedOutputStream in_w; + + PipedInputStream out_r; + PipedOutputStream out_w; + try + { + in_r = new PipedInputStream(); + in_w = new PipedOutputStream(in_r); + + out_r = new PipedInputStream(); + out_w = new PipedOutputStream(out_r); + } + catch (IOException ex) + { + dst.Close(); + throw new TransportException(uri, "Cannot connect pipes", ex); + } + + worker = new Thread(() => + { + try + { + ReceivePack rp = new ReceivePack(dst); + rp.receive(out_r, in_w, null); + } + catch (IOException) + { + // Client side of the pipes should report the problem. + } + catch (Exception) + { + // Clients side will notice we went away, and report. + } + finally + { + try + { + out_r.close(); + } + catch (IOException) + { + // Ignore close failure, we probably crashed above. + } + + try + { + in_w.close(); + } + catch (IOException) + { + // Ignore close failure, we probably crashed above. + } + + dst.Close(); + } + }); + worker.Name = "JGit-Receive-Pack"; + worker.Start(); + + init(in_r, out_w); + readAdvertisedRefs(); + } + + public override void Close() + { + base.Close(); + + if (worker != null) + { + try + { + worker.Join(); + } + catch (ThreadInterruptedException) + { + // Stop waiting and return anyway. + } + finally + { + worker = null; + } + } + } + } + + private class ForkLocalFetchConnection : BasePackFetchConnection + { + private Process uploadPack; + + public ForkLocalFetchConnection( TransportLocal transport) + : base(transport) + { + uploadPack = transport.Spawn(transport.OptionUploadPack); + + Stream upIn = new BufferedStream(uploadPack.StandardInput.BaseStream); + Stream upOut = new BufferedStream(uploadPack.StandardOutput.BaseStream); + + init(upIn, upOut); + readAdvertisedRefs(); + } + + public override void Close() + { + base.Close(); + + if (uploadPack != null) + { + try + { + uploadPack.WaitForExit(); + } + catch (ThreadInterruptedException) + { + // Stop waiting and return anyway. + } + finally + { + uploadPack = null; + } + } + } + } + + private class ForkLocalPushConnection : BasePackPushConnection + { + private Process receivePack; + + public ForkLocalPushConnection( TransportLocal transport) + : base(transport) + { + receivePack = transport.Spawn(transport.OptionReceivePack); + + Stream rpIn = new BufferedStream(receivePack.StandardInput.BaseStream); + Stream rpOut = new BufferedStream(receivePack.StandardOutput.BaseStream); + + init(rpIn, rpOut); + readAdvertisedRefs(); + } + + public override void Close() + { + base.Close(); + + if (receivePack != null) + { + try + { + receivePack.WaitForExit(); + } + catch (ThreadInterruptedException) + { + // Stop waiting and return anyway. + } + finally + { + receivePack = null; + } + } + } + } + #endregion } } diff --git a/GitSharp.Core/Util/Extensions.cs b/GitSharp.Core/Util/Extensions.cs index a65bee3b..128c1773 100644 --- a/GitSharp.Core/Util/Extensions.cs +++ b/GitSharp.Core/Util/Extensions.cs @@ -213,11 +213,21 @@ public static string DirectoryName(this FileSystemInfo fileSystemInfo) return fileSystemInfo.FullName; } + /// + /// Checks if a directory exists with the FullName of the FileSystemInfo. + /// + /// The FileSystemInfo wich needs to be checked. + /// True if a directry exists with the FullName of the FileSystemInfo, false otherwise public static bool IsDirectory(this FileSystemInfo fileSystemInfo) { return Directory.Exists(fileSystemInfo.FullName); } + /// + /// Checks if a file exists with the FullName of the FileSystemInfo. + /// + /// The FileSystemInfo wich needs to be checked. + /// True if a file exists with the FullName of the FileSystemInfo, false otherwise public static bool IsFile(this FileSystemInfo fileSystemInfo) { return File.Exists(fileSystemInfo.FullName); diff --git a/GitSharp.Core/Util/IListUtil.cs b/GitSharp.Core/Util/IListUtil.cs index 16215ee0..56b40112 100644 --- a/GitSharp.Core/Util/IListUtil.cs +++ b/GitSharp.Core/Util/IListUtil.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; +using System.Diagnostics; namespace GitSharp.Core.Util { + [DebuggerStepThrough] public static class IListUtil { public static bool isEmpty(this ICollection l) diff --git a/GitSharp.Tests/GitSharp.Core/Util/LocalDiskRepositoryTestCase.cs b/GitSharp.Tests/GitSharp.Core/Util/LocalDiskRepositoryTestCase.cs index 393ddaca..e3bed004 100644 --- a/GitSharp.Tests/GitSharp.Core/Util/LocalDiskRepositoryTestCase.cs +++ b/GitSharp.Tests/GitSharp.Core/Util/LocalDiskRepositoryTestCase.cs @@ -56,6 +56,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Threading; using GitSharp.Core; using GitSharp.Core.Util; using GitSharp.Core.Util.JavaHelper; @@ -85,7 +86,7 @@ public abstract class LocalDiskRepositoryTestCase { [SetUp] public virtual void setUp(){ - recursiveDelete(testName() + " (SetUp)", trash, false, true); + recursiveDelete(testName() + " (SetUp)", trash, true); mockSystemReader = new MockSystemReader(); mockSystemReader.userGitConfig = new FileBasedConfig(new FileInfo(Path.Combine(trash.FullName, "usergitconfig"))); @@ -121,14 +122,14 @@ public virtual void tearDown() { if (useMMAP) System.GC.Collect(); - recursiveDelete(testName() + " (TearDown)", trash, false, true); + recursiveDelete(testName() + " (TearDown)", trash, true); } [TestFixtureTearDown] public virtual void FixtureTearDown() { - recursiveDelete(testName() + " (FixtureTearDown)", trash, false, true); + recursiveDelete(testName() + " (FixtureTearDown)", trash, true); } /** Increment the {@link #author} and {@link #committer} times. */ @@ -148,21 +149,30 @@ protected void tick() { * the recursively directory to delete, if present. */ protected void recursiveDelete(FileSystemInfo dir) { - recursiveDelete(testName(), dir, false, true); + recursiveDelete(testName(), dir, true); } - private static bool recursiveDelete(string testName, FileSystemInfo fs, bool silent, bool failOnError) + private static void recursiveDelete(string testName, FileSystemInfo fs, bool failOnError) { - Debug.Assert(!(silent && failOnError)); - if (fs.IsFile()) { - fs.DeleteFile(); - return silent; + if (!fs.DeleteFile()) + { + throw new IOException("Unable to delete file: " + fs.FullName); + } + + // Deleting a file only marks it for deletion, following code blocks until + // the file is actually deleted. For a more thorough explanation see the + // comment below @ the directory.Delete() + while (File.Exists(fs.FullName)) + { + Thread.Sleep(0); + } + return; } var dir = new DirectoryInfo(fs.FullName); - if (!dir.Exists) return silent; + if (!Directory.Exists(dir.FullName)) return; try { @@ -170,17 +180,27 @@ private static bool recursiveDelete(string testName, FileSystemInfo fs, bool sil foreach (FileSystemInfo e in ls) { - silent = recursiveDelete(testName, e, silent, failOnError); + recursiveDelete(testName, e, failOnError); } - + dir.Delete(); + + // dir.Delete() only marks the directory for deletion (see also: http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/a2fcc569-1835-471f-b731-3fe9c6bcd2d9), + // so it creates a race condition between the actual deletion of this directory, and the + // deletion of the parent directory. If this directory is not deleted when the parent directory + // is tried to be deleted, an IOException ("Directory not empty") will be thrown. + // The following code blocks untill the directory is actually deleted. + // (btw, dir.Exists keeps returning true on my (Windows 7) machine, even if the Explorer and Command + // say otherwise) + while (Directory.Exists(dir.FullName)) + { + Thread.Sleep(0); + } } - catch (IOException e) + catch (IOException ex) { - ReportDeleteFailure(testName, failOnError, fs, e.Message); + ReportDeleteFailure(testName, failOnError, fs, ex.ToString()); } - - return silent; } private static void ReportDeleteFailure(string name, bool failOnError, FileSystemInfo fsi, string message) diff --git a/GitSharp.Tests/GitSharp/CloneTests.cs b/GitSharp.Tests/GitSharp/CloneTests.cs index 2c330f84..53115c15 100644 --- a/GitSharp.Tests/GitSharp/CloneTests.cs +++ b/GitSharp.Tests/GitSharp/CloneTests.cs @@ -86,7 +86,6 @@ public void Try_cloning_non_existing_repo_git() } [Test] - [Ignore("TransportLocal is not completely ported yet.")] public void Checked_cloned_local_dotGit_suffixed_repo() { //setup of .git directory @@ -100,10 +99,8 @@ public void Checked_cloned_local_dotGit_suffixed_repo() var repositoryPath = new DirectoryInfo(Path.Combine(tempRepository.FullName, Constants.DOT_GIT)); Directory.Move(repositoryPath.FullName + "ted", repositoryPath.FullName); - using (var repo = new Repository(repositoryPath.FullName)) { - Assert.IsTrue(Repository.IsValid(repo.Directory)); Commit headCommit = repo.Head.CurrentCommit; Assert.AreEqual("f3ca78a01f1baa4eaddcc349c97dcab95a379981", headCommit.Hash); } @@ -119,7 +116,6 @@ public void Checked_cloned_local_dotGit_suffixed_repo() } [Test] - [Ignore] public void Check_cloned_repo_http() { string toPath = Path.Combine(trash.FullName, "test"); @@ -127,6 +123,13 @@ public void Check_cloned_repo_http() using (Repository repo = Git.Clone(fromUrl, toPath)) { + + var status = new RepositoryStatus(repo, new RepositoryStatusOptions { ForceContentCheck = false }); + Assert.IsFalse(status.AnyDifferences); + + status = new RepositoryStatus(repo, new RepositoryStatusOptions { ForceContentCheck = true }); + Assert.IsFalse(status.AnyDifferences); + Assert.IsTrue(Repository.IsValid(repo.Directory)); //Verify content is in the proper location var readme = Path.Combine(repo.WorkingDirectory, "master.txt"); diff --git a/GitSharp/Commands/AbstractCommand.cs b/GitSharp/Commands/AbstractCommand.cs index 2bb33790..55cadb89 100644 --- a/GitSharp/Commands/AbstractCommand.cs +++ b/GitSharp/Commands/AbstractCommand.cs @@ -47,7 +47,7 @@ namespace GitSharp.Commands /// /// Abstract base class of all git commands. It provides basic infrastructure /// - public abstract class AbstractCommand : IGitCommand + public abstract class AbstractCommand : IGitCommand, IDisposable { /// /// Abbreviates a ref-name, used in internal output @@ -212,5 +212,17 @@ public virtual string ActualDirectory /// public abstract void Execute(); + + #region IDisposable Members + + public void Dispose() + { + if (_repository != null) + { + _repository.Dispose(); + } + } + + #endregion } } \ No newline at end of file diff --git a/GitSharp/Commands/CloneCommand.cs b/GitSharp/Commands/CloneCommand.cs index 9bd746d1..57db2c17 100644 --- a/GitSharp/Commands/CloneCommand.cs +++ b/GitSharp/Commands/CloneCommand.cs @@ -281,26 +281,25 @@ private FetchResult runFetch() private static GitSharp.Core.Ref guessHEAD(FetchResult result) { - GitSharp.Core.Ref idHEAD = result.GetAdvertisedRef(Constants.HEAD); - List availableRefs = new List(); - GitSharp.Core.Ref head = null; + // Some transports allow us to see where HEAD points to. If that is not so, + // we'll have to guess. + GitSharp.Core.Ref head = result.GetAdvertisedRef(Constants.HEAD); + if (head != null) + { + return head; + } - foreach (GitSharp.Core.Ref r in result.AdvertisedRefs) - { - string n = r.Name; - if (!n.StartsWith(Constants.R_HEADS)) - continue; - availableRefs.Add(r); - if (idHEAD == null || head != null) - continue; - - if (r.ObjectId.Equals(idHEAD.ObjectId)) - head = r; - } - availableRefs.Sort(RefComparator.INSTANCE); - if (idHEAD != null && head == null) - head = idHEAD; - return head; + var availableHeads = result.AdvertisedRefs.Where(r => r.Name.StartsWith(Constants.R_HEADS)); + + // master is our preferred guess, so if it's advertised, return that. + GitSharp.Core.Ref guessedHead = result.GetAdvertisedRef(Constants.R_HEADS + Constants.MASTER); + if (guessedHead == null && availableHeads.Count() > 0) + { + // if master is not advertised, return any other head. + guessedHead = availableHeads.First(); + } + + return guessedHead; } private void doCheckout(GitSharp.Core.Ref branch) diff --git a/GitSharp/GitSharp.csproj b/GitSharp/GitSharp.csproj index bccf7ecb..3274d2dd 100644 --- a/GitSharp/GitSharp.csproj +++ b/GitSharp/GitSharp.csproj @@ -51,6 +51,9 @@ + + Properties\SharedAssemblyInfo.cs + diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs new file mode 100644 index 00000000..ec834399 --- /dev/null +++ b/SharedAssemblyInfo.cs @@ -0,0 +1,13 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyCompany("The Git Development Community")] +[assembly: AssemblyCopyright("Copyright © 2010 The GitSharp Team")] +[assembly: ComVisible(false)] + +#if DEBUG +[assembly: AssemblyConfiguration("Debug")] +#else +[assembly: AssemblyConfiguration("Release")] +#endif \ No newline at end of file