Skip to content

Commit dd2e5d7

Browse files
committed
feat: add optional mirror releases capability
1 parent 0e831d8 commit dd2e5d7

File tree

5 files changed

+65
-2
lines changed

5 files changed

+65
-2
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Allowed options are:
8585
| `issues` | Whether to copy issues from the source project to the destination project. |
8686
| `visibility` | The visibility level of the project on the destination GitLab instance. Can be `public`, `internal`, or `private`. |
8787
| `mirror_trigger_builds` | Whether to trigger builds on the destination project when a push is made to the source project. |
88+
| `mirror_releases` | Whether to mirror releases from the source project to the destination project. |
8889

8990
Be aware that the destination path must be unique for each project / group. If you try to synchronize a project / group with the same destination path as an existing project / group, the synchronization will fail.
9091

@@ -98,7 +99,8 @@ Also, the destination namespace must exist on the destination GitLab instance. I
9899
"ci_cd_catalog": true,
99100
"issues": false,
100101
"visibility": "public",
101-
"mirror_trigger_builds": false
102+
"mirror_trigger_builds": false,
103+
"mirror_releases": false
102104
}
103105
},
104106
"groups": {
@@ -107,7 +109,8 @@ Also, the destination namespace must exist on the destination GitLab instance. I
107109
"ci_cd_catalog": true,
108110
"issues": false,
109111
"visibility": "public",
110-
"mirror_trigger_builds": false
112+
"mirror_trigger_builds": false,
113+
"mirror_releases": false
111114
}
112115
}
113116
}

mirroring/get.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func (g *GitlabInstance) fetchProjects(projectFilters *map[string]bool, groupFil
7373
Issues: groupCreationOptions.Issues,
7474
MirrorTriggerBuilds: groupCreationOptions.MirrorTriggerBuilds,
7575
Visibility: groupCreationOptions.Visibility,
76+
MirrorReleases: groupCreationOptions.MirrorReleases,
7677
})
7778
}
7879
break
@@ -151,6 +152,7 @@ func (g *GitlabInstance) fetchGroups(groupFilters *map[string]bool, mirrorMappin
151152
Issues: groupCreationOptions.Issues,
152153
MirrorTriggerBuilds: groupCreationOptions.MirrorTriggerBuilds,
153154
Visibility: groupCreationOptions.Visibility,
155+
MirrorReleases: groupCreationOptions.MirrorReleases,
154156
})
155157
}
156158
break

mirroring/post.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,50 @@ func (g *GitlabInstance) createGroupFromSource(sourceGroup *gitlab.Group, copyOp
197197

198198
return destinationGroup, err
199199
}
200+
201+
func (g *GitlabInstance) mirrorReleases(sourceProject *gitlab.Project, destinationProject *gitlab.Project) error {
202+
utils.LogVerbosef("Starting releases mirroring for project %s", destinationProject.HTTPURLToRepo)
203+
204+
// Fetch existing releases from the destination project
205+
existingReleases, _, err := g.Gitlab.Releases.ListReleases(destinationProject.ID, &gitlab.ListReleasesOptions{})
206+
if err != nil {
207+
return fmt.Errorf("failed to fetch existing releases for destination project %s: %s", destinationProject.PathWithNamespace, err)
208+
}
209+
210+
// Create a map of existing release tags for quick lookup
211+
existingReleaseTags := make(map[string]bool)
212+
for _, release := range existingReleases {
213+
existingReleaseTags[release.TagName] = true
214+
}
215+
216+
// Fetch releases from the source project
217+
sourceReleases, _, err := g.Gitlab.Releases.ListReleases(sourceProject.ID, &gitlab.ListReleasesOptions{})
218+
if err != nil {
219+
return fmt.Errorf("failed to fetch releases for source project %s: %s", sourceProject.PathWithNamespace, err)
220+
}
221+
222+
// Iterate over each source release
223+
for _, release := range sourceReleases {
224+
// Check if the release already exists in the destination project
225+
if existingReleaseTags[release.TagName] {
226+
utils.LogVerbosef("Release %s already exists in destination project %s, skipping.", release.TagName, destinationProject.PathWithNamespace)
227+
continue
228+
}
229+
230+
utils.LogVerbosef("Mirroring release %s to project %s", release.TagName, destinationProject.PathWithNamespace)
231+
232+
// Create the release in the destination project
233+
_, _, err := g.Gitlab.Releases.CreateRelease(destinationProject.ID, &gitlab.CreateReleaseOptions{
234+
Name: gitlab.Ptr(release.Name),
235+
TagName: gitlab.Ptr(release.TagName),
236+
Description: gitlab.Ptr(release.Description),
237+
ReleasedAt: release.ReleasedAt,
238+
})
239+
if err != nil {
240+
utils.LogVerbosef("Failed to create release %s in project %s: %s", release.TagName, destinationProject.PathWithNamespace, err)
241+
}
242+
}
243+
244+
utils.LogVerbosef("Releases mirroring completed for project %s", destinationProject.HTTPURLToRepo)
245+
return nil
246+
}

mirroring/put.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ func (g *GitlabInstance) updateProjectFromSource(sourceGitlab *GitlabInstance, s
104104
}
105105
}()
106106
}
107+
if copyOptions.MirrorReleases {
108+
go func() {
109+
defer wg.Done()
110+
utils.LogVerbosef("copying project %s releases", destinationProject.PathWithNamespace)
111+
err := g.mirrorReleases(sourceProject, destinationProject)
112+
if err != nil {
113+
errorChan <- fmt.Errorf("Failed to copy project %s releases: %s", destinationProject.PathWithNamespace, err)
114+
}
115+
}()
116+
}
107117
wg.Wait()
108118
close(errorChan)
109119
return utils.MergeErrors(errorChan, 4)

utils/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type MirroringOptions struct {
5050
Issues bool `json:"issues"`
5151
MirrorTriggerBuilds bool `json:"mirror_trigger_builds"`
5252
Visibility string `json:"visibility"`
53+
MirrorReleases bool `json:"mirror_releases"`
5354
}
5455

5556
// MirrorMapping defines the mapping of projects and groups

0 commit comments

Comments
 (0)