@@ -27,6 +27,7 @@ import (
2727 . "github.com/onsi/gomega"
2828
2929 "github.com/go-git/go-git/v5"
30+ "github.com/go-git/go-git/v5/config"
3031 "github.com/go-git/go-git/v5/plumbing/object"
3132 "github.com/go-git/go-git/v5/plumbing/transport/http"
3233)
@@ -145,6 +146,114 @@ func GitCommitAndPush(ctx context.Context, input GitCommitAndPushInput) {
145146 }, input .GitPushWait ... ).Should (Succeed (), "Failed to connect to workload cluster using CAPI kubeconfig" )
146147}
147148
149+ // GitSetRemoteInput is the input to GitSetRemote.
150+ type GitSetRemoteInput struct {
151+ // RepoLocation is the directory where the repository is located.
152+ RepoLocation string
153+
154+ // RemoteName is the name of the remote (e.g., "origin").
155+ RemoteName string `envDefault:"origin"`
156+
157+ // RemoteURL is the URL for the remote repository.
158+ RemoteURL string
159+
160+ // Username is the username for authentication (optional).
161+ Username string `env:"GITEA_USER_NAME"`
162+
163+ // Password is the password for authentication (optional).
164+ Password string `env:"GITEA_USER_PWD"`
165+ }
166+
167+ // GitSetRemote sets or updates the remote URL for a git repository.
168+ // If credentials are provided, they will be embedded in the URL.
169+ func GitSetRemote (ctx context.Context , input GitSetRemoteInput ) {
170+ Expect (Parse (& input )).To (Succeed (), "Failed to parse environment variables" )
171+
172+ Expect (ctx ).NotTo (BeNil (), "ctx is required for GitSetRemote" )
173+ Expect (input .RepoLocation ).ToNot (BeEmpty (), "Invalid argument. input.RepoLocation can't be empty when calling GitSetRemote" )
174+ Expect (input .RemoteName ).ToNot (BeEmpty (), "Invalid argument. input.RemoteName can't be empty when calling GitSetRemote" )
175+ Expect (input .RemoteURL ).ToNot (BeEmpty (), "Invalid argument. input.RemoteURL can't be empty when calling GitSetRemote" )
176+
177+ repo , err := git .PlainOpen (input .RepoLocation )
178+ Expect (err ).ShouldNot (HaveOccurred (), "Failed opening the repo at %s" , input .RepoLocation )
179+
180+ // Try to delete the remote if it exists
181+ _ = repo .DeleteRemote (input .RemoteName )
182+
183+ // Construct the remote URL with credentials if provided
184+ remoteURL := input .RemoteURL
185+ if input .Username != "" && input .Password != "" {
186+ // Parse URL to inject credentials
187+ if strings .HasPrefix (remoteURL , "http://" ) {
188+ remoteURL = fmt .Sprintf ("http://%s:%s@%s" , input .Username , input .Password , strings .TrimPrefix (remoteURL , "http://" ))
189+ } else if strings .HasPrefix (remoteURL , "https://" ) {
190+ remoteURL = fmt .Sprintf ("https://%s:%s@%s" , input .Username , input .Password , strings .TrimPrefix (remoteURL , "https://" ))
191+ }
192+ }
193+
194+ // Create the new remote
195+ _ , err = repo .CreateRemote (& config.RemoteConfig {
196+ Name : input .RemoteName ,
197+ URLs : []string {remoteURL },
198+ })
199+ Expect (err ).ShouldNot (HaveOccurred (), "Failed creating remote %s with URL %s" , input .RemoteName , input .RemoteURL )
200+ }
201+
202+ // GitPushInput is the input to GitPush.
203+ type GitPushInput struct {
204+ // RepoLocation is the directory where the repository is located.
205+ RepoLocation string
206+
207+ // RemoteName is the name of the remote to push to.
208+ RemoteName string `envDefault:"origin"`
209+
210+ // RefSpec is the refspec to push (e.g., "refs/heads/main:refs/heads/main").
211+ // If empty, the default git push behavior is used (push current branch to upstream).
212+ RefSpec string
213+
214+ // Username is the username for authentication (optional).
215+ Username string `env:"GITEA_USER_NAME"`
216+
217+ // Password is the password for authentication (optional).
218+ Password string `env:"GITEA_USER_PWD"`
219+
220+ // Force indicates whether to force push.
221+ Force bool
222+ }
223+
224+ // GitPush pushes changes to the remote repository.
225+ func GitPush (ctx context.Context , input GitPushInput ) {
226+ Expect (Parse (& input )).To (Succeed (), "Failed to parse environment variables" )
227+
228+ Expect (ctx ).NotTo (BeNil (), "ctx is required for GitPush" )
229+ Expect (input .RepoLocation ).ToNot (BeEmpty (), "Invalid argument. input.RepoLocation can't be empty when calling GitPush" )
230+ Expect (input .RemoteName ).ToNot (BeEmpty (), "Invalid argument. input.RemoteName can't be empty when calling GitPush" )
231+
232+ repo , err := git .PlainOpen (input .RepoLocation )
233+ Expect (err ).ShouldNot (HaveOccurred (), "Failed opening the repo at %s" , input .RepoLocation )
234+
235+ pushOptions := & git.PushOptions {
236+ RemoteName : input .RemoteName ,
237+ Force : input .Force ,
238+ }
239+
240+ // If a refspec is provided, use it. Otherwise, push will use the default behavior
241+ // (push current branch to upstream)
242+ if input .RefSpec != "" {
243+ pushOptions .RefSpecs = []config.RefSpec {config .RefSpec (input .RefSpec )}
244+ }
245+
246+ if input .Username != "" {
247+ pushOptions .Auth = & http.BasicAuth {
248+ Username : input .Username ,
249+ Password : input .Password ,
250+ }
251+ }
252+
253+ err = repo .Push (pushOptions )
254+ Expect (err ).ShouldNot (HaveOccurred (), "Failed pushing to remote %s" , input .RemoteName )
255+ }
256+
148257// defaultToCurrentGitRepo retrieves the repository URL and the current branch
149258func defaultToCurrentGitRepo (input * FleetCreateGitRepoInput ) {
150259 if input .Repo != "" {
0 commit comments