@@ -5,7 +5,9 @@ use gix_ref::{FullName, FullNameRef};
5
5
use crate :: bstr:: BStr ;
6
6
use crate :: config:: cache:: util:: ApplyLeniencyDefault ;
7
7
use 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
+ } ;
9
11
use crate :: { push, remote} ;
10
12
11
13
/// Query configuration related to branches.
@@ -20,19 +22,18 @@ impl crate::Repository {
20
22
self . subsection_str_names_of ( "branch" )
21
23
}
22
24
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`,
24
26
/// 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>`.
26
30
///
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.
29
32
///
30
33
/// ### Note
31
34
///
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).
33
36
/// 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.
36
37
///
37
38
/// See also [`Reference::remote_ref_name()`](crate::Reference::remote_ref_name()).
38
39
#[ doc( alias = "branch_upstream_name" , alias = "git2" ) ]
@@ -125,6 +126,73 @@ impl crate::Repository {
125
126
. map ( |res| res. map_err ( Into :: into) )
126
127
}
127
128
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
+
128
196
/// Returns the unvalidated name of the remote associated with the given `short_branch_name`,
129
197
/// typically `main` instead of `refs/heads/main`.
130
198
/// In some cases, the returned name will be an URL.
0 commit comments