@@ -13,6 +13,14 @@ namespace GitHub.Services
1313 [ PartCreationPolicy ( CreationPolicy . NonShared ) ]
1414 public class GitService : IGitService
1515 {
16+ readonly IRepositoryFacade repositoryFacade ;
17+
18+ [ ImportingConstructor ]
19+ public GitService ( IRepositoryFacade repositoryFacade )
20+ {
21+ this . repositoryFacade = repositoryFacade ;
22+ }
23+
1624 /// <summary>
1725 /// Returns the URL of the remote for the specified <see cref="repository"/>. If the repository
1826 /// is null or no remote named origin exists, this method returns null
@@ -56,8 +64,8 @@ public UriString GetUri(string path, string remote = "origin")
5664 /// <returns>An instance of <see cref="IRepositoryModel"/> or null</returns>
5765 public IRepository GetRepository ( string path )
5866 {
59- var repoPath = Repository . Discover ( path ) ;
60- return repoPath == null ? null : new Repository ( repoPath ) ;
67+ var repoPath = repositoryFacade . Discover ( path ) ;
68+ return repoPath == null ? null : repositoryFacade . NewRepository ( repoPath ) ;
6169 }
6270
6371 /// <summary>
@@ -80,42 +88,70 @@ public UriString GetRemoteUri(IRepository repo, string remote = "origin")
8088 /// <remarks>
8189 /// This is equivalent to creating it via MEF with <see cref="CreationPolicy.NonShared"/>
8290 /// </remarks>
83- public static IGitService GitServiceHelper => new GitService ( ) ;
91+ public static IGitService GitServiceHelper => new GitService ( new RepositoryFacade ( ) ) ;
8492
8593 /// <summary>
8694 /// Finds the latest pushed commit of a file and returns the sha of that commit. Returns null when no commits have
8795 /// been found in any remote branches or the current local branch.
8896 /// </summary>
8997 /// <param name="path">The local path of a repository or a file inside a repository. This cannot be null.</param>
98+ /// <param name="remote">The remote name to look for</param>
9099 /// <returns></returns>
91- public Task < string > GetLatestPushedSha ( string path )
100+ public Task < string > GetLatestPushedSha ( string path , string remote = "origin" )
92101 {
93102 Guard . ArgumentNotNull ( path , nameof ( path ) ) ;
94103
95- return Task . Factory . StartNew ( ( ) =>
104+ return Task . Run ( ( ) =>
96105 {
97106 using ( var repo = GetRepository ( path ) )
98107 {
99108 if ( repo != null )
100109 {
101- if ( repo . Head . IsTracking && repo . Head . Tip . Sha == repo . Head . TrackedBranch . Tip . Sha )
110+ // This is the common case where HEAD is tracking a remote branch
111+ var commonAncestor = repo . Head . TrackingDetails . CommonAncestor ;
112+ if ( commonAncestor != null )
102113 {
103- return repo . Head . Tip . Sha ;
114+ return commonAncestor . Sha ;
104115 }
105116
106- var remoteHeads = repo . Refs . Where ( r => r . IsRemoteTrackingBranch ) . ToList ( ) ;
107- foreach ( var c in repo . Commits )
117+ // This is the common case where a branch was forked from a local branch.
118+ // Use CommonAncestor because we don't want to search for a commit that only exists
119+ // locally or that has been added to the remote tracking branch since the fork.
120+ var commonAncestorShas = repo . Branches
121+ . Where ( b => b . IsTracking )
122+ . Select ( b => b . TrackingDetails . CommonAncestor ? . Sha )
123+ . Where ( s => s != null )
124+ . ToArray ( ) ;
125+
126+ var sortByTopological = new CommitFilter { SortBy = CommitSortStrategies . Topological } ;
127+ foreach ( var commit in repo . Commits . QueryBy ( sortByTopological ) )
108128 {
109- if ( repo . Refs . ReachableFrom ( remoteHeads , new [ ] { c } ) . Any ( ) )
129+ if ( commonAncestorShas . Contains ( commit . Sha ) )
110130 {
111- return c . Sha ;
131+ return commit . Sha ;
112132 }
113133 }
134+
135+ // This is a less common case where a branch was forked from a branch
136+ // which has since had new commits added to it.
137+ var nearestCommonAncestor = repo . Branches
138+ . Where ( b => b . IsRemote )
139+ . Select ( b => b . Tip )
140+ . Distinct ( )
141+ . Select ( c => repo . ObjectDatabase . CalculateHistoryDivergence ( c , repo . Head . Tip ) )
142+ . Where ( hd => hd . AheadBy != null )
143+ . OrderBy ( hd => hd . BehindBy )
144+ . Select ( hd => hd . CommonAncestor )
145+ . FirstOrDefault ( ) ;
146+ if ( nearestCommonAncestor != null )
147+ {
148+ return nearestCommonAncestor . Sha ;
149+ }
114150 }
115151
116152 return null ;
117153 }
118154 } ) ;
119155 }
120156 }
121- }
157+ }
0 commit comments