@@ -125,6 +125,7 @@ func NewUseRepo(f *build.File, proxies []string) (*build.File, *build.CallExpr)
125125// useRepos must not be empty.
126126// Keyword arguments are preserved and can be added using the syntax "key=value".
127127// For invalid identifiers (e.g., containing dots), dict unpacking syntax is used.
128+ // If a mapping conflicts with an existing one (same key or same value), the existing one is replaced.
128129func AddRepoUsages (useRepos []* build.CallExpr , repos ... string ) {
129130 if len (repos ) == 0 {
130131 return
@@ -133,43 +134,153 @@ func AddRepoUsages(useRepos []*build.CallExpr, repos ...string) {
133134 panic ("useRepos must not be empty" )
134135 }
135136
136- seen := make (map [string ]struct {})
137+ // Parse all repos to add and track which keys/values are being added
138+ type repoToAdd struct {
139+ key string
140+ value string
141+ isMapping bool
142+ isValidIdent bool
143+ isDictUnpacking bool
144+ }
145+
146+ var reposToAdd []repoToAdd
147+ mappingValuesToAdd := make (map [string ]struct {}) // Values being added as mappings
148+ positionalValuesToAdd := make (map [string ]struct {}) // Values being added as positional
149+ keysToAdd := make (map [string ]struct {})
150+
151+ for _ , repo := range repos {
152+ key , value , isMapping := parseRepoMapping (repo )
153+ validIdent := isValidIdentifier (key )
154+
155+ reposToAdd = append (reposToAdd , repoToAdd {
156+ key : key ,
157+ value : value ,
158+ isMapping : isMapping ,
159+ isValidIdent : validIdent ,
160+ isDictUnpacking : isMapping && ! validIdent ,
161+ })
162+
163+ if isMapping {
164+ mappingValuesToAdd [value ] = struct {}{}
165+ keysToAdd [key ] = struct {}{}
166+ } else {
167+ positionalValuesToAdd [value ] = struct {}{}
168+ }
169+ }
170+
171+ // Track existing values to detect duplicates when adding positional args
172+ existingValues := make (map [string ]struct {})
137173 for _ , useRepo := range useRepos {
138174 if len (useRepo .List ) == 0 {
139- // Invalid use_repo call, skip.
140175 continue
141176 }
142177 for _ , arg := range useRepo .List [1 :] {
143- seen [repoFromUseRepoArg (arg )] = struct {}{}
178+ if val := repoFromUseRepoArg (arg ); val != "" {
179+ existingValues [val ] = struct {}{}
180+ }
144181 }
145182 }
146183
147- lastUseRepo := getLastUseRepo (useRepos )
148- var dictEntries []* build.KeyValueExpr
184+ // Remove conflicting arguments from all use_repo calls
185+ for _ , useRepo := range useRepos {
186+ if len (useRepo .List ) == 0 {
187+ continue
188+ }
149189
150- for _ , repo := range repos {
151- key , value , isMapping := parseRepoMapping ( repo )
190+ var newArgs []build. Expr
191+ var dictToUpdate * build. DictExpr
152192
153- // Check if already exists
154- if _ , ok := seen [value ]; ok {
155- continue
193+ for _ , arg := range useRepo .List [1 :] {
194+ shouldKeep := true
195+
196+ switch arg := arg .(type ) {
197+ case * build.StringExpr :
198+ // Positional argument: only remove if a mapping is being added with this value
199+ if _ , conflicts := mappingValuesToAdd [arg .Value ]; conflicts {
200+ shouldKeep = false
201+ }
202+ case * build.AssignExpr :
203+ // Keyword argument: remove if same key is being added
204+ if ident , ok := arg .LHS .(* build.Ident ); ok {
205+ if _ , conflicts := keysToAdd [ident .Name ]; conflicts {
206+ shouldKeep = false
207+ }
208+ }
209+ // Or if same value is being added as a mapping (replacement)
210+ if str , ok := arg .RHS .(* build.StringExpr ); ok {
211+ if _ , conflicts := mappingValuesToAdd [str .Value ]; conflicts {
212+ shouldKeep = false
213+ }
214+ }
215+ case * build.UnaryExpr :
216+ // Dict unpacking: we'll handle this separately
217+ if arg .Op == "**" {
218+ if dict , ok := arg .X .(* build.DictExpr ); ok {
219+ dictToUpdate = dict
220+ shouldKeep = false // Remove from list, we'll re-add after filtering
221+ }
222+ }
223+ }
224+
225+ if shouldKeep {
226+ newArgs = append (newArgs , arg )
227+ }
156228 }
157229
158- if ! isMapping {
230+ // Filter dict entries if we have a dict to update
231+ if dictToUpdate != nil {
232+ var filteredDictEntries []* build.KeyValueExpr
233+ for _ , kv := range dictToUpdate .List {
234+ shouldKeep := true
235+ // Check if key conflicts
236+ if keyStr , ok := kv .Key .(* build.StringExpr ); ok {
237+ if _ , conflicts := keysToAdd [keyStr .Value ]; conflicts {
238+ shouldKeep = false
239+ }
240+ }
241+ // Check if value conflicts with a mapping being added
242+ if valStr , ok := kv .Value .(* build.StringExpr ); ok {
243+ if _ , conflicts := mappingValuesToAdd [valStr .Value ]; conflicts {
244+ shouldKeep = false
245+ }
246+ }
247+ if shouldKeep {
248+ filteredDictEntries = append (filteredDictEntries , kv )
249+ }
250+ }
251+ // Only keep dict if it still has entries after filtering
252+ if len (filteredDictEntries ) > 0 {
253+ dictToUpdate .List = filteredDictEntries
254+ newArgs = append (newArgs , & build.UnaryExpr {Op : "**" , X : dictToUpdate })
255+ }
256+ }
257+
258+ useRepo .List = append (useRepo .List [:1 ], newArgs ... )
259+ }
260+
261+ // Add new repos to the last use_repo call
262+ lastUseRepo := getLastUseRepo (useRepos )
263+ var dictEntries []* build.KeyValueExpr
264+
265+ for _ , repo := range reposToAdd {
266+ if ! repo .isMapping {
159267 // Simple positional argument: use_repo(ext, "foo")
160- lastUseRepo .List = append (lastUseRepo .List , & build.StringExpr {Value : repo })
161- } else if isValidIdentifier (key ) {
268+ // Skip if already exists (to preserve existing mappings)
269+ if _ , exists := existingValues [repo .value ]; ! exists {
270+ lastUseRepo .List = append (lastUseRepo .List , & build.StringExpr {Value : repo .value })
271+ }
272+ } else if repo .isValidIdent {
162273 // Valid identifier as keyword argument: use_repo(ext, bar = "baz")
163274 lastUseRepo .List = append (lastUseRepo .List , & build.AssignExpr {
164- LHS : & build.Ident {Name : key },
275+ LHS : & build.Ident {Name : repo . key },
165276 Op : "=" ,
166- RHS : & build.StringExpr {Value : value },
277+ RHS : & build.StringExpr {Value : repo . value },
167278 })
168279 } else {
169280 // Invalid identifier, need dict unpacking: use_repo(ext, **{"foo.2": "foo"})
170281 dictEntries = append (dictEntries , & build.KeyValueExpr {
171- Key : & build.StringExpr {Value : key },
172- Value : & build.StringExpr {Value : value },
282+ Key : & build.StringExpr {Value : repo . key },
283+ Value : & build.StringExpr {Value : repo . value },
173284 })
174285 }
175286 }
0 commit comments