Пример #1
0
        protected GitId NextGitId()
        {
            var id = new GitId(m_NextCommitId.ToString("X6"));

            m_NextCommitId += 100;
            return(id);
        }
Пример #2
0
        async ValueTask <GitId?> LoadIdFromFile(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentNullException(nameof(fileName));
            }

            if (!GitRepository.TryReadRefFile(fileName, null, out var body))
            {
                return(null);
            }

            if (body.StartsWith("ref:", StringComparison.Ordinal))
            {
                var ob = await Repository.ReferenceRepository.GetAsync(body.Substring(4).Trim()).ConfigureAwait(false);

                return(ob?.Id);
            }
            else if (GitId.TryParse(body, out var oid))
            {
                return(oid);
            }
            else if (GitId.TryParse(body.Trim(), out oid))
            {
                return(oid);
            }
            return(null);
        }
Пример #3
0
        public ChangeLogEntry(
            DateTime date,
            CommitType type,
            string?scope,
            bool isBreakingChange,
            string summary,
            IReadOnlyList <string> body,
            IReadOnlyList <ChangeLogEntryFooter> footers,
            IReadOnlyList <string> breakingChangeDescriptions,
            GitId commit)
        {
            if (footers is null)
            {
                throw new ArgumentNullException(nameof(footers));
            }

            if (breakingChangeDescriptions is null)
            {
                throw new ArgumentNullException(nameof(breakingChangeDescriptions));
            }

            Date  = date;
            Type  = type;
            Scope = scope;
            ContainsBreakingChanges = isBreakingChange || breakingChangeDescriptions.Count > 0;
            Summary = summary ?? throw new ArgumentNullException(nameof(summary));
            Body    = body ?? throw new ArgumentNullException(nameof(body));
            Footers = footers;
            BreakingChangeDescriptions = breakingChangeDescriptions;
            Commit = commit;
        }
Пример #4
0
        protected internal override async ValueTask <GitReference?> ResolveAsync(GitReference gitReference)
        {
            string dir      = gitReference.Name.Contains('/', StringComparison.Ordinal) ? GitDir : WorkTreeDir;
            string fileName = Path.Combine(dir, gitReference.Name);

            if (!File.Exists(fileName))
            {
                return(null);
            }

            if (GitRepository.TryReadRefFile(fileName, null, out var body))
            {
                if (body.StartsWith("ref: ", StringComparison.OrdinalIgnoreCase))
                {
                    body = body.Substring("ref: ".Length);
                    var ob = await Repository.ReferenceRepository.GetAsync(body.Trim()).ConfigureAwait(false);

                    if (ob is not null)
                    {
                        return(ob);
                    }
                }
                else if (GitId.TryParse(body, out var id))
                {
                    return(new GitReference(Repository.ReferenceRepository, gitReference.Name, id));
                }
            }

            return(gitReference); // Not symbolic, and exists. Or error and exists
        }
Пример #5
0
        public override async ValueTask <TGitObject?> GetByIdAsync <TGitObject>(GitId id)
            where TGitObject : class
        {
            var name = id.ToString();

            string path = Path.Combine(_objectsDir, name.Substring(0, 2), name.Substring(2));

            if (!File.Exists(path))
            {
                return(null);
            }

            var fileReader = FileBucket.OpenRead(path, false);

            try
            {
                var rdr = new GitObjectFileBucket(fileReader);

                GitObject ob = await GitObject.FromBucketAsync(Repository, rdr, id).ConfigureAwait(false);

                if (ob is TGitObject tg)
                {
                    return(tg);
                }

                rdr.Dispose();

                return(null);
            }
            catch
            {
                fileReader.Dispose();
                throw;
            }
        }
Пример #6
0
        protected async ValueTask <(bool Success, uint Index)> TryFindIdAsync(GitId id)
        {
            if (FanOut == null)
            {
                return(false, 0);
            }

            uint first      = (id[0] == 0) ? 0 : FanOut[id[0] - 1];
            uint countAfter = FanOut[id[0]];

            if (first == countAfter) // Includes countAfter=0, which overflows the loop
            {
                // No need to check.
                return(false, 0);
            }

            uint c = countAfter;

            while (first + 1 < c)
            {
                uint mid = first + (c - first) / 2;

                var check = await GetGitIdByIndexAsync(mid).ConfigureAwait(false);

                int n = id.CompareTo(check);

                if (n == 0)
                {
                    return(true, mid);
                }
                else if (n < 0)
                {
                    c = mid;
                }
                else
                {
                    first = mid + 1;
                }
            }

            if (first >= countAfter)
            {
                return(false, countAfter);
            }

            var check2 = await GetGitIdByIndexAsync(first).ConfigureAwait(false);

            int n2 = id.CompareTo(check2);

            if (n2 == 0)
            {
                return(true, first);
            }
            else if (n2 > 0)
            {
                first++;
            }

            return(false, first);
        }
Пример #7
0
        public async ValueTask <GitId> ReadTreeIdAsync()
        {
            if (_treeId is not null)
            {
                return(_treeId);
            }

            var(bb, eol) = await Inner.ReadUntilEolFullAsync(AcceptedEols, null, 5 /* "tree " */ + GitId.MaxHashLength * 2 + 2 /* ALL EOL */).ConfigureAwait(false);

            if (bb.IsEof || eol == BucketEol.None || !bb.StartsWithASCII("tree "))
            {
                throw new GitBucketException($"Expected 'tree' record at start of commit in '{Inner.Name}'");
            }

            if (GitId.TryParse(bb.Slice(5, eol), out var id))
            {
                _treeId = id;
            }
            else
            {
                throw new GitBucketException($"Expected valid 'tree' record at start of commit in '{Inner.Name}'");
            }

            return(_treeId);
        }
Пример #8
0
        private protected virtual async ValueTask ReadRefs()
        {
            string fileName = Path.Combine(GitDir, PackedRefsFile);

            if (!File.Exists(fileName))
            {
                return;
            }

            try
            {
                using var sr = FileBucket.OpenRead(fileName, false);

                var idLength = GitId.HashLength(Repository.InternalConfig.IdType) * 2;

                GitRefPeel?last = null;
                while (true)
                {
                    var(bb, eol) = await sr.ReadUntilEolFullAsync(BucketEol.LF).ConfigureAwait(false);

                    if (bb.IsEof)
                    {
                        return;
                    }

                    bb = bb.Trim(eol);
                    ParseLineToPeel(bb, ref last, idLength);
                }
            }
            catch (FileNotFoundException)
            {
                return;
            }
        }
Пример #9
0
        internal override bool ContainsId(GitId id)
        {
            var name = id.ToString();

            string path = Path.Combine(_objectsDir, name.Substring(0, 2), name.Substring(2));

            return(File.Exists(path));
        }
Пример #10
0
 GitCommitInfo EnsureCommit(GitId id)
 {
     if (!Commits.TryGetValue(id, out var v))
     {
         Commits.Add(id, v = new GitCommitInfo(id, Repository));
     }
     return(v);
 }
Пример #11
0
        public async Task WalkHistory(string path)
        {
            //bool small = false;
            if (path.Contains('>'))
            {
                path = GitTestEnvironment.GetRepository((GitTestDir)Enum.Parse(typeof(GitTestDir), path.Substring(1)));
                return;
            }

            using var repo = GitRepository.Open(path);

            if (repo.IsShallow)
            {
                return;
            }

            var r = await repo.GetPlumbing().RevisionList(new GitRevisionListArgs {
                MaxCount = 32, FirstParentOnly = true
            }).ToListAsync();

            Assert.AreEqual(32, r.Count);
            Assert.AreEqual(32, r.Count(x => x != null));
            Assert.AreEqual(32, r.Distinct().Count());

            var revs = repo.Head.Revisions.Take(32).Select(x => x.Commit.Id).ToList();

            if (!r.SequenceEqual(revs))
            {
                int?nDiff = null;
                for (int i = 0; i < Math.Min(revs.Count, r.Count); i++)
                {
                    TestContext.WriteLine($"{i:00} {r[i]} - {revs[i]}");

                    if (!nDiff.HasValue && r[i] != revs[i])
                    {
                        nDiff = i;
                    }
                }
                Assert.Fail($"Different list at {nDiff}");
            }


            if (repo.Commits[GitId.Parse("b71c6c3b64bc002731bc2d6c49080a4855d2c169")] is GitCommit manyParents)
            {
                TestContext.WriteLine($"Found commit {manyParents}, so we triggered the many parent handling");
                manyParents.Revisions.Take(3).ToList();

                Assert.IsTrue(manyParents.Parent !.ParentCount > 3);
            }

            var id = repo.Head.Id !.ToString();

            for (int i = id.Length - 1; i > 7; i--)
            {
                string searchVia = id.Substring(0, i);
                Assert.IsNotNull(await repo.Commits.ResolveIdAsync(searchVia), $"Able to find via {searchVia}, len={i}");
            }
        }
Пример #12
0
        internal override ValueTask <GitObjectBucket?> ResolveById(GitId id)
        {
            var name = id.ToString();

            string path = Path.Combine(_objectsDir, name.Substring(0, 2), name.Substring(2));

            if (!File.Exists(path))
            {
                return(default);
Пример #13
0
        public async ValueTask <(GitId, GitObjectType)> ReadObjectIdAsync()
        {
            if (_objectId is not null)
            {
                return(_objectId, _type);
            }

            var(bb, eol) = await Inner.ReadUntilEolFullAsync(AcceptedEols, null, 7 /* "object " */ + GitId.MaxHashLength * 2 + 2 /* ALL EOL */).ConfigureAwait(false);

            if (bb.IsEof || eol == BucketEol.None || !bb.StartsWithASCII("object "))
            {
                throw new GitBucketException($"Expected 'object' record at start of tag in '{Inner.Name}'");
            }

            if (GitId.TryParse(bb.Slice(7, eol), out var id))
            {
                _objectId = id;
            }
            else
            {
                throw new GitBucketException($"Expected valid 'object' record at start of tag in '{Inner.Name}'");
            }

            (bb, eol) = await Inner.ReadUntilEolFullAsync(AcceptedEols, null, 5 /* "type " */ + 6 /* "commit" */ + 2 /* ALL EOL */).ConfigureAwait(false);

            if (bb.IsEof || eol == BucketEol.None || !bb.StartsWithASCII("type "))
            {
                _objectId = null;
                throw new GitBucketException($"Expected 'type' record of tag in '{Inner.Name}'");
            }

            bb = bb.Slice(5, eol);

            if (bb.EqualsASCII("commit"))
            {
                _type = GitObjectType.Commit;
            }
            else if (bb.EqualsASCII("tree"))
            {
                _type = GitObjectType.Tree;
            }
            else if (bb.EqualsASCII("blob"))
            {
                _type = GitObjectType.Blob;
            }
            else if (bb.EqualsASCII("tag"))
            {
                _type = GitObjectType.Tag;
            }
            else
            {
                throw new GitBucketException($"Expected valid 'type' record in tag in '{Inner.Name}'");
            }

            return(_objectId, _type);
        }
Пример #14
0
        public VersionInfo(NuGetVersion version, GitId commit)
        {
            if (commit.IsNull)
            {
                throw new ArgumentException("Commit must not be empty", nameof(commit));
            }

            Version = version ?? throw new ArgumentNullException(nameof(version));
            Commit  = commit;
        }
Пример #15
0
        private protected override async ValueTask ReadRefs()
        {
            GitRefPeel?last     = null;
            var        idLength = GitId.HashLength(Repository.InternalConfig.IdType) * 2;

            await foreach (var line in Repository.WalkPlumbingCommand("show-ref", new[] { "-d", "--head" },
                                                                      expectedResults: new int[] { 0 /* ok */, 1 /* no references found */ }).ConfigureAwait(false))
            {
                ParseLineToPeel(line.Trim(), ref last, idLength);
            }
        }
Пример #16
0
        private protected void ParseLineToPeel(BucketBytes line, ref GitRefPeel?last, int idLength)
        {
            if (char.IsLetterOrDigit((char)line[0]) && line.Length > idLength + 1)
            {
                if (GitId.TryParse(line.Slice(0, idLength), out var oid))
                {
                    string name = line.Slice(idLength + 1).Trim().ToUTF8String();

                    if (GitReference.ValidName(name, false))
                    {
                        _peelRefs ![name] = last = new GitRefPeel {
Пример #17
0
        protected GitId NextGitId()
        {
            var shaBytes = new byte[20];

            m_CommitIdSource.NextBytes(shaBytes);

            var fullId        = String.Join("", shaBytes.Select(x => x.ToString("x2")));
            var abbreviatedId = fullId.Substring(0, 7);
            var id            = new GitId(fullId, abbreviatedId);

            return(id);
        }
Пример #18
0
        protected async ValueTask <GitId> GetGitIdByIndexAsync(uint i)
        {
            int hashLength = GitId.HashLength(IdType);

            byte[] oidData = new byte[hashLength];

            if (await ReadFromChunkAsync("OIDL", i * hashLength, oidData).ConfigureAwait(false) != hashLength)
            {
                throw new InvalidOperationException();
            }

            return(new GitId(IdType, oidData));
        }
Пример #19
0
        public void Constructor_initializes_properties()
        {
            // ARRANGE
            var version = NuGetVersion.Parse("1.2.3-alpha");
            var commit  = new GitId("123abc");

            // ACT
            var sut = new VersionInfo(version, commit);

            // ASSERT
            Assert.Equal(version, sut.Version);
            Assert.Equal(commit, sut.Commit);
        }
Пример #20
0
        private async ValueTask <byte[]> GetOffsetArrayAsync(uint start, uint count, byte[] oids)
        {
            if (count == 0)
            {
                return(Array.Empty <byte>());
            }

            if (_ver == 2)
            {
                int    sz   = GitId.HashLength(_idType);
                byte[] data = new byte[4 * count];

                long offset = 8 /* header */ + 256 * 4 /* fanout */
                              + sz * _fanOut ![255]    // Hashes
Пример #21
0
        /// <summary>
        /// Initializes a new instance of <see cref="CommitReferenceTextElement"/>
        /// </summary>
        /// <param name="text">The text element's text</param>
        /// <param name="id">The commit's id</param>
        public CommitReferenceTextElement(string text, GitId id)
        {
            if (String.IsNullOrWhiteSpace(text))
            {
                throw new ArgumentException("Value must not be null or whitespace", nameof(text));
            }

            if (id.IsNull)
            {
                throw new ArgumentException("Commit id must not be empty", nameof(id));
            }

            Text     = text;
            CommitId = id;
        }
Пример #22
0
        public async ValueTask <GitId?> ReadFirstParentIdAsync()
        {
            if (_parents is not null)
            {
                return(_parents.First());
            }
            else if (_treeId is null)
            {
                await ReadTreeIdAsync().ConfigureAwait(false);
            }

            // Typically every commit has a parent, so optimize for that case
            var(bb, eol) = await Inner.ReadUntilEolFullAsync(AcceptedEols, requested : ParentLineReadLength).ConfigureAwait(false);

            if (bb.IsEof || eol == BucketEol.None || !bb.StartsWithASCII("parent "))
            {
                if (bb.IsEof || !bb.StartsWithASCII("author "))
                {
                    throw new GitBucketException($"Expected 'parent' or 'author', but got neither in commit {Name} Bucket");
                }

                _parents = Array.Empty <GitId>();

                // We accidentally read the first part of the author line. Let's keep things clean

                if (eol == BucketEol.None)
                {
                    var authorBucket = (bb.Slice("author ".Length).ToArray().AsBucket() + Inner);
                    (bb, eol) = await authorBucket.ReadUntilEolFullAsync(AcceptedEols, requested : MaxHeader).ConfigureAwait(false);
                }

                _author = GitSignatureRecord.TryReadFromBucket(bb.Slice(eol), out var author) ? author : throw new GitBucketException($"Invalid author line in {Name} Bucket");
                return(null);
            }
            else if (GitId.TryParse(bb.Slice("parent ".Length, eol), out var id))
            {
                _parents = new List <GitId>()
                {
                    id
                };
                return(id);
            }
            else
            {
                throw new GitBucketException($"Invalid parent line in '{Inner.Name}");
            }
        }
Пример #23
0
        async ValueTask <bool> GetHasBitmap()
        {
            if (ChunkReader is null)
            {
                return(false);
            }

            byte[] multiPackId = new byte[Repository.InternalConfig.IdType.HashLength()];
            if (multiPackId.Length != (await ChunkReader.ReadAtAsync(AfterChunkPosition, multiPackId).ConfigureAwait(false)))
            {
                return(false);
            }

            GitId id = new GitId(Repository.InternalConfig.IdType, multiPackId);

            _multiPackBitmapPath = Path.Combine(_dir, $"multi-pack-index-{id}.bitmap");
            return(File.Exists(_multiPackBitmapPath));
        }
Пример #24
0
        private async Task <Uri?> TryGetWebUriAsync(IGitLabClient gitlabClient, GitId commitId)
        {
            if (m_ProjectInfo == null)
            {
                throw new InvalidOperationException();
            }

            m_Logger.LogDebug($"Getting web uri for commit '{commitId}'");

            try
            {
                var commit = await gitlabClient.Commits.GetAsync(m_ProjectInfo.ProjectPath, commitId.Id);

                return(new Uri(commit.WebUrl));
            }
            catch (Exception ex) when(ex is GitLabException gitlabException && gitlabException.HttpStatusCode == HttpStatusCode.NotFound)
            {
                return(null);
            }
        }
Пример #25
0
        private async Task <Uri?> TryGetWebUriAsync(IGitHubClient githubClient, GitId commitId)
        {
            if (m_ProjectInfo == null)
            {
                throw new InvalidOperationException();
            }

            m_Logger.LogDebug($"Getting web uri for commit '{commitId}'");

            try
            {
                var commit = await githubClient.Repository.Commit.Get(m_ProjectInfo.Owner, m_ProjectInfo.Repository, commitId.Id);

                return(new Uri(commit.HtmlUrl));
            }
            catch (Exception ex) when(ex is ApiValidationException || ex is NotFoundException)
            {
                return(null);
            }
        }
Пример #26
0
        protected override async ValueTask InitAsync()
        {
            await base.InitAsync().ConfigureAwait(false);

            _haveV2 = GetChunkLength("GDA2").HasValue;

            if (_baseCommitGraphs > 0)
            {
                byte[] baseGraphs = new byte[_baseCommitGraphs * IdType.HashLength()];

                if (baseGraphs.Length != await ReadFromChunkAsync("BASE", 0, baseGraphs).ConfigureAwait(false))
                {
                    throw new GitException($"Can't read commit-graph base chunks from {ChunkReader} Bucket");
                }

                GitId parentId = GitId.FromByteArrayOffset(IdType, baseGraphs, (_baseCommitGraphs - 1) * IdType.HashLength());

                ParentGraph = _graphRoot !.GetCommitGraph(parentId);
            }
        }
Пример #27
0
        private async ValueTask <byte[]> GetOidArrayAsync(uint start, uint count)
        {
            if (count == 0)
            {
                return(Array.Empty <byte>());
            }

            if (_ver == 2)
            {
                int    sz   = GitId.HashLength(_idType);
                byte[] data = new byte[sz * count];

                long offset = 8 /* header */ + 256 * 4 /* fanout */ + sz * start;

                if (data.Length != await _fIdx !.ReadAtAsync(offset, data).ConfigureAwait(false))
                {
                    return(Array.Empty <byte>());
                }

                return(data);
            }
            else if (_ver == 1)
            {
                int    sz   = GitId.HashLength(_idType) + 4;
                byte[] data = new byte[sz * count];

                long offset = 256 * 4 /* fanout */ + sz * start;

                if (data.Length != await _fIdx !.ReadAtAsync(offset, data).ConfigureAwait(false))
                {
                    return(Array.Empty <byte>());
                }

                return(data);
            }
            else
            {
                return(Array.Empty <byte>());
            }
        }
Пример #28
0
        private async ValueTask <GitId> GetOidAsync(int index)
        {
            int offset = OidOffset;

            if (index < offset)
            {
                return(await ParentGraph !.GetOidAsync(index).ConfigureAwait(false));
            }

            index -= offset;

            int hashLength = GitId.HashLength(IdType);

            byte[] oidData = new byte[hashLength];

            if (hashLength != await ReadFromChunkAsync("OIDL", index * hashLength, oidData).ConfigureAwait(false))
            {
                throw new InvalidOperationException();
            }

            return(new GitId(IdType, oidData));
        }
Пример #29
0
 public VersionInfo(NuGetVersion version, GitId commit)
 {
     Version = version ?? throw new ArgumentNullException(nameof(version));
     Commit  = commit;
 }
Пример #30
0
 public GitCommitInfo(GitId from, GitRepository repo)
 {
     Id       = from;
     _commit  = repo;
     _parents = new GitAsyncLazy <IEnumerable <GitId> >(GetParentIds);
 }
Пример #31
0
        public void UseGitClient()
        {
            GitCommitArgs ga = new GitCommitArgs();
            ga.Author.Name = "Tester";
            ga.Author.EmailAddress = "*****@*****.**";
            ga.Signature.Name = "Other";
            ga.Signature.EmailAddress = "*****@*****.**";

            // Use stable time and offset to always produce the same hash
            DateTime ct = new DateTime(2002, 01, 01, 0, 0, 0, DateTimeKind.Utc);
            ga.Author.When = ct;
            ga.Author.TimeOffsetInMinutes = 120;
            ga.Signature.When = ct;
            ga.Signature.TimeOffsetInMinutes = 120;

            string repoDir = GetTempPath();
            string repo2Dir = GetTempPath();
            GitId firstResult;
            GitId lastCommit;
            using (GitRepository repo = GitRepository.Create(repoDir))
            using (GitClient git = new GitClient())
            {
                string ignoreFile = Path.Combine(repoDir, ".gitignore");
                string file = Path.Combine(repoDir, "newfile");
                string subDir = Path.Combine(repoDir, "dir");
                string fileInSubDir = Path.Combine(subDir, "file2");
                string file3 = Path.Combine(repoDir, "other");
                string file4 = Path.Combine(repoDir, "q.ignore");
                File.WriteAllText(file, "Some body");
                Directory.CreateDirectory(subDir);
                File.WriteAllText(fileInSubDir, "Some other body");
                File.WriteAllText(file3, "file3");

                File.WriteAllText(ignoreFile, "*.ignore\n");
                File.WriteAllText(file4, "file4");

                git.Add(ignoreFile);
                git.Add(file);
                git.Commit(repoDir, ga, out firstResult);

                git.Add(fileInSubDir);

                int ticked = 0;

                File.AppendAllText(file, "\nExtra Line");

                GitStatusArgs gsa = new GitStatusArgs();
                gsa.IncludeIgnored = true;
                gsa.IncludeUnmodified = true;

                Assert.That(git.Status(repoDir, gsa,
                    delegate(object sender, GitStatusEventArgs e)
                    {
                        switch (e.RelativePath)
                        {
                            case "newfile":
                                //Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.Added));
                                Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.Normal), "newfile index normal");
                                Assert.That(e.WorkingDirectoryStatus, Is.EqualTo(GitStatus.Normal), "newfile wc modified");
                                Assert.That(e.WorkingDirectoryModified);
                                Assert.That(e.Ignored, Is.False);
                                break;
                            case "dir/file2":
                                Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.New), "file2 index added");
                                Assert.That(e.WorkingDirectoryStatus, Is.EqualTo(GitStatus.Normal), "file2 wc normal");
                                Assert.That(e.Ignored, Is.False);
                                break;
                            case "other":
                                Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.None));
                                Assert.That(e.WorkingDirectoryStatus, Is.EqualTo(GitStatus.New));
                                Assert.That(e.Ignored, Is.False);
                                break;
                            case ".gitignore":
                                Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.Normal));
                                Assert.That(e.WorkingDirectoryStatus, Is.EqualTo(GitStatus.Normal));
                                Assert.That(e.Ignored, Is.False);
                                break;
                            case "q.ignore":
                                // TODO: Make this ignored
                                Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.None));
                                Assert.That(e.WorkingDirectoryStatus, Is.EqualTo(GitStatus.Normal));
                                Assert.That(e.Ignored, Is.True);
                                break;
                            default:
                                Assert.Fail("Invalid node found: {0}", e.RelativePath);
                                break;
                        }

                        Assert.That(e.FullPath, Is.EqualTo(Path.GetFullPath(Path.Combine(repoDir, e.RelativePath))));
                        ticked++;
                    }), Is.True);

                Assert.That(ticked, Is.EqualTo(5), "Ticked");

                ga.LogMessage = "Intermediate";
                git.Commit(repoDir, ga);

                Assert.That(git.Delete(fileInSubDir));
                Assert.That(git.Add(file));

                GitId commit;

                ga.LogMessage = "A log message to remember";

                // The passed path is currently just used to find the local repository
                lastCommit = new GitId("996cf198b49ed6fce3bcba232e2d88eb473560f9");

                Assert.That(git.Commit(repoDir, ga, out commit));
                Assert.That(commit, Is.EqualTo(lastCommit));

                File.Move(file, file + ".a");

                ticked = 0;
                gsa.IncludeIgnored = false;
                gsa.IncludeUnversioned = true;
                gsa.IncludeUnmodified = false;
                Assert.That(git.Status(repoDir, gsa,
                    delegate(object sender, GitStatusEventArgs e)
                    {
                        switch (e.RelativePath)
                        {
                            /*case "dir":
                                Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.Normal), "dir index normal");
                                Assert.That(e.WorkingDirectoryStatus, Is.EqualTo(GitStatus.New), "dir wc normal");
                                break;*/
                            case "newfile":
                                Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.None), "newfile index normal");
                                Assert.That(e.WorkingDirectoryStatus, Is.EqualTo(GitStatus.Deleted), "newfile wc deleted");
                                break;
                            case "newfile.a":
                                Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.None), "newfile.a index normal");
                                Assert.That(e.WorkingDirectoryStatus, Is.EqualTo(GitStatus.New), "newfile.a wc new");
                                break;
                            case "other":
                                Assert.That(e.IndexStatus, Is.EqualTo(GitStatus.None), "other index normal");
                                Assert.That(e.WorkingDirectoryStatus, Is.EqualTo(GitStatus.New), "other wc normal");
                                break;
                            default:
                                Assert.Fail("Invalid node found: {0}", e.RelativePath);
                                break;
                        }

                        Assert.That(e.FullPath, Is.EqualTo(Path.GetFullPath(Path.Combine(repoDir, e.RelativePath))));
                        ticked++;
                    }), Is.True);

                Assert.That(ticked, Is.EqualTo(3));

                GitCloneArgs gc = new GitCloneArgs();
                gc.Synchronous = true;

                git.Clone(repoDir, repo2Dir, gc);

                GitCommit theCommit;
                Assert.That(repo.Lookup(commit, out theCommit));
                Assert.That(repo.Branches.Create(theCommit, "vNext"));
                Assert.That(repo.Branches, Is.Not.Empty);
            }

            using (GitRepository repo1 = new GitRepository(repoDir))
            using (GitRepository repo2 = new GitRepository(repo2Dir))
            {
                GitReference head = repo1.HeadReference;
                Assert.That(head, Is.Not.Null, "Has head");

                Assert.That(head.Name, Is.EqualTo("refs/heads/master"));
                //Assert.That(repo2.Head, Is.Not.Null);

                GitId headId;
                Assert.That(repo1.ResolveReference(repo1.HeadReference, out headId));
                Assert.That(headId, Is.EqualTo(lastCommit));
                GitCommit commit;

                Assert.That(repo1.Lookup(headId, out commit));
                Assert.That(commit, Is.Not.Null, "Have a commit");

                Assert.That(commit.Id, Is.EqualTo(lastCommit));
                Assert.That(commit.Ancestors, Is.Not.Empty);
                Assert.That(commit.Ancestor, Is.Not.Null);
                Assert.That(commit.Ancestor.Ancestor, Is.Not.Null);
                Assert.That(commit.Ancestor.Ancestor.Ancestor, Is.Null);
                Assert.That(commit.Ancestor.Ancestor.Id, Is.EqualTo(firstResult));

                Assert.That(commit.Author, Is.Not.Null);
                Assert.That(commit.Author.Name, Is.EqualTo("Tester"));
                Assert.That(commit.Author.EmailAddress, Is.EqualTo("*****@*****.**"));

                Assert.That(commit.Committer, Is.Not.Null);
                Assert.That(commit.Committer.Name, Is.EqualTo("Other"));
                Assert.That(commit.Committer.EmailAddress, Is.EqualTo("*****@*****.**"));

                Assert.That(commit.Committer.TimeOffsetInMinutes, Is.EqualTo(120), "Time offset"); // CEST dependent
                Assert.That(commit.Committer.When, Is.EqualTo(ct), "Exact time");
                Assert.That(commit.LogMessage, Is.EqualTo("A log message to remember\n"));

                Assert.That(commit.Parents, Is.Not.Empty);
                Assert.That(commit.ParentIds, Is.Not.Empty);

                Assert.That(commit.Tree, Is.Not.Empty);
                Assert.That(commit.Tree.Count, Is.EqualTo(2));
                Assert.That(commit.Ancestor.Tree.Count, Is.EqualTo(3));
                Assert.That(commit.Ancestor.Ancestor.Tree.Count, Is.EqualTo(2));
                Assert.That(commit.Tree.Id, Is.Not.EqualTo(commit.Ancestor.Tree.Id));

                GitId id;
                Assert.That(repo1.LookupViaPrefix(commit.Id.ToString(), out id));
                Assert.That(id, Is.EqualTo(commit.Id));

                Assert.That(repo1.LookupViaPrefix(commit.Id.ToString().Substring(0, 10), out id));
                Assert.That(id, Is.EqualTo(commit.Id));

                Assert.That(commit.Peel<GitObject>().Id, Is.EqualTo(commit.Tree.Id));
                Assert.That(commit.Peel<GitTree>(), Is.EqualTo(commit.Tree)); // Compares members
                Assert.That(commit.Tree.Peel<GitObject>(), Is.Null);

                GitTagArgs ta = new GitTagArgs();
                ta.Signature.When = ct;
                ta.Signature.Name = "Me";
                ta.Signature.EmailAddress = "[email protected]";
                ta.LogMessage = "Some message";
                ga.Author.TimeOffsetInMinutes = 120;
                Assert.That(commit.Tag("MyTag", ta, out id));
                Assert.That(id, Is.EqualTo(new GitId("db31f8333fc64d7e7921ea91f6e007b755dcfcbb")));

                GitTag tag;
                Assert.That(repo1.Lookup(id, out tag));
                Assert.That(tag, Is.Not.Null);
                Assert.That(tag.Name, Is.EqualTo("MyTag"));
                Assert.That(tag.LogMessage, Is.EqualTo("Some message\n"));
                Assert.That(tag.Tagger.Name, Is.EqualTo("Me"));

                Assert.That(tag.Target.Id, Is.EqualTo(commit.Id));
                Assert.That(tag.Peel<GitTree>(), Is.EqualTo(commit.Peel<GitTree>()));

                repo1.CheckOut(commit.Tree);

                //Console.WriteLine("1:");
                //foreach (GitTreeEntry e in commit.Tree)
                //{
                //    Console.WriteLine(string.Format("{0}: {1} ({2})", e.Name, e.Kind, e.Children.Count));
                //}

                //Console.WriteLine("2:");
                //foreach (GitTreeEntry e in commit.Ancestor.Tree)
                //{
                //    Console.WriteLine(string.Format("{0}: {1} ({2})", e.Name, e.Kind, e.Children.Count));
                //}

                //Console.WriteLine("3:");
                //foreach (GitTreeEntry e in commit.Ancestor.Ancestor.Tree)
                //{
                //    Console.WriteLine(string.Format("{0}: {1} ({2})", e.Name, e.Kind, e.Children.Count));
                //}
                //Console.WriteLine("-");
            }
        }