feat(commit): add ability to amend commit

This commit is contained in:
Aleksey Kulikov 2021-10-21 15:44:04 +03:00
parent 4948bba773
commit 199dce111a
4 changed files with 234 additions and 21 deletions

View file

@ -113,6 +113,56 @@ Pointer<git_oid> create({
}
}
/// Amend an existing commit by replacing only non-null values.
///
/// This creates a new commit that is exactly the same as the old commit, except that
/// any non-null values will be updated. The new commit has the same parents as the old commit.
///
/// The [updateRef] value works as in the regular [create], updating the ref to point to
/// the newly rewritten commit. If you want to amend a commit that is not currently
/// the tip of the branch and then rewrite the following commits to reach a ref, pass
/// this as null and update the rest of the commit chain and ref separately.
///
/// Throws a [LibGit2Error] if error occured.
Pointer<git_oid> amend({
required Pointer<git_repository> repoPointer,
required Pointer<git_commit> commitPointer,
String? updateRef,
required Pointer<git_signature>? authorPointer,
required Pointer<git_signature>? committerPointer,
String? messageEncoding,
required String? message,
required Pointer<git_tree>? treePointer,
}) {
final out = calloc<git_oid>();
final updateRefC = updateRef?.toNativeUtf8().cast<Int8>() ?? nullptr;
final messageEncodingC =
messageEncoding?.toNativeUtf8().cast<Int8>() ?? nullptr;
final messageC = message?.toNativeUtf8().cast<Int8>() ?? nullptr;
final error = libgit2.git_commit_amend(
out,
commitPointer,
updateRefC,
authorPointer ?? nullptr,
committerPointer ?? nullptr,
messageEncodingC,
messageC,
treePointer ?? nullptr,
);
calloc.free(updateRefC);
calloc.free(messageEncodingC);
calloc.free(messageC);
if (error < 0) {
calloc.free(out);
throw LibGit2Error(libgit2.git_error_last());
} else {
return out;
}
}
/// Get the encoding for the message of a commit, as a string representing a standard encoding name.
///
/// The encoding may be NULL if the encoding header in the commit is missing;

View file

@ -58,6 +58,39 @@ class Commit {
));
}
/// Amends an existing commit by replacing only non-null values.
///
/// This creates a new commit that is exactly the same as the old commit, except that
/// any non-null values will be updated. The new commit has the same parents as the old commit.
///
/// The [updateRef] value works as in the regular [create], updating the ref to point to
/// the newly rewritten commit. If you want to amend a commit that is not currently
/// the tip of the branch and then rewrite the following commits to reach a ref, pass
/// this as null and update the rest of the commit chain and ref separately.
///
/// Throws a [LibGit2Error] if error occured.
static Oid amend({
required Repository repo,
required Commit commit,
Signature? author,
Signature? committer,
Tree? tree,
String? updateRef,
String? message,
String? messageEncoding,
}) {
return Oid(bindings.amend(
repoPointer: repo.pointer,
commitPointer: commit.pointer,
authorPointer: author?.pointer,
committerPointer: committer?.pointer,
treePointer: tree?.pointer,
updateRef: updateRef,
message: message,
messageEncoding: messageEncoding,
));
}
/// Returns the encoding for the message of a commit, as a string
/// representing a standard encoding name.
String get messageEncoding => bindings.messageEncoding(_commitPointer);

View file

@ -509,6 +509,59 @@ class Repository {
);
}
/// Amends an existing commit by replacing only non-null values.
///
/// This creates a new commit that is exactly the same as the old commit, except that
/// any non-null values will be updated. The new commit has the same parents as the old commit.
///
/// The [updateRef] value works as in the regular [create], updating the ref to point to
/// the newly rewritten commit. If you want to amend a commit that is not currently
/// the tip of the branch and then rewrite the following commits to reach a ref, pass
/// this as null and update the rest of the commit chain and ref separately.
///
/// Throws a [LibGit2Error] if error occured.
Oid amendCommit({
required Commit commit,
Signature? author,
Signature? committer,
Tree? tree,
String? updateRef,
String? message,
String? messageEncoding,
}) {
return Commit.amend(
repo: this,
commit: commit,
author: author,
committer: committer,
tree: tree,
updateRef: updateRef,
message: message,
messageEncoding: messageEncoding,
);
}
/// Reverts the given commit against the given "our" commit, producing an index that
/// reflects the result of the revert.
///
/// [mainline] is parent of the [revertCommit] if it is a merge (i.e. 1, 2).
///
/// The returned index must be freed explicitly with `free()`.
///
/// Throws a [LibGit2Error] if error occured.
Index revertCommit({
required Commit revertCommit,
required Commit ourCommit,
mainline = 0,
}) {
return Index(commit_bindings.revertCommit(
repoPointer: _repoPointer,
revertCommitPointer: revertCommit.pointer,
ourCommitPointer: ourCommit.pointer,
mainline: mainline,
));
}
/// Finds a single object and intermediate reference (if there is one) by a [spec] revision string.
///
/// See `man gitrevisions`, or https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions
@ -875,27 +928,6 @@ class Repository {
));
}
/// Reverts the given commit against the given "our" commit, producing an index that
/// reflects the result of the revert.
///
/// [mainline] is parent of the [revertCommit] if it is a merge (i.e. 1, 2).
///
/// The returned index must be freed explicitly with `free()`.
///
/// Throws a [LibGit2Error] if error occured.
Index revertCommit({
required Commit revertCommit,
required Commit ourCommit,
mainline = 0,
}) {
return Index(commit_bindings.revertCommit(
repoPointer: _repoPointer,
revertCommitPointer: revertCommit.pointer,
ourCommitPointer: ourCommit.pointer,
mainline: mainline,
));
}
/// Merges two trees, producing an index that reflects the result of the merge.
/// The index may be written as-is to the working directory or checked out. If the index
/// is to be converted to a tree, the caller should resolve any conflicts that arose as part

View file

@ -222,6 +222,104 @@ void main() {
parent.free();
});
test('successfully amends commit with default arguments', () {
final oldHead = repo.head;
final commit = repo.lookupCommit(repo['821ed6e']);
expect(commit.oid, oldHead.target);
final amendedOid = repo.amendCommit(
commit: commit,
message: 'amended commit\n',
updateRef: 'HEAD',
);
final amendedCommit = repo.lookupCommit(amendedOid);
final newHead = repo.head;
expect(amendedCommit.oid, newHead.target);
expect(amendedCommit.message, 'amended commit\n');
expect(amendedCommit.author, commit.author);
expect(amendedCommit.committer, commit.committer);
expect(amendedCommit.tree.oid, commit.tree.oid);
expect(amendedCommit.parents, commit.parents);
amendedCommit.free();
commit.free();
newHead.free();
oldHead.free();
});
test('successfully amends commit with provided arguments', () {
final oldHead = repo.head;
final commit = repo.lookupCommit(repo['821ed6e']);
expect(commit.oid, oldHead.target);
final amendedOid = repo.amendCommit(
commit: commit,
message: 'amended commit\n',
updateRef: 'HEAD',
author: author,
committer: commiter,
tree: tree,
);
final amendedCommit = repo.lookupCommit(amendedOid);
final newHead = repo.head;
expect(amendedCommit.oid, newHead.target);
expect(amendedCommit.message, 'amended commit\n');
expect(amendedCommit.author, author);
expect(amendedCommit.committer, commiter);
expect(amendedCommit.tree.oid, tree.oid);
expect(amendedCommit.parents, commit.parents);
amendedCommit.free();
commit.free();
newHead.free();
oldHead.free();
});
test('successfully amends commit that is not the tip of the branch', () {
final head = repo.head;
final commit = repo.lookupCommit(repo['78b8bf1']);
expect(commit.oid, isNot(head.target));
expect(
() => repo.amendCommit(
commit: commit,
message: 'amended commit\n',
),
returnsNormally,
);
commit.free();
head.free();
});
test(
'throws when trying to amend commit that is not the tip of the branch '
'with HEAD provided as update reference', () {
final head = repo.head;
final commit = repo.lookupCommit(repo['78b8bf1']);
expect(commit.oid, isNot(head.target));
expect(
() => repo.amendCommit(
commit: commit,
message: 'amended commit\n',
updateRef: 'HEAD',
),
throwsA(
isA<LibGit2Error>().having(
(e) => e.toString(),
'error',
"commit to amend is not the tip of the given branch",
),
),
);
commit.free();
head.free();
});
test('returns string representation of Commit object', () {
final commit = repo.lookupCommit(mergeCommit);
expect(commit.toString(), contains('Commit{'));