diff --git a/lib/src/bindings/branch.dart b/lib/src/bindings/branch.dart index 4faf088..bebaf08 100644 --- a/lib/src/bindings/branch.dart +++ b/lib/src/bindings/branch.dart @@ -202,5 +202,159 @@ String name(Pointer ref) { } } +/// Find the remote name of a remote-tracking branch. +/// +/// This will return the name of the remote whose fetch refspec is matching the +/// given branch. E.g. given a branch "refs/remotes/test/master", it will extract +/// the "test" part. +/// +/// Throws a [LibGit2Error] if refspecs from multiple remotes match or if error +/// occured. +String remoteName({ + required Pointer repoPointer, + required String branchName, +}) { + final out = calloc(); + final branchNameC = branchName.toNativeUtf8().cast(); + final error = libgit2.git_branch_remote_name(out, repoPointer, branchNameC); + + calloc.free(branchNameC); + + if (error < 0) { + calloc.free(out); + throw LibGit2Error(libgit2.git_error_last()); + } else { + final result = out.ref.ptr.cast().toDartString(); + calloc.free(out); + return result; + } +} + +/// Get the upstream of a branch. +/// +/// Given a reference, this will return a new reference object corresponding to +/// its remote tracking branch. The reference must be a local branch. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer getUpstream(Pointer branch) { + final out = calloc>(); + final error = libgit2.git_branch_upstream(out, branch); + + if (error < 0) { + calloc.free(out); + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + +/// Set a branch's upstream branch. +/// +/// This will update the configuration to set the branch named [branchName] as +/// the upstream of branch. Pass a null name to unset the upstream information. +/// +/// **Note**: The actual tracking reference must have been already created for +/// the operation to succeed. +/// +/// Throws a [LibGit2Error] if error occured. +void setUpstream({ + required Pointer branchPointer, + required String? branchName, +}) { + final branchNameC = branchName?.toNativeUtf8().cast() ?? nullptr; + final error = libgit2.git_branch_set_upstream(branchPointer, branchNameC); + + calloc.free(branchNameC); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } +} + +/// Get the upstream name of a branch. +/// +/// Given a local branch, this will return its remote-tracking branch +/// information, as a full reference name, ie. "feature/nice" would become +/// "refs/remotes/origin/feature/nice", depending on that branch's configuration. +/// +/// Throws a [LibGit2Error] if error occured. +String upstreamName({ + required Pointer repoPointer, + required String branchName, +}) { + final out = calloc(); + final branchNameC = branchName.toNativeUtf8().cast(); + final error = libgit2.git_branch_upstream_name(out, repoPointer, branchNameC); + + calloc.free(branchNameC); + + if (error < 0) { + calloc.free(out); + throw LibGit2Error(libgit2.git_error_last()); + } else { + final result = out.ref.ptr.cast().toDartString(); + calloc.free(out); + return result; + } +} + +/// Retrieve the upstream remote of a local branch. +/// +/// This will return the currently configured "branch.*.remote" for a given +/// branch. This branch must be local. +/// +/// Throws a [LibGit2Error] if error occured. +String upstreamRemote({ + required Pointer repoPointer, + required String branchName, +}) { + final out = calloc(); + final branchNameC = branchName.toNativeUtf8().cast(); + final error = libgit2.git_branch_upstream_remote( + out, + repoPointer, + branchNameC, + ); + + calloc.free(branchNameC); + + if (error < 0) { + calloc.free(out); + throw LibGit2Error(libgit2.git_error_last()); + } else { + final result = out.ref.ptr.cast().toDartString(); + calloc.free(out); + return result; + } +} + +/// Retrieve the upstream merge of a local branch. +/// +/// This will return the currently configured "branch.*.merge" for a given +/// branch. This branch must be local. +/// +/// Throws a [LibGit2Error] if error occured. +String upstreamMerge({ + required Pointer repoPointer, + required String branchName, +}) { + final out = calloc(); + final branchNameC = branchName.toNativeUtf8().cast(); + final error = libgit2.git_branch_upstream_merge( + out, + repoPointer, + branchNameC, + ); + + if (error < 0) { + calloc.free(out); + throw LibGit2Error(libgit2.git_error_last()); + } else { + final result = out.ref.ptr.cast().toDartString(); + calloc.free(out); + return result; + } +} + /// Free the given reference to release memory. void free(Pointer ref) => reference_bindings.free(ref); diff --git a/lib/src/branch.dart b/lib/src/branch.dart index 446ec4f..d97b183 100644 --- a/lib/src/branch.dart +++ b/lib/src/branch.dart @@ -147,6 +147,79 @@ class Branch { /// Throws a [LibGit2Error] if error occured. String get name => bindings.name(_branchPointer); + /// Remote name of a remote-tracking branch. + /// + /// This will return the name of the remote whose fetch refspec is matching + /// the given branch. E.g. given a branch "refs/remotes/test/master", it + /// will extract the "test" part. + /// + /// Throws a [LibGit2Error] if refspecs from multiple remotes match or if + /// error occured. + String get remoteName { + final owner = reference_bindings.owner(_branchPointer); + final branchName = reference_bindings.name(_branchPointer); + return bindings.remoteName(repoPointer: owner, branchName: branchName); + } + + /// Upstream [Reference] of a local branch. + /// + /// **IMPORTANT**: Should be freed to release allocated memory. + /// + /// Throws a [LibGit2Error] if error occured. + Reference get upstream => Reference(bindings.getUpstream(_branchPointer)); + + /// Sets a branch's upstream branch. + /// + /// This will update the configuration to set the branch named [branchName] as + /// the upstream of branch. Pass a null name to unset the upstream + /// information. + /// + /// **Note**: The actual tracking reference must have been already created for + /// the operation to succeed. + /// + /// Throws a [LibGit2Error] if error occured. + void setUpstream(String? branchName) => bindings.setUpstream( + branchPointer: _branchPointer, + branchName: branchName, + ); + + /// Upstream name of a branch. + /// + /// Given a local branch, this will return its remote-tracking branch + /// information, as a full reference name, ie. "feature/nice" would become + /// "refs/remotes/origin/feature/nice", depending on that branch's configuration. + /// + /// Throws a [LibGit2Error] if error occured. + String get upstreamName { + final owner = reference_bindings.owner(_branchPointer); + final branchName = reference_bindings.name(_branchPointer); + return bindings.upstreamName(repoPointer: owner, branchName: branchName); + } + + /// Upstream remote of a local branch. + /// + /// This will return the currently configured "branch.*.remote" for a branch. + /// Branch must be local. + /// + /// Throws a [LibGit2Error] if error occured. + String get upstreamRemote { + final owner = reference_bindings.owner(_branchPointer); + final branchName = reference_bindings.name(_branchPointer); + return bindings.upstreamRemote(repoPointer: owner, branchName: branchName); + } + + /// Upstream merge of a local branch. + /// + /// This will return the currently configured "branch.*.merge" for a branch. + /// Branch must be local. + /// + /// Throws a [LibGit2Error] if error occured. + String get upstreamMerge { + final owner = reference_bindings.owner(_branchPointer); + final branchName = reference_bindings.name(_branchPointer); + return bindings.upstreamMerge(repoPointer: owner, branchName: branchName); + } + /// Releases memory allocated for branch object. void free() => bindings.free(_branchPointer); diff --git a/test/assets/testrepo/.gitdir/config b/test/assets/testrepo/.gitdir/config index 51f9253..4ee1ae8 100644 --- a/test/assets/testrepo/.gitdir/config +++ b/test/assets/testrepo/.gitdir/config @@ -3,7 +3,10 @@ filemode = true bare = false logallrefupdates = true - editor = vim + editor = vim [remote "origin"] url = git://github.com/SkinnyMind/libgit2dart.git fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/test/assets/testrepo/.gitdir/refs/remotes/origin/master b/test/assets/testrepo/.gitdir/refs/remotes/origin/master new file mode 100644 index 0000000..fb9321c --- /dev/null +++ b/test/assets/testrepo/.gitdir/refs/remotes/origin/master @@ -0,0 +1 @@ +821ed6e80627b8769d170a293862f9fc60825226 diff --git a/test/branch_test.dart b/test/branch_test.dart index 0d318f7..e5e8304 100644 --- a/test/branch_test.dart +++ b/test/branch_test.dart @@ -26,7 +26,7 @@ void main() { group('Branch', () { test('returns a list of all branches', () { - const branchesExpected = ['feature', 'master']; + const branchesExpected = ['feature', 'master', 'origin/master']; final branches = repo.branches; for (var i = 0; i < branches.length; i++) { @@ -46,7 +46,13 @@ void main() { }); test('returns a list of remote branches', () { - expect(repo.branchesRemote, []); + const branchesExpected = ['origin/master']; + final branches = repo.branchesRemote; + + for (var i = 0; i < branches.length; i++) { + expect(branches[i].name, branchesExpected[i]); + branches[i].free(); + } }); test('throws when trying to return list and error occurs', () { @@ -121,6 +127,113 @@ void main() { expect(() => nullBranch.name, throwsA(isA())); }); + test('returns remote name of a remote-tracking branch', () { + final branch = repo.branchesRemote.first; + expect(branch.remoteName, 'origin'); + branch.free(); + }); + + test( + 'throws when getting remote name of a remote-tracking branch and ' + 'error occurs', () { + final branch = repo.lookupBranch(name: 'master'); + expect(() => branch.remoteName, throwsA(isA())); + }); + + test('returns upstream of a local branch', () { + final branch = repo.lookupBranch(name: 'master'); + final upstream = branch.upstream; + + expect(upstream.isRemote, true); + expect(upstream.name, 'refs/remotes/origin/master'); + + upstream.free(); + branch.free(); + }); + + test('throws when trying to get upstream of a remote branch', () { + final branch = repo.branchesRemote.first; + expect(() => branch.upstream, throwsA(isA())); + branch.free(); + }); + + test('successfully sets upstream of a branch', () { + final branch = repo.lookupBranch(name: 'master'); + var upstream = branch.upstream; + expect(upstream.name, 'refs/remotes/origin/master'); + + final ref = repo.createReference( + name: 'refs/remotes/origin/new', + target: 'refs/heads/master', + ); + branch.setUpstream(ref.shorthand); + + upstream = branch.upstream; + expect(upstream.name, 'refs/remotes/origin/new'); + + ref.free(); + upstream.free(); + branch.free(); + }); + + test('successfully unsets upstream of a branch', () { + final branch = repo.lookupBranch(name: 'master'); + final upstream = branch.upstream; + expect(upstream.name, 'refs/remotes/origin/master'); + + branch.setUpstream(null); + expect(() => branch.upstream, throwsA(isA())); + + upstream.free(); + branch.free(); + }); + + test('throws when trying to set upstream of a branch and error occurs', () { + final branch = repo.lookupBranch(name: 'master'); + expect( + () => branch.setUpstream('some/upstream'), + throwsA(isA()), + ); + branch.free(); + }); + + test('returns upstream name of a local branch', () { + final branch = repo.lookupBranch(name: 'master'); + expect(branch.upstreamName, 'refs/remotes/origin/master'); + branch.free(); + }); + + test('throws when trying to get upstream name of a branch and error occurs', + () { + final branch = repo.lookupBranch(name: 'feature'); + expect(() => branch.upstreamName, throwsA(isA())); + branch.free(); + }); + + test('returns upstream remote of a local branch', () { + final branch = repo.lookupBranch(name: 'master'); + expect(branch.upstreamRemote, 'origin'); + branch.free(); + }); + + test('throws when trying to get upstream remote of a remote branch', () { + final branch = repo.branchesRemote.first; + expect(() => branch.upstreamName, throwsA(isA())); + branch.free(); + }); + + test('returns upstream merge of a local branch', () { + final branch = repo.lookupBranch(name: 'master'); + expect(branch.upstreamMerge, 'refs/heads/master'); + branch.free(); + }); + + test('throws when trying to get upstream merge of a remote branch', () { + final branch = repo.branchesRemote.first; + expect(() => branch.upstreamMerge, throwsA(isA())); + branch.free(); + }); + group('create()', () { test('successfully creates', () { final commit = repo.lookupCommit(lastCommit); @@ -128,7 +241,7 @@ void main() { final branch = repo.createBranch(name: 'testing', target: commit); final branches = repo.branches; - expect(repo.branches.length, 3); + expect(repo.branches.length, 4); expect(branch.target, lastCommit); for (final branch in branches) { @@ -194,7 +307,7 @@ void main() { final branch = repo.lookupBranch(name: 'renamed'); final branches = repo.branches; - expect(branches.length, 2); + expect(branches.length, 3); expect( () => repo.lookupBranch(name: 'feature'), throwsA(isA()), diff --git a/test/libgit2_test.dart b/test/libgit2_test.dart index 5ac089a..6896649 100644 --- a/test/libgit2_test.dart +++ b/test/libgit2_test.dart @@ -1,5 +1,4 @@ import 'package:libgit2dart/libgit2dart.dart'; -import 'package:libgit2dart/src/libgit2.dart'; import 'package:libgit2dart/src/util.dart'; import 'package:test/test.dart'; diff --git a/test/packbuilder_test.dart b/test/packbuilder_test.dart index bef3ae7..b48f33c 100644 --- a/test/packbuilder_test.dart +++ b/test/packbuilder_test.dart @@ -123,7 +123,7 @@ void main() { test('successfully packs with provided packDelegate', () { Directory('${repo.workdir}.git/objects/pack/').createSync(); void packDelegate(PackBuilder packBuilder) { - final branches = repo.branches; + final branches = repo.branchesLocal; for (final branch in branches) { final ref = repo.lookupReference('refs/heads/${branch.name}'); for (final commit in repo.log(oid: ref.target)) { diff --git a/test/reference_test.dart b/test/reference_test.dart index c9729c5..9d714fb 100644 --- a/test/reference_test.dart +++ b/test/reference_test.dart @@ -30,6 +30,7 @@ void main() { 'refs/heads/feature', 'refs/heads/master', 'refs/notes/commits', + 'refs/remotes/origin/master', 'refs/tags/v0.1', 'refs/tags/v0.2', ], @@ -80,14 +81,14 @@ void main() { test('returns the short name', () { final ref = repo.createReference( - name: 'refs/remotes/origin/master', + name: 'refs/remotes/origin/upstream', target: repo[lastCommit], ); final head = repo.head; expect(head.shorthand, 'master'); - expect(ref.shorthand, 'origin/master'); + expect(ref.shorthand, 'origin/upstream'); head.free(); ref.free(); @@ -106,13 +107,8 @@ void main() { }); test('checks if reference is a remote branch', () { - final ref = repo.createReference( - name: 'refs/remotes/origin/master', - target: repo[lastCommit], - ); - + final ref = repo.lookupReference('refs/remotes/origin/master'); expect(ref.isRemote, true); - ref.free(); }); @@ -431,7 +427,7 @@ void main() { expect(ref1.target.sha, '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8'); expect(ref2.target.sha, 'f0fdbf506397e9f58c59b88dfdd72778ec06cc0c'); - expect(repo.references.length, 5); + expect(repo.references.length, 6); repo.renameReference( oldName: 'refs/tags/v0.1', @@ -445,7 +441,7 @@ void main() { '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8', ); expect(repo.references, isNot(contains('refs/tags/v0.1'))); - expect(repo.references.length, 4); + expect(repo.references.length, 5); ref1.free(); ref2.free();