@@ -200,7 +200,7 @@ mod branch {
200200 use crate :: bstr:: BStr ;
201201 use crate :: config:: cache:: util:: ApplyLeniencyDefault ;
202202 use crate :: config:: tree:: { Branch , Push , Section } ;
203- use crate :: repository:: branch_remote_ref_name;
203+ use crate :: repository:: { branch_remote_ref_name, branch_remote_tracking_ref_name } ;
204204 use crate :: { push, remote} ;
205205
206206 /// Query configuration related to branches.
@@ -225,6 +225,9 @@ mod branch {
225225 /// ### Note
226226 ///
227227 /// This name refers to what Git calls upstream branch (as opposed to upstream *tracking* branch).
228+ /// The value is also fast to retrieve compared to its tracking branch.
229+ /// Also note that a [remote::Direction] isn't used here as Git only supports (and requires) configuring
230+ /// the remote to fetch from, not the one to push to.
228231 #[ doc( alias = "branch_upstream_name" , alias = "git2" ) ]
229232 pub fn branch_remote_ref_name (
230233 & self ,
@@ -270,34 +273,50 @@ mod branch {
270273 }
271274 }
272275 } else {
273- let search = gix_refspec:: MatchGroup :: from_push_specs (
274- remote
275- . push_specs
276- . iter ( )
277- . map ( gix_refspec:: RefSpec :: to_ref)
278- . filter ( |spec| spec. destination ( ) . is_some ( ) ) ,
279- ) ;
280- let null_id = self . object_hash ( ) . null ( ) ;
281- let out = search. match_remotes (
282- Some ( gix_refspec:: match_group:: Item {
283- full_ref_name : name. as_bstr ( ) ,
284- target : & null_id,
285- object : None ,
286- } )
287- . into_iter ( ) ,
288- ) ;
289- out. mappings . into_iter ( ) . next ( ) . and_then ( |m| {
290- m. rhs . map ( |name| {
291- FullName :: try_from ( name. into_owned ( ) )
292- . map ( Cow :: Owned )
293- . map_err ( Into :: into)
294- } )
295- } )
276+ matching_remote ( name, remote. push_specs . iter ( ) , self . object_hash ( ) )
277+ . map ( |res| res. map_err ( Into :: into) )
296278 }
297279 }
298280 }
299281 }
300282
283+ /// Return the validated name of the reference that tracks the corresponding reference of `name` on the remote for
284+ /// `direction`. Note that a branch with that name might not actually exist.
285+ ///
286+ /// * with `remote` being [remote::Direction::Fetch], we return the tracking branch that is on the destination
287+ /// side of a `src:dest` refspec. For instance, with `name` being `main` and the default refspec
288+ /// `refs/heads/*:refs/remotes/origin/*`, `refs/heads/main` would match and produce `refs/remotes/origin/main`.
289+ /// * with `remote` being [remote::Direction::Push], we return the tracking branch that corresponds to the remote
290+ /// branch that we would push to. For instance, with `name` being `main` and no setup at all, we
291+ /// would push to `refs/heads/main` on the remote. And that one would be fetched matching the
292+ /// `refs/heads/*:refs/remotes/origin/*` fetch refspec, hence `refs/remotes/origin/main` is returned.
293+ /// Note that `push` refspecs can be used to map `main` to `other` (using a push refspec `refs/heads/main:refs/heads/other`),
294+ /// which would then lead to `refs/remotes/origin/other` to be returned instead.
295+ ///
296+ /// Note that if there is an ambiguity, that is if `name` maps to multiple tracking branches, the first matching mapping
297+ /// is returned, according to the order in which the fetch or push refspecs occour in the configuration file.
298+ #[ doc( alias = "branch_upstream_name" , alias = "git2" ) ]
299+ pub fn branch_remote_tracking_ref_name (
300+ & self ,
301+ name : & FullNameRef ,
302+ direction : remote:: Direction ,
303+ ) -> Option < Result < Cow < ' _ , FullNameRef > , branch_remote_tracking_ref_name:: Error > > {
304+ let remote_ref = match self . branch_remote_ref_name ( name, direction) ? {
305+ Ok ( r) => r,
306+ Err ( err) => return Some ( Err ( err. into ( ) ) ) ,
307+ } ;
308+ let remote = match self . branch_remote ( name. shorten ( ) , direction) ? {
309+ Ok ( r) => r,
310+ Err ( err) => return Some ( Err ( err. into ( ) ) ) ,
311+ } ;
312+
313+ if remote. fetch_specs . is_empty ( ) {
314+ return None ;
315+ }
316+ matching_remote ( remote_ref. as_ref ( ) , remote. fetch_specs . iter ( ) , self . object_hash ( ) )
317+ . map ( |res| res. map_err ( Into :: into) )
318+ }
319+
301320 /// Returns the unvalidated name of the remote associated with the given `short_branch_name`,
302321 /// typically `main` instead of `refs/heads/main`.
303322 /// In some cases, the returned name will be an URL.
@@ -353,6 +372,36 @@ mod branch {
353372 } )
354373 }
355374 }
375+
376+ fn matching_remote < ' a > (
377+ lhs : & FullNameRef ,
378+ specs : impl IntoIterator < Item = & ' a gix_refspec:: RefSpec > ,
379+ object_hash : gix_hash:: Kind ,
380+ ) -> Option < Result < Cow < ' static , FullNameRef > , gix_validate:: reference:: name:: Error > > {
381+ let search = gix_refspec:: MatchGroup {
382+ specs : specs
383+ . into_iter ( )
384+ . map ( gix_refspec:: RefSpec :: to_ref)
385+ . filter ( |spec| spec. source ( ) . is_some ( ) && spec. destination ( ) . is_some ( ) )
386+ . collect ( ) ,
387+ } ;
388+ let null_id = object_hash. null ( ) ;
389+ let out = search. match_remotes (
390+ Some ( gix_refspec:: match_group:: Item {
391+ full_ref_name : lhs. as_bstr ( ) ,
392+ target : & null_id,
393+ object : None ,
394+ } )
395+ . into_iter ( ) ,
396+ ) ;
397+ out. mappings . into_iter ( ) . next ( ) . and_then ( |m| {
398+ m. rhs . map ( |name| {
399+ FullName :: try_from ( name. into_owned ( ) )
400+ . map ( Cow :: Owned )
401+ . map_err ( Into :: into)
402+ } )
403+ } )
404+ }
356405}
357406
358407impl crate :: Repository {
0 commit comments