Skip to content

Commit 1486c72

Browse files
author
Gusted
committed
Merge branch 'main' into Enable-partial-clone
2 parents 348e952 + 1514e13 commit 1486c72

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+1709
-573
lines changed

custom/conf/app.example.ini

+3
Original file line numberDiff line numberDiff line change
@@ -1496,6 +1496,9 @@ PATH =
14961496
;;
14971497
;; Timeout for Sendmail
14981498
;SENDMAIL_TIMEOUT = 5m
1499+
;;
1500+
;; convert \r\n to \n for Sendmail
1501+
;SENDMAIL_CONVERT_CRLF = true
14991502

15001503
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
15011504
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+1
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type
667667
command or full path).
668668
- `SENDMAIL_ARGS`: **_empty_**: Specify any extra sendmail arguments.
669669
- `SENDMAIL_TIMEOUT`: **5m**: default timeout for sending email through sendmail
670+
- `SENDMAIL_CONVERT_CRLF`: **true**: Most versions of sendmail prefer LF line endings rather than CRLF line endings. Set this to false if your version of sendmail requires CRLF line endings.
670671
- `SEND_BUFFER_LEN`: **100**: Buffer length of mailing queue. **DEPRECATED** use `LENGTH` in `[queue.mailer]`
671672

672673
## Cache (`cache`)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
date: "2021-12-13:10:10+08:00"
3+
title: "Permissions"
4+
slug: "permissions"
5+
weight: 14
6+
toc: false
7+
draft: false
8+
menu:
9+
sidebar:
10+
parent: "usage"
11+
name: "Permissions"
12+
weight: 14
13+
identifier: "permissions"
14+
---
15+
16+
# Permissions
17+
18+
**Table of Contents**
19+
20+
{{< toc >}}
21+
22+
Gitea supports permissions for repository so that you can give different access for different people. At first, we need to know about `Unit`.
23+
24+
## Unit
25+
26+
In Gitea, we call a sub module of a repository `Unit`. Now we have following units.
27+
28+
| Name | Description | Permissions |
29+
| --------------- | ---------------------------------------------------- | ----------- |
30+
| Code | Access source code, files, commits and branches. | Read Write |
31+
| Issues | Organize bug reports, tasks and milestones. | Read Write |
32+
| PullRequests | Enable pull requests and code reviews. | Read Write |
33+
| Releases | Track project versions and downloads. | Read Write |
34+
| Wiki | Write and share documentation with collaborators. | Read Write |
35+
| ExternalWiki | Link to an external wiki | Read |
36+
| ExternalTracker | Link to an external issue tracker | Read |
37+
| Projects | The URL to the template repository | Read Write |
38+
| Settings | Manage the repository | Admin |
39+
40+
With different permissions, people could do different things with these units.
41+
42+
| Name | Read | Write | Admin |
43+
| --------------- | ------------------------------------------------- | ---------------------------- | ------------------------- |
44+
| Code | View code trees, files, commits, branches and etc. | Push codes. | - |
45+
| Issues | View issues and create new issues. | Add labels, assign, close | - |
46+
| PullRequests | View pull requests and create new pull requests. | Add labels, assign, close | - |
47+
| Releases | View releases and download files. | Create/Edit releases | - |
48+
| Wiki | View wiki pages. Clone the wiki repository. | Create/Edit wiki pages, push | - |
49+
| ExternalWiki | Link to an external wiki | - | - |
50+
| ExternalTracker | Link to an external issue tracker | - | - |
51+
| Projects | View the boards | Change issues across boards | - |
52+
| Settings | - | - | Manage the repository |
53+
54+
And there are some differences for permissions between individual repositories and organization repositories.
55+
56+
## Individual Repository
57+
58+
For individual repositories, the creators are the only owners of repositories and have no limit to change anything of this
59+
repository or delete it. Repositories owners could add collaborators to help maintain the repositories. Collaborators could have `Read`, `Write` and `Admin` permissions.
60+
61+
## Organization Repository
62+
63+
Different from individual repositories, the owner of organization repositories are the owner team of this organization.
64+
65+
### Team
66+
67+
A team in an organization has unit permissions settings. It can have members and repositories scope. A team could access all the repositories in this organization or special repositories changed by the owner team. A team could also be allowed to create new
68+
repositories.
69+
70+
The owner team will be created when the organization created and the creator will become the first member of the owner team.
71+
Notice Gitea will not allow a people is a member of organization but not in any team. The owner team could not be deleted and only
72+
members of owner team could create a new team. Admin team could be created to manage some of repositories, members of admin team
73+
could do anything with these repositories. Generate team could be created by the owner team to do the permissions allowed operations.

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.16
55
require (
66
cloud.google.com/go v0.78.0 // indirect
77
code.gitea.io/gitea-vet v0.2.1
8-
code.gitea.io/sdk/gitea v0.14.0
8+
code.gitea.io/sdk/gitea v0.15.1
99
gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be
1010
gitea.com/go-chi/cache v0.0.0-20211013020926-78790b11abf1
1111
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
3838
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
3939
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
4040
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
41-
code.gitea.io/sdk/gitea v0.14.0 h1:m4J352I3p9+bmJUfS+g0odeQzBY/5OXP91Gv6D4fnJ0=
42-
code.gitea.io/sdk/gitea v0.14.0/go.mod h1:89WiyOX1KEcvjP66sRHdu0RafojGo60bT9UqW17VbWs=
41+
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
42+
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
4343
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
4444
gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be h1:IzSwPVzd2hE6e67ujY8ReBCrQ5IFNd0uiBmC7Ux5IaY=
4545
gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be/go.mod h1:/vR0YjlusOYvosKYW7QKcSnrY0nPLe4RQ/DGi3+i/Do=

integrations/api_repo_teams_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import (
1010
"testing"
1111

1212
repo_model "code.gitea.io/gitea/models/repo"
13+
"code.gitea.io/gitea/models/unit"
1314
"code.gitea.io/gitea/models/unittest"
1415
user_model "code.gitea.io/gitea/models/user"
1516
api "code.gitea.io/gitea/modules/structs"
17+
"code.gitea.io/gitea/modules/util"
1618

1719
"github.com/stretchr/testify/assert"
1820
)
@@ -36,7 +38,7 @@ func TestAPIRepoTeams(t *testing.T) {
3638
if assert.Len(t, teams, 2) {
3739
assert.EqualValues(t, "Owners", teams[0].Name)
3840
assert.False(t, teams[0].CanCreateOrgRepo)
39-
assert.EqualValues(t, []string{"repo.code", "repo.issues", "repo.pulls", "repo.releases", "repo.wiki", "repo.ext_wiki", "repo.ext_issues"}, teams[0].Units)
41+
assert.True(t, util.IsEqualSlice(unit.AllUnitKeyNames(), teams[0].Units), fmt.Sprintf("%v == %v", unit.AllUnitKeyNames(), teams[0].Units))
4042
assert.EqualValues(t, "owner", teams[0].Permission)
4143

4244
assert.EqualValues(t, "test_team", teams[1].Name)

integrations/api_team_test.go

+96-18
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"testing"
1212

1313
"code.gitea.io/gitea/models"
14+
"code.gitea.io/gitea/models/unit"
1415
"code.gitea.io/gitea/models/unittest"
1516
user_model "code.gitea.io/gitea/models/user"
1617
"code.gitea.io/gitea/modules/convert"
@@ -65,11 +66,12 @@ func TestAPITeam(t *testing.T) {
6566
}
6667
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
6768
resp = session.MakeRequest(t, req, http.StatusCreated)
69+
apiTeam = api.Team{}
6870
DecodeJSON(t, resp, &apiTeam)
6971
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
70-
teamToCreate.Permission, teamToCreate.Units)
72+
teamToCreate.Permission, teamToCreate.Units, nil)
7173
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
72-
teamToCreate.Permission, teamToCreate.Units)
74+
teamToCreate.Permission, teamToCreate.Units, nil)
7375
teamID := apiTeam.ID
7476

7577
// Edit team.
@@ -85,51 +87,128 @@ func TestAPITeam(t *testing.T) {
8587

8688
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
8789
resp = session.MakeRequest(t, req, http.StatusOK)
90+
apiTeam = api.Team{}
8891
DecodeJSON(t, resp, &apiTeam)
8992
checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
90-
teamToEdit.Permission, teamToEdit.Units)
93+
teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
9194
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
92-
teamToEdit.Permission, teamToEdit.Units)
95+
teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
9396

9497
// Edit team Description only
9598
editDescription = "first team"
9699
teamToEditDesc := api.EditTeamOption{Description: &editDescription}
97100
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEditDesc)
98101
resp = session.MakeRequest(t, req, http.StatusOK)
102+
apiTeam = api.Team{}
99103
DecodeJSON(t, resp, &apiTeam)
100104
checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
101-
teamToEdit.Permission, teamToEdit.Units)
105+
teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
102106
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
103-
teamToEdit.Permission, teamToEdit.Units)
107+
teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
104108

105109
// Read team.
106110
teamRead := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
111+
assert.NoError(t, teamRead.GetUnits())
107112
req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID)
108113
resp = session.MakeRequest(t, req, http.StatusOK)
114+
apiTeam = api.Team{}
109115
DecodeJSON(t, resp, &apiTeam)
110116
checkTeamResponse(t, &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
111-
teamRead.Authorize.String(), teamRead.GetUnitNames())
117+
teamRead.AccessMode.String(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
118+
119+
// Delete team.
120+
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
121+
session.MakeRequest(t, req, http.StatusNoContent)
122+
unittest.AssertNotExistsBean(t, &models.Team{ID: teamID})
123+
124+
// create team again via UnitsMap
125+
// Create team.
126+
teamToCreate = &api.CreateTeamOption{
127+
Name: "team2",
128+
Description: "team two",
129+
IncludesAllRepositories: true,
130+
Permission: "write",
131+
UnitsMap: map[string]string{"repo.code": "read", "repo.issues": "write", "repo.wiki": "none"},
132+
}
133+
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
134+
resp = session.MakeRequest(t, req, http.StatusCreated)
135+
apiTeam = api.Team{}
136+
DecodeJSON(t, resp, &apiTeam)
137+
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
138+
"read", nil, teamToCreate.UnitsMap)
139+
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
140+
"read", nil, teamToCreate.UnitsMap)
141+
teamID = apiTeam.ID
142+
143+
// Edit team.
144+
editDescription = "team 1"
145+
editFalse = false
146+
teamToEdit = &api.EditTeamOption{
147+
Name: "teamtwo",
148+
Description: &editDescription,
149+
Permission: "write",
150+
IncludesAllRepositories: &editFalse,
151+
UnitsMap: map[string]string{"repo.code": "read", "repo.pulls": "read", "repo.releases": "write"},
152+
}
153+
154+
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
155+
resp = session.MakeRequest(t, req, http.StatusOK)
156+
apiTeam = api.Team{}
157+
DecodeJSON(t, resp, &apiTeam)
158+
checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
159+
"read", nil, teamToEdit.UnitsMap)
160+
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
161+
"read", nil, teamToEdit.UnitsMap)
162+
163+
// Edit team Description only
164+
editDescription = "second team"
165+
teamToEditDesc = api.EditTeamOption{Description: &editDescription}
166+
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEditDesc)
167+
resp = session.MakeRequest(t, req, http.StatusOK)
168+
apiTeam = api.Team{}
169+
DecodeJSON(t, resp, &apiTeam)
170+
checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
171+
"read", nil, teamToEdit.UnitsMap)
172+
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
173+
"read", nil, teamToEdit.UnitsMap)
174+
175+
// Read team.
176+
teamRead = unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
177+
req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID)
178+
resp = session.MakeRequest(t, req, http.StatusOK)
179+
apiTeam = api.Team{}
180+
DecodeJSON(t, resp, &apiTeam)
181+
assert.NoError(t, teamRead.GetUnits())
182+
checkTeamResponse(t, &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
183+
teamRead.AccessMode.String(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
112184

113185
// Delete team.
114186
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
115187
session.MakeRequest(t, req, http.StatusNoContent)
116188
unittest.AssertNotExistsBean(t, &models.Team{ID: teamID})
117189
}
118190

119-
func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string) {
120-
assert.Equal(t, name, apiTeam.Name, "name")
121-
assert.Equal(t, description, apiTeam.Description, "description")
122-
assert.Equal(t, includesAllRepositories, apiTeam.IncludesAllRepositories, "includesAllRepositories")
123-
assert.Equal(t, permission, apiTeam.Permission, "permission")
124-
sort.StringSlice(units).Sort()
125-
sort.StringSlice(apiTeam.Units).Sort()
126-
assert.EqualValues(t, units, apiTeam.Units, "units")
191+
func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) {
192+
t.Run(name+description, func(t *testing.T) {
193+
assert.Equal(t, name, apiTeam.Name, "name")
194+
assert.Equal(t, description, apiTeam.Description, "description")
195+
assert.Equal(t, includesAllRepositories, apiTeam.IncludesAllRepositories, "includesAllRepositories")
196+
assert.Equal(t, permission, apiTeam.Permission, "permission")
197+
if units != nil {
198+
sort.StringSlice(units).Sort()
199+
sort.StringSlice(apiTeam.Units).Sort()
200+
assert.EqualValues(t, units, apiTeam.Units, "units")
201+
}
202+
if unitsMap != nil {
203+
assert.EqualValues(t, unitsMap, apiTeam.UnitsMap, "unitsMap")
204+
}
205+
})
127206
}
128207

129-
func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string) {
208+
func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) {
130209
team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: id}).(*models.Team)
131210
assert.NoError(t, team.GetUnits(), "GetUnits")
132-
checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units)
211+
checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units, unitsMap)
133212
}
134213

135214
type TeamSearchResults struct {
@@ -162,5 +241,4 @@ func TestAPITeamSearch(t *testing.T) {
162241
req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team")
163242
req.Header.Add("X-Csrf-Token", csrf)
164243
session.MakeRequest(t, req, http.StatusForbidden)
165-
166244
}

integrations/org_test.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,10 @@ func TestOrgRestrictedUser(t *testing.T) {
156156
resp := adminSession.MakeRequest(t, req, http.StatusCreated)
157157
DecodeJSON(t, resp, &apiTeam)
158158
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
159-
teamToCreate.Permission, teamToCreate.Units)
159+
teamToCreate.Permission, teamToCreate.Units, nil)
160160
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
161-
teamToCreate.Permission, teamToCreate.Units)
162-
//teamID := apiTeam.ID
161+
teamToCreate.Permission, teamToCreate.Units, nil)
162+
// teamID := apiTeam.ID
163163

164164
// Now we need to add the restricted user to the team
165165
req = NewRequest(t, "PUT",
@@ -172,5 +172,4 @@ func TestOrgRestrictedUser(t *testing.T) {
172172

173173
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName))
174174
restrictedSession.MakeRequest(t, req, http.StatusOK)
175-
176175
}

models/access.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i
162162
// Owner team gets owner access, and skip for teams that do not
163163
// have relations with repository.
164164
if t.IsOwnerTeam() {
165-
t.Authorize = perm.AccessModeOwner
165+
t.AccessMode = perm.AccessModeOwner
166166
} else if !t.hasRepository(e, repo.ID) {
167167
continue
168168
}
@@ -171,7 +171,7 @@ func recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i
171171
return fmt.Errorf("getMembers '%d': %v", t.ID, err)
172172
}
173173
for _, m := range t.Members {
174-
updateUserAccess(accessMap, m, t.Authorize)
174+
updateUserAccess(accessMap, m, t.AccessMode)
175175
}
176176
}
177177

@@ -210,10 +210,10 @@ func recalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid
210210

211211
for _, t := range teams {
212212
if t.IsOwnerTeam() {
213-
t.Authorize = perm.AccessModeOwner
213+
t.AccessMode = perm.AccessModeOwner
214214
}
215215

216-
accessMode = maxAccessMode(accessMode, t.Authorize)
216+
accessMode = maxAccessMode(accessMode, t.AccessMode)
217217
}
218218
}
219219

models/avatars/avatar.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"path"
1111
"strconv"
1212
"strings"
13+
"sync"
1314

1415
"code.gitea.io/gitea/models/db"
1516
"code.gitea.io/gitea/modules/base"
@@ -31,16 +32,24 @@ func init() {
3132
db.RegisterModel(new(EmailHash))
3233
}
3334

35+
var (
36+
defaultAvatarLink string
37+
once sync.Once
38+
)
39+
3440
// DefaultAvatarLink the default avatar link
3541
func DefaultAvatarLink() string {
36-
u, err := url.Parse(setting.AppSubURL)
37-
if err != nil {
38-
log.Error("GetUserByEmail: %v", err)
39-
return ""
40-
}
42+
once.Do(func() {
43+
u, err := url.Parse(setting.AppSubURL)
44+
if err != nil {
45+
log.Error("Can not parse AppSubURL: %v", err)
46+
return
47+
}
4148

42-
u.Path = path.Join(u.Path, "/assets/img/avatar_default.png")
43-
return u.String()
49+
u.Path = path.Join(u.Path, "/assets/img/avatar_default.png")
50+
defaultAvatarLink = u.String()
51+
})
52+
return defaultAvatarLink
4453
}
4554

4655
// HashEmail hashes email address to MD5 string. https://en.gravatar.com/site/implement/hash/

0 commit comments

Comments
 (0)