Skip to content

Commit 2ba6961

Browse files
committed
Make Repository.Commit() able to amend the current tip of the Head
1 parent fb60830 commit 2ba6961

File tree

7 files changed

+158
-36
lines changed

7 files changed

+158
-36
lines changed

LibGit2Sharp.Tests/CommitFixture.cs

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ public void CanCommitALittleBit()
487487

488488
repo.Head[relativeFilepath].ShouldBeNull();
489489

490-
var author = new Signature("Author N. Ame", "him@there.com", DateTimeOffset.Now.AddSeconds(-10));
490+
var author = DummySignature;
491491
Commit commit = repo.Commit("Initial egotistic commit", author, author);
492492

493493
AssertBlobContent(repo.Head[relativeFilepath], "nulltoken\n");
@@ -534,12 +534,9 @@ private static void AssertBlobContent(TreeEntry entry, string expectedContent)
534534
((Blob)(entry.Target)).ContentAsUtf8().ShouldEqual(expectedContent);
535535
}
536536

537-
[Test]
538-
public void CanGeneratePredictableObjectShas()
537+
private static void CommitToANewRepository(string path)
539538
{
540-
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
541-
542-
using (var repo = Repository.Init(scd.DirectoryPath))
539+
using (Repository repo = Repository.Init(path))
543540
{
544541
const string relativeFilepath = "test.txt";
545542
string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath);
@@ -548,16 +545,100 @@ public void CanGeneratePredictableObjectShas()
548545
repo.Index.Stage(relativeFilepath);
549546

550547
var author = new Signature("nulltoken", "emeric.fermas@gmail.com", DateTimeOffset.Parse("Wed, Dec 14 2011 08:29:03 +0100"));
551-
Commit commit = repo.Commit("Initial commit\n", author, author);
548+
repo.Commit("Initial commit\n", author, author);
549+
}
550+
}
552551

553-
commit.Sha.ShouldEqual("1fe3126578fc4eca68c193e4a3a0a14a0704624d");
552+
[Test]
553+
public void CanGeneratePredictableObjectShas()
554+
{
555+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
556+
557+
CommitToANewRepository(scd.DirectoryPath);
554558

559+
using (var repo = new Repository(scd.DirectoryPath))
560+
{
561+
Commit commit = repo.Commits.Single();
562+
commit.Sha.ShouldEqual("1fe3126578fc4eca68c193e4a3a0a14a0704624d");
555563
Tree tree = commit.Tree;
556564
tree.Sha.ShouldEqual("2b297e643c551e76cfa1f93810c50811382f9117");
557565

558566
Blob blob = tree.Files.Single();
559567
blob.Sha.ShouldEqual("9daeafb9864cf43055ae93beb0afd6c7d144bfa4");
560568
}
561569
}
570+
571+
[Test]
572+
public void CanAmendARootCommit()
573+
{
574+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
575+
576+
CommitToANewRepository(scd.DirectoryPath);
577+
578+
using (var repo = new Repository(scd.DirectoryPath))
579+
{
580+
repo.Head.Commits.Count().ShouldEqual(1);
581+
582+
Commit originalCommit = repo.Head.Tip;
583+
originalCommit.Parents.Count().ShouldEqual(0);
584+
585+
CreateAndStageANewFile(repo);
586+
587+
Commit amendedCommit = repo.Commit("I'm rewriting the history!", DummySignature, DummySignature, true);
588+
589+
repo.Head.Commits.Count().ShouldEqual(1);
590+
591+
AssertCommitHasBeenAmended(repo, amendedCommit, originalCommit);
592+
}
593+
}
594+
595+
[Test]
596+
public void CanAmendACommitWithMoreThanOneParent()
597+
{
598+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath);
599+
using (var repo = new Repository(path.RepositoryPath))
600+
{
601+
var mergedCommit = repo.Lookup<Commit>("be3563a");
602+
mergedCommit.ShouldNotBeNull();
603+
mergedCommit.Parents.Count().ShouldEqual(2);
604+
605+
repo.Reset(ResetOptions.Soft, mergedCommit.Sha);
606+
607+
CreateAndStageANewFile(repo);
608+
609+
Commit amendedCommit = repo.Commit("I'm rewriting the history!", DummySignature, DummySignature, true);
610+
611+
AssertCommitHasBeenAmended(repo, amendedCommit, mergedCommit);
612+
}
613+
}
614+
615+
private static void CreateAndStageANewFile(Repository repo)
616+
{
617+
string relativeFilepath = string.Format("new-file-{0}.txt", Guid.NewGuid());
618+
string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath);
619+
620+
File.WriteAllText(filePath, "brand new content\n");
621+
repo.Index.Stage(relativeFilepath);
622+
}
623+
624+
private void AssertCommitHasBeenAmended(Repository repo, Commit amendedCommit, Commit originalCommit)
625+
{
626+
Commit headCommit = repo.Head.Tip;
627+
headCommit.ShouldEqual(amendedCommit);
628+
629+
amendedCommit.Sha.ShouldNotEqual(originalCommit.Sha);
630+
CollectionAssert.AreEqual(originalCommit.Parents, amendedCommit.Parents);
631+
}
632+
633+
[Test]
634+
public void CanNotAmendAnEmptyRepository()
635+
{
636+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
637+
638+
using (Repository repo = Repository.Init(scd.DirectoryPath))
639+
{
640+
Assert.Throws<LibGit2Exception>(() => repo.Commit("I can not amend anything !:(", DummySignature, DummySignature, true));
641+
}
642+
}
562643
}
563644
}

LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ static BaseFixture()
2020
public static string StandardTestRepoPath { get; private set; }
2121
public static DirectoryInfo ResourcesDirectory { get; private set; }
2222

23+
public static readonly Signature DummySignature = new Signature("Author N. Ame", "him@there.com", DateTimeOffset.Now);
24+
2325
private static void SetUpTestEnvironment()
2426
{
2527
var source = new DirectoryInfo(@"../../Resources");

LibGit2Sharp/CommitCollection.cs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -129,55 +129,55 @@ private static bool PointsAtTheHead(string shaOrRefName)
129129
/// <param name = "message">The description of why a change was made to the repository.</param>
130130
/// <param name = "author">The <see cref = "Signature" /> of who made the change.</param>
131131
/// <param name = "committer">The <see cref = "Signature" /> of who added the change to the repository.</param>
132+
/// <param name="amendPreviousCommit">True to amend the current <see cref="Commit"/> pointed at by <see cref="Repository.Head"/>, false otherwise.</param>
132133
/// <returns>The generated <see cref = "Commit" />.</returns>
133-
public Commit Create(string message, Signature author, Signature committer)
134+
public Commit Create(string message, Signature author, Signature committer, bool amendPreviousCommit)
134135
{
135136
Ensure.ArgumentNotNull(message, "message");
136137
Ensure.ArgumentNotNull(author, "author");
137138
Ensure.ArgumentNotNull(committer, "committer");
138139

140+
if (amendPreviousCommit && repo.Info.IsEmpty)
141+
{
142+
throw new LibGit2Exception("Can not amend anything. The Head doesn't point at any commit.");
143+
}
144+
139145
GitOid treeOid;
140146
int res = NativeMethods.git_tree_create_fromindex(out treeOid, repo.Index.Handle);
141-
string encoding = null;
142-
143147
Ensure.Success(res);
144148

145-
Reference head = repo.Refs["HEAD"];
149+
var parentIds = RetrieveParentIdsOfTheCommitBeingCreated(repo, amendPreviousCommit);
146150

147151
GitOid commitOid;
148152
using (var treePtr = new ObjectSafeWrapper(new ObjectId(treeOid), repo))
149-
using (ObjectSafeWrapper headPtr = RetrieveHeadCommitPtr(head))
153+
using (var parentObjectPtrs = new DisposableEnumerable<ObjectSafeWrapper>(parentIds.Select(id => new ObjectSafeWrapper(id, repo))))
150154
using (SignatureSafeHandle authorHandle = author.BuildHandle())
151155
using (SignatureSafeHandle committerHandle = committer.BuildHandle())
152156
{
153-
IntPtr[] parentPtrs = BuildArrayFrom(headPtr);
154-
res = NativeMethods.git_commit_create(out commitOid, repo.Handle, head.CanonicalName, authorHandle,
155-
committerHandle, encoding, message, treePtr.ObjectPtr, parentPtrs.Count(), parentPtrs);
157+
string encoding = null; //TODO: Handle the encoding of the commit to be created
158+
159+
IntPtr[] parentsPtrs = parentObjectPtrs.Select(o => o.ObjectPtr ).ToArray();
160+
res = NativeMethods.git_commit_create(out commitOid, repo.Handle, repo.Refs["HEAD"].CanonicalName, authorHandle,
161+
committerHandle, encoding, message, treePtr.ObjectPtr, parentObjectPtrs.Count(), parentsPtrs);
162+
Ensure.Success(res);
156163
}
157-
Ensure.Success(res);
158164

159165
return repo.Lookup<Commit>(new ObjectId(commitOid));
160166
}
161167

162-
private static IntPtr[] BuildArrayFrom(ObjectSafeWrapper headPtr)
168+
private static IEnumerable<ObjectId> RetrieveParentIdsOfTheCommitBeingCreated(Repository repo, bool amendPreviousCommit)
163169
{
164-
if (headPtr.ObjectPtr == IntPtr.Zero)
170+
if (amendPreviousCommit)
165171
{
166-
return new IntPtr[] { };
172+
return repo.Head.Tip.Parents.Select(c => c.Id);
167173
}
168174

169-
return new[] { headPtr.ObjectPtr };
170-
}
171-
172-
private ObjectSafeWrapper RetrieveHeadCommitPtr(Reference head)
173-
{
174-
DirectReference oidRef = head.ResolveToDirectReference();
175-
if (oidRef == null)
175+
if (repo.Info.IsEmpty)
176176
{
177-
return new ObjectSafeWrapper(null, repo);
177+
return Enumerable.Empty<ObjectId>();
178178
}
179179

180-
return new ObjectSafeWrapper(new ObjectId(oidRef.TargetIdentifier), repo);
180+
return new[] { repo.Head.Tip.Id };
181181
}
182182

183183
private class CommitEnumerator : IEnumerator<Commit>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
5+
namespace LibGit2Sharp.Core
6+
{
7+
internal class DisposableEnumerable<T> : IEnumerable<T>, IDisposable where T : IDisposable
8+
{
9+
private readonly IEnumerable<T> enumerable;
10+
11+
public DisposableEnumerable(IEnumerable<T> enumerable)
12+
{
13+
this.enumerable = enumerable;
14+
}
15+
16+
public IEnumerator<T> GetEnumerator()
17+
{
18+
return enumerable.GetEnumerator();
19+
}
20+
21+
IEnumerator IEnumerable.GetEnumerator()
22+
{
23+
return GetEnumerator();
24+
}
25+
26+
public void Dispose()
27+
{
28+
foreach (var entry in enumerable)
29+
{
30+
entry.Dispose();
31+
}
32+
}
33+
}
34+
}

LibGit2Sharp/IQueryableCommitCollection.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public interface IQueryableCommitCollection : ICommitCollection //TODO: Find a n
1515
/// <param name = "message">The description of why a change was made to the repository.</param>
1616
/// <param name = "author">The <see cref = "Signature" /> of who made the change.</param>
1717
/// <param name = "committer">The <see cref = "Signature" /> of who added the change to the repository.</param>
18+
/// <param name="amendPreviousCommit">True to amend the current <see cref="Commit"/> pointed at by <see cref="Repository.Head"/>, false otherwise.</param>
1819
/// <returns>The generated <see cref = "Commit" />.</returns>
19-
Commit Create(string message, Signature author, Signature committer);
20+
Commit Create(string message, Signature author, Signature committer, bool amendPreviousCommit);
2021
}
2122
}

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
<Compile Include="Configuration.cs" />
5454
<Compile Include="ConfigurationLevel.cs" />
5555
<Compile Include="Core\Compat\Tuple.cs" />
56+
<Compile Include="Core\DisposableEnumerable.cs" />
5657
<Compile Include="Core\EnumExtensions.cs" />
5758
<Compile Include="Core\GitObjectExtensions.cs" />
5859
<Compile Include="Core\ReferenceExtensions.cs" />

LibGit2Sharp/RepositoryExtensions.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,13 @@ public static Branch CreateBranch(this Repository repository, string branchName,
104104
/// </summary>
105105
/// <param name = "repository">The <see cref = "Repository" /> being worked with.</param>
106106
/// <param name = "message">The description of why a change was made to the repository.</param>
107+
/// <param name="amendPreviousCommit">True to amend the current <see cref="Commit"/> pointed at by <see cref="Repository.Head"/>, false otherwise.</param>
107108
/// <returns>The generated <see cref = "LibGit2Sharp.Commit" />.</returns>
108-
public static Commit Commit(this Repository repository, string message)
109+
public static Commit Commit(this Repository repository, string message, bool amendPreviousCommit = false)
109110
{
110111
Signature author = BuildSignatureFromGlobalConfiguration(repository, DateTimeOffset.Now);
111112

112-
return repository.Commit(message, author);
113+
return repository.Commit(message, author, amendPreviousCommit);
113114
}
114115

115116
/// <summary>
@@ -119,12 +120,13 @@ public static Commit Commit(this Repository repository, string message)
119120
/// <param name = "repository">The <see cref = "Repository" /> being worked with.</param>
120121
/// <param name = "author">The <see cref = "Signature" /> of who made the change.</param>
121122
/// <param name = "message">The description of why a change was made to the repository.</param>
123+
/// <param name="amendPreviousCommit">True to amend the current <see cref="Commit"/> pointed at by <see cref="Repository.Head"/>, false otherwise.</param>
122124
/// <returns>The generated <see cref = "LibGit2Sharp.Commit" />.</returns>
123-
public static Commit Commit(this Repository repository, string message, Signature author)
125+
public static Commit Commit(this Repository repository, string message, Signature author, bool amendPreviousCommit = false)
124126
{
125127
Signature committer = BuildSignatureFromGlobalConfiguration(repository, DateTimeOffset.Now);
126128

127-
return repository.Commit(message, author, committer);
129+
return repository.Commit(message, author, committer, amendPreviousCommit);
128130
}
129131

130132
/// <summary>
@@ -134,10 +136,11 @@ public static Commit Commit(this Repository repository, string message, Signatur
134136
/// <param name = "author">The <see cref = "Signature" /> of who made the change.</param>
135137
/// <param name = "committer">The <see cref = "Signature" /> of who added the change to the repository.</param>
136138
/// <param name = "message">The description of why a change was made to the repository.</param>
139+
/// <param name="amendPreviousCommit">True to amend the current <see cref="Commit"/> pointed at by <see cref="Repository.Head"/>, false otherwise.</param>
137140
/// <returns>The generated <see cref = "LibGit2Sharp.Commit" />.</returns>
138-
public static Commit Commit(this Repository repository, string message, Signature author, Signature committer)
141+
public static Commit Commit(this Repository repository, string message, Signature author, Signature committer, bool amendPreviousCommit = false)
139142
{
140-
return repository.Commits.Create(message, author, committer);
143+
return repository.Commits.Create(message, author, committer, amendPreviousCommit);
141144
}
142145

143146
private static Signature BuildSignatureFromGlobalConfiguration(Repository repository, DateTimeOffset now)

0 commit comments

Comments
 (0)