8000 Introduce Repositories.Branches.Update() · mm201/libgit2sharp@188b68b · GitHub
[go: up one dir, main page]

Skip to content

Commit 188b68b

Browse files
jamillnulltoken
authored andcommitted
Introduce Repositories.Branches.Update()
1 parent e808fb6 commit 188b68b

File tree

7 files changed

+289
-0
lines changed

7 files changed

+289
-0
lines changed

LibGit2Sharp.Tests/BranchFixture.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,102 @@ public void CanWalkCommitsFromAnotherBranch()
438438
}
439439
}
440440

441+
[Fact]
442+
public void CanSetUpstreamBranch()
443+
{
444+
const string testBranchName = "branchToSetUpstreamInfoFor";
445+
const string upstreamBranchName = "refs/remotes/origin/master";
446+
447+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath);
448+
449+
using (var repo = new Repository(path.RepositoryPath))
450+
{
451+
Branch branch = repo.CreateBranch(testBranchName);
452+
Assert.False(branch.IsTracking);
453+
454+
Branch upstreamBranch = repo.Branches[upstreamBranchName];
455+
repo.Branches.Update(branch,
456+
b => b.Upstream = upstreamBranch.CanonicalName);
457+
458+
// Verify the immutability of the branch.
459+
Assert.False(branch.IsTracking);
460+
461+
// Get the updated branch information.
462+
branch = repo.Branches[testBranchName];
463+
464+
Remote upstreamRemote = repo.Network.Remotes["origin"];
465+
Assert.NotNull(upstreamRemote);
466+
467+
Assert.True(branch.IsTracking);
468+
Assert.Equal(upstreamBranch, branch.TrackedBranch);
469+
Assert.Equal(upstreamRemote, branch.Remote);
470+
}
471+
}
472+
473+
[Fact]
474+
public void CanSetLocalUpstreamBranch()
475+
{
476+
const string testBranchName = "branchToSetUpstreamInfoFor";
477+
const string upstreamBranchName = "refs/heads/master";
478+
479+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath);
480+
481+
using (var repo = new Repository(path.RepositoryPath))
482+
{
483+
Branch branch = repo.CreateBranch(testBranchName);
484+
Assert.False(branch.IsTracking);
485+
486+
Branch upstreamBranch = repo.Branches[upstreamBranchName];
487+
488+
repo.Branches.Update(branch,
489+
b => b.Upstream = upstreamBranch.CanonicalName);
490+
491+
// Get the updated branch information.
492+
branch = repo.Branches[testBranchName];
493+
494+
// Branches that track the local remote do not have the "Remote" property set.
495+
// Verify (through the configuration entry) that the local remote is set as expected.
496+
Assert.Null(branch.Remote);
497+
ConfigurationEntry<string> remoteConfigEntry = repo.Config.Get<string>("branch", testBranchName, "remote");
498+
Assert.NotNull(remoteConfigEntry);
499+
Assert.Equal(".", remoteConfigEntry.Value);
500+
501+
ConfigurationEntry<string> mergeConfigEntry = repo.Config.Get<string>("branch", testBranchName, "merge");
502+
Assert.NotNull(mergeConfigEntry);
503+
Assert.Equal("refs/heads/master", mergeConfigEntry.Value);
504+
505+
// Verify the IsTracking and TrackedBranch properties.
506+
Assert.True(branch.IsTracking);
507+
Assert.Equal(upstreamBranch, branch.TrackedBranch);
508+
}
509+
}
510+
511+
[Fact]
512+
public void CanUnsetUpstreamBranch()
513+
{
514+
const string testBranchName = "branchToSetUpstreamInfoFor";
515+
const string upstreamBranchName = "refs/remotes/origin/master";
516+
517+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath);
518+
using (var repo = new Repository(path.RepositoryPath))
519+
{
520+
Branch branch = repo.CreateBranch(testBranchName);
521+
Assert.False(branch.IsTracking);
522+
523+
branch = repo.Branches.Update(branch,
524+
b => b.Upstream = upstreamBranchName);
525+
526+
// Got the updated branch from the Update() method
527+
Assert.True(branch.IsTracking);
528+
529+
branch = repo.Branches.Update(branch,
530+
b => b.Upstream = null);
531+
532+
// Verify this is no longer a tracking branch
533+
Assert.False(branch.IsTracking);
534+
}
535+
}
536+
441537
[Fact]
442538
public void CanWalkCommitsFromBranch()
443539
{

LibGit2Sharp/BranchCollection.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,24 @@ public virtual Branch Move(Branch branch, string newName, bool allowOverwrite =
190190
return this[newName];
191191
}
192192

193+
/// <summary>
194+
/// Update properties of a branch.
195+
/// </summary>
196+
/// <param name="branch">The branch to update.</param>
197+
/// <param name="actions">Delegate to perform updates on the branch.</param>
198+
/// <returns>The updated branch.</returns>
199+
public virtual Branch Update(Branch branch, params Action<BranchUpdater>[] actions)
200+
{
201+
var updater = new BranchUpdater(repo, branch);
202+
203+
foreach (Action<BranchUpdater> action in actions)
204+
{
205+
action(updater);
206+
}
207+
208+
return this[branch.Name];
209+
}
210+
193211
private static bool LooksLikeABranchName(string referenceName)
194212
{
195213
return referenceName == "HEAD" ||

LibGit2Sharp/BranchUpdater.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System;
2+
using LibGit2Sharp.Core;
3+
using System.Globalization;
4+
using LibGit2Sharp.Core.Handles;
5+
6+
namespace LibGit2Sharp
7+
{
8+
/// <summary>
9+
/// Exposes properties of a branch that can be updated.
10+
/// </summary>
11+
public class BranchUpdater
12+
{
13+
private readonly Repository repo;
14+
private readonly Branch branch;
15+
16+
/// <summary>
17+
/// Needed for mocking purposes.
18+
/// </summary>
19+
protected BranchUpdater()
20+
{ }
21+
22+
internal BranchUpdater(Repository repo, Branch branch)
23+
{
24+
Ensure.ArgumentNotNull(repo, "repo");
25+
Ensure.ArgumentNotNull(branch, "branch");
26+
27+
this.repo = repo;
28+
this.branch = branch;
29+
}
30+
31+
/// <summary>
32+
/// Sets the upstream information for the branch.
33+
/// <para>
34+
/// Passing null or string.Empty will unset the upstream.
35+
/// </para>
36+
/// </summary>
37+
public virtual string Upstream
38+
{
39+
set
40+
{
41+
if (string.IsNullOrEmpty(value))
42+
{
43+
UnsetUpstream();
44+
return;
45+
}
46+
47+
SetUpstream(value);
48+
}
49+
}
50+
51+
private void UnsetUpstream()
52+
{
53+
repo.Config.Unset(string.Format("branch.{0}.remote", branch.Name));
54+
repo.Config.Unset(string.Format("branch.{0}.merge", branch.Name));
55+
}
56+
57+
/// <summary>
58+
/// Set the upstream information for the current branch.
59+
/// </summary>
60+
/// <param name="upStreamBranchName">The upstream branch to track.</param>
61+
private void SetUpstream(string upStreamBranchName)
62+
{
63+
if (branch.IsRemote)
64+
{
65+
throw new LibGit2SharpException("Cannot set upstream branch on a remote branch.");
66+
}
67+
68+
string remoteName;
69+
string branchName;
70+
71+
GetUpstreamInformation(upStreamBranchName, out remoteName, out branchName);
72+
73+
SetUpstreamTo(remoteName, branchName);
74+
}
75+
76+
private void SetUpstreamTo(string remoteName, string branchName)
77+
{
78+
if (!remoteName.Equals(".", StringComparison.Ordinal))
79+
{
80+
// Verify that remote exists.
81+
repo.Network.Remotes.RemoteForName(remoteName);
82+
}
83+
84+
repo.Config.Set(string.Format("branch.{0}.remote", branch.Name), remoteName);
85+
repo.Config.Set(string.Format("branch.{0}.merge", branch.Name), branchName);
86+
}
87+
88+
/// <summary>
89+
/// Get the upstream remote and merge branch name from a Canonical branch name.
90+
/// This will return the remote name (or ".") if a local branch for the remote name.
91+
/// </summary>
92+
/// <param name="canonicalName">The canonical branch name to parse.</param>
93+
/// <param name="remoteName">The name of the corresponding remote the branch belongs to
94+
/// or "." if it is a local branch.</param>
95+
/// <param name="mergeBranchName">The name of the upstream branch to merge into.</param>
96+
private void GetUpstreamInformation(string canonicalName, out string remoteName, out string mergeBranchName)
97+
{
98+
remoteName = null;
99+
mergeBranchName = null;
100+
101+
const string localPrefix = "refs/heads/";
102+
const string remotePrefix = "refs/remotes/";
103+
104+
if (canonicalName.StartsWith(localPrefix, StringComparison.Ordinal))
105+
{
106+
remoteName = ".";
107+
mergeBranchName = canonicalName;
108+
}
109+
else if (canonicalName.StartsWith(remotePrefix, StringComparison.Ordinal))
110+
{
111+
using (ReferenceSafeHandle branchPtr = repo.Refs.RetrieveReferencePtr(canonicalName))
112+
{
113+
remoteName = Proxy.git_branch_remote_name(repo.Handle, branchPtr);
114+
}
115+
116+
Remote remote = repo.Network.Remotes.RemoteForName(remoteName);
117+
using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repo.Handle, remote.Name, true))
118+
{
119+
GitFetchSpecHandle fetchSpecPtr = Proxy.git_remote_fetchspec(remoteHandle);
120+
mergeBranchName = Proxy.git_fetchspec_rtransform(fetchSpecPtr, canonicalName);
121+
}
122+
}
123+
else
124+
{
125+
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
126+
"'{0}' does not look like a valid canonical branch name.", canonicalName));
127+
}
128+
}
129+
}
130+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace LibGit2Sharp.Core.Handles
4+
{
5+
internal class GitFetchSpecHandle : NotOwnedSafeHandleBase
6+
{
7+
}
8+
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,13 @@ internal static extern int git_reference_symbolic_set_target(
669669
[DllImport(libgit2)]
670670
internal static extern GitReferenceType git_reference_type(ReferenceSafeHandle reference);
671671

672+
[DllImport(libgit2)]
673+
internal static extern int git_refspec_rtransform(
674+
byte[] target,
675+
UIntPtr outlen,
676+
GitFetchSpecHandle refSpec,
677+
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name);
678+
672679
[DllImport(libgit2)]
673680
internal static extern int git_remote_connect(RemoteSafeHandle remote, GitDirection direction);
674681

@@ -706,6 +713,9 @@ internal static extern int git_remote_load(
706713
[DllImport(libgit2)]
707714
internal static extern int git_remote_ls(RemoteSafeHandle remote, git_headlist_cb headlist_cb, IntPtr payload);
708715

716+
[DllImport(libgit2)]
717+
internal static extern GitFetchSpecHandle git_remote_fetchspec(RemoteSafeHandle remote);
718+
709719
[DllImport(libgit2)]
710720
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))]
711721
internal static extern string git_remote_name(RemoteSafeHandle remote);

LibGit2Sharp/Core/Proxy.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,26 @@ public static GitReferenceType git_reference_type(ReferenceSafeHandle reference)
12501250

12511251
#endregion
12521252

1253+
#region git_refspec
1254+
1255+
public static string git_fetchspec_rtransform(GitFetchSpecHandle refSpecPtr, string name)
1256+
{
1257+
using (ThreadAffinity())
1258+
{
1259+
// libgit2 API does not support querying for required buffer size.
1260+
// Use a sufficiently large buffer until it does.
1261+
var buffer = new byte[1024];
1262+
1263+
// TODO: Use code pattern similar to Proxy.git_message_prettify() when available
1264+
int res = NativeMethods.git_refspec_rtransform(buffer, (UIntPtr)buffer.Length, refSpecPtr, name);
1265+
Ensure.ZeroResult(res);
1266+
1267+
return Utf8Marshaler.Utf8FromBuffer(buffer) ?? string.Empty;
1268+
}
1269+
}
1270+
1271+
#endregion
1272+
12531273
#region git_remote_
12541274

12551275
public static RemoteSafeHandle git_remote_create(RepositorySafeHandle repo, string name, string url)
@@ -1281,6 +1301,11 @@ public static void git_remote_disconnect(RemoteSafeHandle remote)
12811301
}
12821302
}
12831303

1304+
public static GitFetchSpecHandle git_remote_fetchspec(RemoteSafeHandle remote)
1305+
{
1306+
return NativeMethods.git_remote_fetchspec(remote);
1307+
}
1308+
12841309
public static void git_remote_download(RemoteSafeHandle remote, TransferProgressHandler onTransferProgress)
12851310
{
12861311
using (ThreadAffinity())

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,13 @@
6262
<Compile Include="Branch.cs" />
6363
<Compile Include="BranchCollection.cs" />
6464
<Compile Include="BranchCollectionExtensions.cs" />
65+
<Compile Include="BranchUpdater.cs" />
6566
<Compile Include="Changes.cs" />
6667
<Compile Include="CheckoutCallbacks.cs" />
6768
<Compile Include="CheckoutOptions.cs" />
6869
<Compile Include="Conflict.cs" />
6970
<Compile Include="ConflictCollection.cs" />
71+
<Compile Include="Core\Handles\GitFetchSpecHandle.cs" />
7072
<Compile Include="Network.cs" />
7173
<Compile Include="Core\GitRemoteHead.cs" />
7274
<Compile Include="OrphanedHeadException.cs" />

0 commit comments

Comments
 (0)
0