@@ -5,7 +5,9 @@ use gix_ref::{FullName, FullNameRef};
55use crate :: bstr:: BStr ;
66use crate :: config:: cache:: util:: ApplyLeniencyDefault ;
77use crate :: config:: tree:: { Branch , Push } ;
8- use crate :: repository:: { branch_remote_ref_name, branch_remote_tracking_ref_name} ;
8+ use crate :: repository:: {
9+ branch_remote_ref_name, branch_remote_tracking_ref_name, upstream_branch_and_remote_name_for_tracking_branch,
10+ } ;
911use crate :: { push, remote} ;
1012
1113/// Query configuration related to branches.
@@ -20,19 +22,18 @@ impl crate::Repository {
2022 self . subsection_str_names_of ( "branch" )
2123 }
2224
23- /// Returns the validated reference on the remote associated with the given `name`,
25+ /// Returns the validated reference name of the upstream branch on the remote associated with the given `name`,
2426 /// which will be used when *merging*.
25- /// The returned value corresponds to the `branch.<short_branch_name>.merge` configuration key.
27+ /// The returned value corresponds to the `branch.<short_branch_name>.merge` configuration key for [`remote::Direction::Fetch`].
28+ /// For the [push direction](`remote::Direction::Push`) the Git configuration is used for a variety of different outcomes,
29+ /// similar to what would happen when running `git push <name>`.
2630 ///
27- /// Returns `None` if there is no value at the given key, or if no remote or remote ref is configured.
28- /// May return an error if the reference name to be returned is invalid.
31+ /// Returns `None` if there is nothing configured, or if no remote or remote ref is configured.
2932 ///
3033 /// ### Note
3134 ///
32- /// This name refers to what Git calls upstream branch (as opposed to upstream *tracking* branch).
35+ /// The returned name refers to what Git calls upstream branch (as opposed to upstream *tracking* branch).
3336 /// The value is also fast to retrieve compared to its tracking branch.
34- /// Also note that a [remote::Direction] isn't used here as Git only supports (and requires) configuring
35- /// the remote to fetch from, not the one to push to.
3637 ///
3738 /// See also [`Reference::remote_ref_name()`](crate::Reference::remote_ref_name()).
3839 #[ doc( alias = "branch_upstream_name" , alias = "git2" ) ]
@@ -125,6 +126,73 @@ impl crate::Repository {
125126 . map ( |res| res. map_err ( Into :: into) )
126127 }
127128
129+ /// Given a local `tracking_branch` name, find the remote that maps to it along with the name of the branch on
130+ /// the side of the remote, also called upstream branch.
131+ ///
132+ /// Return `Ok(None)` if there is no remote with fetch-refspecs that would match `tracking_branch` on the right-hand side,
133+ /// or `Err` if the matches were ambiguous.
134+ ///
135+ /// ### Limitations
136+ ///
137+ /// A single valid mapping is required as fine-grained matching isn't implemented yet. This means that
138+ pub fn upstream_branch_and_remote_for_tracking_branch (
139+ & self ,
140+ tracking_branch : & FullNameRef ,
141+ ) -> Result < Option < ( FullName , crate :: Remote < ' _ > ) > , upstream_branch_and_remote_name_for_tracking_branch:: Error > {
142+ use upstream_branch_and_remote_name_for_tracking_branch:: Error ;
143+ if tracking_branch. category ( ) != Some ( gix_ref:: Category :: RemoteBranch ) {
144+ return Err ( Error :: BranchCategory {
145+ full_name : tracking_branch. to_owned ( ) ,
146+ } ) ;
147+ }
148+
149+ let null = self . object_hash ( ) . null ( ) ;
150+ let item_to_search = gix_refspec:: match_group:: Item {
151+ full_ref_name : tracking_branch. as_bstr ( ) ,
152+ target : & null,
153+ object : None ,
154+ } ;
155+ let mut candidates = Vec :: new ( ) ;
156+ let mut ambiguous_remotes = Vec :: new ( ) ;
157+ for remote_name in self . remote_names ( ) {
158+ let remote = self . find_remote ( remote_name. as_ref ( ) ) ?;
159+ let match_group = gix_refspec:: MatchGroup :: from_fetch_specs (
160+ remote
161+ . refspecs ( remote:: Direction :: Fetch )
162+ . iter ( )
163+ . map ( |spec| spec. to_ref ( ) ) ,
164+ ) ;
165+ let out = match_group. match_rhs ( Some ( item_to_search) . into_iter ( ) ) ;
166+ match & out. mappings [ ..] {
167+ [ ] => { }
168+ [ one] => candidates. push ( ( remote. clone ( ) , one. lhs . clone ( ) . into_owned ( ) ) ) ,
169+ [ ..] => ambiguous_remotes. push ( remote) ,
170+ }
171+ }
172+
173+ if candidates. len ( ) == 1 {
174+ let ( remote, candidate) = candidates. pop ( ) . expect ( "just checked for one entry" ) ;
175+ let upstream_branch = match candidate {
176+ gix_refspec:: match_group:: SourceRef :: FullName ( name) => gix_ref:: FullName :: try_from ( name. into_owned ( ) ) ?,
177+ gix_refspec:: match_group:: SourceRef :: ObjectId ( _) => {
178+ unreachable ! ( "Such a reverse mapping isn't ever produced" )
179+ }
180+ } ;
181+ return Ok ( Some ( ( upstream_branch, remote) ) ) ;
182+ }
183+ if ambiguous_remotes. len ( ) + candidates. len ( ) > 1 {
184+ return Err ( Error :: AmbiguousRemotes {
185+ remotes : ambiguous_remotes
186+ . into_iter ( )
187+ . map ( |r| r. name )
188+ . chain ( candidates. into_iter ( ) . map ( |( r, _) | r. name ) )
189+ . flatten ( )
190+ . collect ( ) ,
191+ } ) ;
192+ }
193+ Ok ( None )
194+ }
195+
128196 /// Returns the unvalidated name of the remote associated with the given `short_branch_name`,
129197 /// typically `main` instead of `refs/heads/main`.
130198 /// In some cases, the returned name will be an URL.
0 commit comments