From 477a1cc40ebd3ecb116c632b0717bba748e914d2 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Wed, 11 Jan 2023 13:31:16 +0800 Subject: [PATCH] Improve utils of slices (#22379) - Move the file `compare.go` and `slice.go` to `slice.go`. - Fix `ExistsInSlice`, it's buggy - It uses `sort.Search`, so it assumes that the input slice is sorted. - It passes `func(i int) bool { return slice[i] == target })` to `sort.Search`, that's incorrect, check the doc of `sort.Search`. - Conbine `IsInt64InSlice(int64, []int64)` and `ExistsInSlice(string, []string)` to `SliceContains[T]([]T, T)`. - Conbine `IsSliceInt64Eq([]int64, []int64)` and `IsEqualSlice([]string, []string)` to `SliceSortedEqual[T]([]T, T)`. - Add `SliceEqual[T]([]T, T)` as a distinction from `SliceSortedEqual[T]([]T, T)`. - Redesign `RemoveIDFromList([]int64, int64) ([]int64, bool)` to `SliceRemoveAll[T]([]T, T) []T`. - Add `SliceContainsFunc[T]([]T, func(T) bool)` and `SliceRemoveAllFunc[T]([]T, func(T) bool)` for general use. - Add comments to explain why not `golang.org/x/exp/slices`. - Add unit tests. --- cmd/admin.go | 2 +- cmd/dump.go | 11 +-- models/asymkey/ssh_key.go | 8 +- models/auth/oauth2.go | 4 +- models/git/branches.go | 8 +- models/issues/assignees.go | 2 +- models/issues/issue.go | 4 +- models/org_team.go | 21 ++--- models/user.go | 21 ++--- modules/repository/init.go | 6 +- modules/util/compare.go | 92 ------------------- modules/util/slice.go | 91 ++++++++++++++++-- modules/util/slice_test.go | 88 ++++++++++++++++++ routers/api/v1/utils/hook.go | 34 +++---- routers/web/repo/issue.go | 4 +- routers/web/repo/webhook.go | 2 +- routers/web/user/notification.go | 2 +- routers/web/user/setting/profile.go | 2 +- services/auth/source/ldap/source_search.go | 2 +- .../auth/source/smtp/source_authenticate.go | 2 +- services/wiki/wiki.go | 2 +- tests/integration/api_repo_teams_test.go | 2 +- 22 files changed, 228 insertions(+), 182 deletions(-) delete mode 100644 modules/util/compare.go create mode 100644 modules/util/slice_test.go diff --git a/cmd/admin.go b/cmd/admin.go index 7463b21d8..a662011f9 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -950,7 +950,7 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { if c.IsSet("auth-type") { conf.Auth = c.String("auth-type") validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} - if !contains(validAuthTypes, strings.ToUpper(c.String("auth-type"))) { + if !util.SliceContainsString(validAuthTypes, strings.ToUpper(c.String("auth-type"))) { return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5") } conf.Auth = c.String("auth-type") diff --git a/cmd/dump.go b/cmd/dump.go index 672a8cdc9..f40ddbac2 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -409,15 +409,6 @@ func runDump(ctx *cli.Context) error { return nil } -func contains(slice []string, s string) bool { - for _, v := range slice { - if v == s { - return true - } - } - return false -} - // addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error { absPath, err := filepath.Abs(absPath) @@ -438,7 +429,7 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA currentAbsPath := path.Join(absPath, file.Name()) currentInsidePath := path.Join(insidePath, file.Name()) if file.IsDir() { - if !contains(excludeAbsPath, currentAbsPath) { + if !util.SliceContainsString(excludeAbsPath, currentAbsPath) { if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil { return err } diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index 4d6ae8103..8d84c2f7d 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -409,14 +409,14 @@ func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys [ sshKeySplit := strings.Split(v, " ") if len(sshKeySplit) > 1 { key := strings.Join(sshKeySplit[:2], " ") - if !util.ExistsInSlice(key, providedKeys) { + if !util.SliceContainsString(providedKeys, key) { providedKeys = append(providedKeys, key) } } } // Check if Public Key sync is needed - if util.IsEqualSlice(giteaKeys, providedKeys) { + if util.SliceSortedEqual(giteaKeys, providedKeys) { log.Trace("synchronizePublicKeys[%s]: Public Keys are already in sync for %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys)) return false } @@ -425,7 +425,7 @@ func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys [ // Add new Public SSH Keys that doesn't already exist in DB var newKeys []string for _, key := range providedKeys { - if !util.ExistsInSlice(key, giteaKeys) { + if !util.SliceContainsString(giteaKeys, key) { newKeys = append(newKeys, key) } } @@ -436,7 +436,7 @@ func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys [ // Mark keys from DB that no longer exist in the source for deletion var giteaKeysToDelete []string for _, giteaKey := range giteaKeys { - if !util.ExistsInSlice(giteaKey, providedKeys) { + if !util.SliceContainsString(providedKeys, giteaKey) { log.Trace("synchronizePublicKeys[%s]: Marking Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey) giteaKeysToDelete = append(giteaKeysToDelete, giteaKey) } diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 8e5a003d1..09d4bfc4e 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -69,13 +69,13 @@ func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool { if ip != nil && ip.IsLoopback() { // strip port uri.Host = uri.Hostname() - if util.IsStringInSlice(uri.String(), app.RedirectURIs, true) { + if util.SliceContainsString(app.RedirectURIs, uri.String(), true) { return true } } } } - return util.IsStringInSlice(redirectURI, app.RedirectURIs, true) + return util.SliceContainsString(app.RedirectURIs, redirectURI, true) } // Base32 characters, but lowercased. diff --git a/models/git/branches.go b/models/git/branches.go index 2becbc3d1..5ec9fc173 100644 --- a/models/git/branches.go +++ b/models/git/branches.go @@ -342,7 +342,7 @@ func IsProtectedBranch(ctx context.Context, repoID int64, branchName string) (bo // updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with // the users from newWhitelist which have explicit read or write access to the repo. func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { - hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist) + hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist) if !hasUsersChanged { return currentWhitelist, nil } @@ -363,7 +363,7 @@ func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, c // updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with // the users from newWhitelist which have write access to the repo. func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { - hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist) + hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist) if !hasUsersChanged { return currentWhitelist, nil } @@ -392,7 +392,7 @@ func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, curre // updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with // the teams from newWhitelist which have write access to the repo. func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) { - hasTeamsChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist) + hasTeamsChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist) if !hasTeamsChanged { return currentWhitelist, nil } @@ -404,7 +404,7 @@ func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, curre whitelist = make([]int64, 0, len(teams)) for i := range teams { - if util.IsInt64InSlice(teams[i].ID, newWhitelist) { + if util.SliceContains(newWhitelist, teams[i].ID) { whitelist = append(whitelist, teams[i].ID) } } diff --git a/models/issues/assignees.go b/models/issues/assignees.go index 159086bd0..16f675a83 100644 --- a/models/issues/assignees.go +++ b/models/issues/assignees.go @@ -155,7 +155,7 @@ func MakeIDsFromAPIAssigneesToAdd(ctx context.Context, oneAssignee string, multi var requestAssignees []string // Keeping the old assigning method for compatibility reasons - if oneAssignee != "" && !util.IsStringInSlice(oneAssignee, multipleAssignees) { + if oneAssignee != "" && !util.SliceContainsString(multipleAssignees, oneAssignee) { requestAssignees = append(requestAssignees, oneAssignee) } diff --git a/models/issues/issue.go b/models/issues/issue.go index 417d6a155..4a8ab0682 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -1529,7 +1529,7 @@ func IsUserParticipantsOfIssue(user *user_model.User, issue *Issue) bool { log.Error(err.Error()) return false } - return util.IsInt64InSlice(user.ID, userIDs) + return util.SliceContains(userIDs, user.ID) } // UpdateIssueMentions updates issue-user relations for mentioned users. @@ -2023,7 +2023,7 @@ func (issue *Issue) GetParticipantIDsByIssue(ctx context.Context) ([]int64, erro Find(&userIDs); err != nil { return nil, fmt.Errorf("get poster IDs: %w", err) } - if !util.IsInt64InSlice(issue.PosterID, userIDs) { + if !util.SliceContains(userIDs, issue.PosterID) { return append(userIDs, issue.PosterID), nil } return userIDs, nil diff --git a/models/org_team.go b/models/org_team.go index b3ee842c1..2bbf1d8d8 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -398,20 +398,13 @@ func DeleteTeam(t *organization.Team) error { return fmt.Errorf("findProtectedBranches: %w", err) } for _, p := range protections { - var matched1, matched2, matched3 bool - if len(p.WhitelistTeamIDs) != 0 { - p.WhitelistTeamIDs, matched1 = util.RemoveIDFromList( - p.WhitelistTeamIDs, t.ID) - } - if len(p.ApprovalsWhitelistTeamIDs) != 0 { - p.ApprovalsWhitelistTeamIDs, matched2 = util.RemoveIDFromList( - p.ApprovalsWhitelistTeamIDs, t.ID) - } - if len(p.MergeWhitelistTeamIDs) != 0 { - p.MergeWhitelistTeamIDs, matched3 = util.RemoveIDFromList( - p.MergeWhitelistTeamIDs, t.ID) - } - if matched1 || matched2 || matched3 { + lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs) + p.WhitelistTeamIDs = util.SliceRemoveAll(p.WhitelistTeamIDs, t.ID) + p.ApprovalsWhitelistTeamIDs = util.SliceRemoveAll(p.ApprovalsWhitelistTeamIDs, t.ID) + p.MergeWhitelistTeamIDs = util.SliceRemoveAll(p.MergeWhitelistTeamIDs, t.ID) + if lenIDs != len(p.WhitelistTeamIDs) || + lenApprovalIDs != len(p.ApprovalsWhitelistTeamIDs) || + lenMergeIDs != len(p.MergeWhitelistTeamIDs) { if _, err = sess.ID(p.ID).Cols( "whitelist_team_i_ds", "merge_whitelist_team_i_ds", diff --git a/models/user.go b/models/user.go index 715d0e386..10282d0db 100644 --- a/models/user.go +++ b/models/user.go @@ -141,20 +141,13 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) break } for _, p := range protections { - var matched1, matched2, matched3 bool - if len(p.WhitelistUserIDs) != 0 { - p.WhitelistUserIDs, matched1 = util.RemoveIDFromList( - p.WhitelistUserIDs, u.ID) - } - if len(p.ApprovalsWhitelistUserIDs) != 0 { - p.ApprovalsWhitelistUserIDs, matched2 = util.RemoveIDFromList( - p.ApprovalsWhitelistUserIDs, u.ID) - } - if len(p.MergeWhitelistUserIDs) != 0 { - p.MergeWhitelistUserIDs, matched3 = util.RemoveIDFromList( - p.MergeWhitelistUserIDs, u.ID) - } - if matched1 || matched2 || matched3 { + lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs) + p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, u.ID) + p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, u.ID) + p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, u.ID) + if lenIDs != len(p.WhitelistUserIDs) || + lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) || + lenMergeIDs != len(p.MergeWhitelistUserIDs) { if _, err = e.ID(p.ID).Cols( "whitelist_user_i_ds", "merge_whitelist_user_i_ds", diff --git a/modules/repository/init.go b/modules/repository/init.go index 59284a5ba..2b0d0be7b 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -170,7 +170,7 @@ func LoadRepoConfig() { } for _, f := range customFiles { - if !util.IsStringInSlice(f, files, true) { + if !util.SliceContainsString(files, f, true) { files = append(files, f) } } @@ -200,12 +200,12 @@ func LoadRepoConfig() { // Filter out invalid names and promote preferred licenses. sortedLicenses := make([]string, 0, len(Licenses)) for _, name := range setting.Repository.PreferredLicenses { - if util.IsStringInSlice(name, Licenses, true) { + if util.SliceContainsString(Licenses, name, true) { sortedLicenses = append(sortedLicenses, name) } } for _, name := range Licenses { - if !util.IsStringInSlice(name, setting.Repository.PreferredLicenses, true) { + if !util.SliceContainsString(setting.Repository.PreferredLicenses, name, true) { sortedLicenses = append(sortedLicenses, name) } } diff --git a/modules/util/compare.go b/modules/util/compare.go deleted file mode 100644 index 9ac778dfd..000000000 --- a/modules/util/compare.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package util - -import ( - "sort" - "strings" -) - -// Int64Slice attaches the methods of Interface to []int64, sorting in increasing order. -type Int64Slice []int64 - -func (p Int64Slice) Len() int { return len(p) } -func (p Int64Slice) Less(i, j int) bool { return p[i] < p[j] } -func (p Int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -// IsSliceInt64Eq returns if the two slice has the same elements but different sequences. -func IsSliceInt64Eq(a, b []int64) bool { - if len(a) != len(b) { - return false - } - sort.Sort(Int64Slice(a)) - sort.Sort(Int64Slice(b)) - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - return true -} - -// ExistsInSlice returns true if string exists in slice. -func ExistsInSlice(target string, slice []string) bool { - i := sort.Search(len(slice), - func(i int) bool { return slice[i] == target }) - return i < len(slice) -} - -// IsStringInSlice sequential searches if string exists in slice. -func IsStringInSlice(target string, slice []string, insensitive ...bool) bool { - caseInsensitive := false - if len(insensitive) != 0 && insensitive[0] { - caseInsensitive = true - target = strings.ToLower(target) - } - - for i := 0; i < len(slice); i++ { - if caseInsensitive { - if strings.ToLower(slice[i]) == target { - return true - } - } else { - if slice[i] == target { - return true - } - } - } - return false -} - -// IsInt64InSlice sequential searches if int64 exists in slice. -func IsInt64InSlice(target int64, slice []int64) bool { - for i := 0; i < len(slice); i++ { - if slice[i] == target { - return true - } - } - return false -} - -// IsEqualSlice returns true if slices are equal. -func IsEqualSlice(target, source []string) bool { - if len(target) != len(source) { - return false - } - - if (target == nil) != (source == nil) { - return false - } - - sort.Strings(target) - sort.Strings(source) - - for i, v := range target { - if v != source[i] { - return false - } - } - - return true -} diff --git a/modules/util/slice.go b/modules/util/slice.go index 17345cbc4..74356f549 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -1,17 +1,90 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT +// Most of the functions in this file can have better implementations with "golang.org/x/exp/slices". +// However, "golang.org/x/exp" is experimental and unreliable, we shouldn't use it. +// So lets waiting for the "slices" has be promoted to the main repository one day. + package util -// RemoveIDFromList removes the given ID from the slice, if found. -// It does not preserve order, and assumes the ID is unique. -func RemoveIDFromList(list []int64, id int64) ([]int64, bool) { - n := len(list) - 1 - for i, item := range list { - if item == id { - list[i] = list[n] - return list[:n], true +import "strings" + +// SliceContains returns true if the target exists in the slice. +func SliceContains[T comparable](slice []T, target T) bool { + return SliceContainsFunc(slice, func(t T) bool { return t == target }) +} + +// SliceContainsFunc returns true if any element in the slice satisfies the targetFunc. +func SliceContainsFunc[T any](slice []T, targetFunc func(T) bool) bool { + for _, v := range slice { + if targetFunc(v) { + return true } } - return list, false + return false +} + +// SliceContainsString sequential searches if string exists in slice. +func SliceContainsString(slice []string, target string, insensitive ...bool) bool { + if len(insensitive) != 0 && insensitive[0] { + target = strings.ToLower(target) + return SliceContainsFunc(slice, func(t string) bool { return strings.ToLower(t) == target }) + } + + return SliceContains(slice, target) +} + +// SliceSortedEqual returns true if the two slices will be equal when they get sorted. +// It doesn't require that the slices have been sorted, and it doesn't sort them either. +func SliceSortedEqual[T comparable](s1, s2 []T) bool { + if len(s1) != len(s2) { + return false + } + + counts := make(map[T]int, len(s1)) + for _, v := range s1 { + counts[v]++ + } + for _, v := range s2 { + counts[v]-- + } + + for _, v := range counts { + if v != 0 { + return false + } + } + return true +} + +// SliceEqual returns true if the two slices are equal. +func SliceEqual[T comparable](s1, s2 []T) bool { + if len(s1) != len(s2) { + return false + } + + for i, v := range s1 { + if s2[i] != v { + return false + } + } + return true +} + +// SliceRemoveAll removes all the target elements from the slice. +func SliceRemoveAll[T comparable](slice []T, target T) []T { + return SliceRemoveAllFunc(slice, func(t T) bool { return t == target }) +} + +// SliceRemoveAllFunc removes all elements which satisfy the targetFunc from the slice. +func SliceRemoveAllFunc[T comparable](slice []T, targetFunc func(T) bool) []T { + idx := 0 + for _, v := range slice { + if targetFunc(v) { + continue + } + slice[idx] = v + idx++ + } + return slice[:idx] } diff --git a/modules/util/slice_test.go b/modules/util/slice_test.go new file mode 100644 index 000000000..b0b771a79 --- /dev/null +++ b/modules/util/slice_test.go @@ -0,0 +1,88 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSliceContains(t *testing.T) { + assert.True(t, SliceContains([]int{2, 0, 2, 3}, 2)) + assert.True(t, SliceContains([]int{2, 0, 2, 3}, 0)) + assert.True(t, SliceContains([]int{2, 0, 2, 3}, 3)) + + assert.True(t, SliceContains([]string{"2", "0", "2", "3"}, "0")) + assert.True(t, SliceContains([]float64{2, 0, 2, 3}, 0)) + assert.True(t, SliceContains([]bool{false, true, false}, true)) + + assert.False(t, SliceContains([]int{2, 0, 2, 3}, 4)) + assert.False(t, SliceContains([]int{}, 4)) + assert.False(t, SliceContains(nil, 4)) +} + +func TestSliceContainsString(t *testing.T) { + assert.True(t, SliceContainsString([]string{"c", "b", "a", "b"}, "a")) + assert.True(t, SliceContainsString([]string{"c", "b", "a", "b"}, "b")) + assert.True(t, SliceContainsString([]string{"c", "b", "a", "b"}, "A", true)) + assert.True(t, SliceContainsString([]string{"C", "B", "A", "B"}, "a", true)) + + assert.False(t, SliceContainsString([]string{"c", "b", "a", "b"}, "z")) + assert.False(t, SliceContainsString([]string{"c", "b", "a", "b"}, "A")) + assert.False(t, SliceContainsString([]string{}, "a")) + assert.False(t, SliceContainsString(nil, "a")) +} + +func TestSliceSortedEqual(t *testing.T) { + assert.True(t, SliceSortedEqual([]int{2, 0, 2, 3}, []int{2, 0, 2, 3})) + assert.True(t, SliceSortedEqual([]int{3, 0, 2, 2}, []int{2, 0, 2, 3})) + assert.True(t, SliceSortedEqual([]int{}, []int{})) + assert.True(t, SliceSortedEqual([]int(nil), nil)) + assert.True(t, SliceSortedEqual([]int(nil), []int{})) + assert.True(t, SliceSortedEqual([]int{}, []int{})) + + assert.True(t, SliceSortedEqual([]string{"2", "0", "2", "3"}, []string{"2", "0", "2", "3"})) + assert.True(t, SliceSortedEqual([]float64{2, 0, 2, 3}, []float64{2, 0, 2, 3})) + assert.True(t, SliceSortedEqual([]bool{false, true, false}, []bool{false, true, false})) + + assert.False(t, SliceSortedEqual([]int{2, 0, 2}, []int{2, 0, 2, 3})) + assert.False(t, SliceSortedEqual([]int{}, []int{2, 0, 2, 3})) + assert.False(t, SliceSortedEqual(nil, []int{2, 0, 2, 3})) + assert.False(t, SliceSortedEqual([]int{2, 0, 2, 4}, []int{2, 0, 2, 3})) + assert.False(t, SliceSortedEqual([]int{2, 0, 0, 3}, []int{2, 0, 2, 3})) +} + +func TestSliceEqual(t *testing.T) { + assert.True(t, SliceEqual([]int{2, 0, 2, 3}, []int{2, 0, 2, 3})) + assert.True(t, SliceEqual([]int{}, []int{})) + assert.True(t, SliceEqual([]int(nil), nil)) + assert.True(t, SliceEqual([]int(nil), []int{})) + assert.True(t, SliceEqual([]int{}, []int{})) + + assert.True(t, SliceEqual([]string{"2", "0", "2", "3"}, []string{"2", "0", "2", "3"})) + assert.True(t, SliceEqual([]float64{2, 0, 2, 3}, []float64{2, 0, 2, 3})) + assert.True(t, SliceEqual([]bool{false, true, false}, []bool{false, true, false})) + + assert.False(t, SliceEqual([]int{3, 0, 2, 2}, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual([]int{2, 0, 2}, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual([]int{}, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual(nil, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual([]int{2, 0, 2, 4}, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual([]int{2, 0, 0, 3}, []int{2, 0, 2, 3})) +} + +func TestSliceRemoveAll(t *testing.T) { + assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 0), []int{2, 2, 3}) + assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 2), []int{0, 3}) + assert.Equal(t, SliceRemoveAll([]int{0, 0, 0, 0}, 0), []int{}) + assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 4), []int{2, 0, 2, 3}) + assert.Equal(t, SliceRemoveAll([]int{}, 0), []int{}) + assert.Equal(t, SliceRemoveAll([]int(nil), 0), []int(nil)) + assert.Equal(t, SliceRemoveAll([]int{}, 0), []int{}) + + assert.Equal(t, SliceRemoveAll([]string{"2", "0", "2", "3"}, "0"), []string{"2", "2", "3"}) + assert.Equal(t, SliceRemoveAll([]float64{2, 0, 2, 3}, 0), []float64{2, 2, 3}) + assert.Equal(t, SliceRemoveAll([]bool{false, true, false}, true), []bool{false, false}) +} diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index 065b761ad..44fba22b5 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -107,11 +107,11 @@ func toAPIHook(ctx *context.APIContext, repoLink string, hook *webhook.Webhook) } func issuesHook(events []string, event string) bool { - return util.IsStringInSlice(event, events, true) || util.IsStringInSlice(string(webhook_module.HookEventIssues), events, true) + return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventIssues), true) } func pullHook(events []string, event string) bool { - return util.IsStringInSlice(event, events, true) || util.IsStringInSlice(string(webhook_module.HookEventPullRequest), events, true) + return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true) } // addHook add the hook specified by `form`, `orgID` and `repoID`. If there is @@ -130,15 +130,15 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID HookEvent: &webhook_module.HookEvent{ ChooseEvents: true, HookEvents: webhook_module.HookEvents{ - Create: util.IsStringInSlice(string(webhook_module.HookEventCreate), form.Events, true), - Delete: util.IsStringInSlice(string(webhook_module.HookEventDelete), form.Events, true), - Fork: util.IsStringInSlice(string(webhook_module.HookEventFork), form.Events, true), + Create: util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true), + Delete: util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true), + Fork: util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true), Issues: issuesHook(form.Events, "issues_only"), IssueAssign: issuesHook(form.Events, string(webhook_module.HookEventIssueAssign)), IssueLabel: issuesHook(form.Events, string(webhook_module.HookEventIssueLabel)), IssueMilestone: issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone)), IssueComment: issuesHook(form.Events, string(webhook_module.HookEventIssueComment)), - Push: util.IsStringInSlice(string(webhook_module.HookEventPush), form.Events, true), + Push: util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true), PullRequest: pullHook(form.Events, "pull_request_only"), PullRequestAssign: pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)), PullRequestLabel: pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)), @@ -146,9 +146,9 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID PullRequestComment: pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)), PullRequestReview: pullHook(form.Events, "pull_request_review"), PullRequestSync: pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)), - Wiki: util.IsStringInSlice(string(webhook_module.HookEventWiki), form.Events, true), - Repository: util.IsStringInSlice(string(webhook_module.HookEventRepository), form.Events, true), - Release: util.IsStringInSlice(string(webhook_module.HookEventRelease), form.Events, true), + Wiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true), + Repository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true), + Release: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true), }, BranchFilter: form.BranchFilter, }, @@ -277,14 +277,14 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh w.PushOnly = false w.SendEverything = false w.ChooseEvents = true - w.Create = util.IsStringInSlice(string(webhook_module.HookEventCreate), form.Events, true) - w.Push = util.IsStringInSlice(string(webhook_module.HookEventPush), form.Events, true) - w.Create = util.IsStringInSlice(string(webhook_module.HookEventCreate), form.Events, true) - w.Delete = util.IsStringInSlice(string(webhook_module.HookEventDelete), form.Events, true) - w.Fork = util.IsStringInSlice(string(webhook_module.HookEventFork), form.Events, true) - w.Repository = util.IsStringInSlice(string(webhook_module.HookEventRepository), form.Events, true) - w.Wiki = util.IsStringInSlice(string(webhook_module.HookEventWiki), form.Events, true) - w.Release = util.IsStringInSlice(string(webhook_module.HookEventRelease), form.Events, true) + w.Create = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true) + w.Push = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true) + w.Create = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true) + w.Delete = util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true) + w.Fork = util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true) + w.Repository = util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true) + w.Wiki = util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true) + w.Release = util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true) w.BranchFilter = form.BranchFilter err := w.SetHeaderAuthorization(form.AuthorizationHeader) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 07dd98f1a..f037c771d 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -139,7 +139,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti viewType := ctx.FormString("type") sortType := ctx.FormString("sort") types := []string{"all", "your_repositories", "assigned", "created_by", "mentioned", "review_requested"} - if !util.IsStringInSlice(viewType, types, true) { + if !util.SliceContainsString(types, viewType, true) { viewType = "all" } @@ -3087,7 +3087,7 @@ func updateAttachments(ctx *context.Context, item interface{}, files []string) e return fmt.Errorf("unknown Type: %T", content) } for i := 0; i < len(attachments); i++ { - if util.IsStringInSlice(attachments[i].UUID, files) { + if util.SliceContainsString(files, attachments[i].UUID) { continue } if err := repo_model.DeleteAttachment(attachments[i], true); err != nil { diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index bf56e3d0b..96261af67 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -110,7 +110,7 @@ func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) { func checkHookType(ctx *context.Context) string { hookType := strings.ToLower(ctx.Params(":type")) - if !util.IsStringInSlice(hookType, setting.Webhook.Types, true) { + if !util.SliceContainsString(setting.Webhook.Types, hookType, true) { ctx.NotFound("checkHookType", nil) return "" } diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index b21d52bbf..e96b3dd27 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -214,7 +214,7 @@ func NotificationSubscriptions(ctx *context.Context) { ctx.Data["SortType"] = sortType state := ctx.FormString("state") - if !util.IsStringInSlice(state, []string{"all", "open", "closed"}, true) { + if !util.SliceContainsString([]string{"all", "open", "closed"}, state, true) { state = "all" } ctx.Data["State"] = state diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index ef45ad8a8..dac00850b 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -413,7 +413,7 @@ func UpdateUserLang(ctx *context.Context) { ctx.Data["PageIsSettingsAppearance"] = true if len(form.Language) != 0 { - if !util.IsStringInSlice(form.Language, setting.Langs) { + if !util.SliceContainsString(setting.Langs, form.Language) { ctx.Flash.Error(ctx.Tr("settings.update_language_not_found", form.Language)) ctx.Redirect(setting.AppSubURL + "/user/settings/appearance") return diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index 6b6b56db2..68ebba391 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -246,7 +246,7 @@ func (source *Source) getMappedMemberships(l *ldap.Conn, uid string) (map[string membershipsToAdd := map[string][]string{} membershipsToRemove := map[string][]string{} for group, memberships := range ldapGroupsToTeams { - isUserInGroup := util.IsStringInSlice(group, usersLdapGroups) + isUserInGroup := util.SliceContainsString(usersLdapGroups, group) if isUserInGroup { for org, teams := range memberships { membershipsToAdd[org] = teams diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index 00aaa8e08..36b351198 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -23,7 +23,7 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str idx := strings.Index(userName, "@") if idx == -1 { return nil, user_model.ErrUserNotExist{Name: userName} - } else if !util.IsStringInSlice(userName[idx+1:], strings.Split(source.AllowedDomains, ","), true) { + } else if !util.SliceContainsString(strings.Split(source.AllowedDomains, ","), userName[idx+1:], true) { return nil, user_model.ErrUserNotExist{Name: userName} } } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index e483416fd..e5cb2db02 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -35,7 +35,7 @@ const ( ) func nameAllowed(name string) error { - if util.IsStringInSlice(name, reservedWikiNames) { + if util.SliceContainsString(reservedWikiNames, name) { return repo_model.ErrWikiReservedName{ Title: name, } diff --git a/tests/integration/api_repo_teams_test.go b/tests/integration/api_repo_teams_test.go index e4d054aaf..102f170d9 100644 --- a/tests/integration/api_repo_teams_test.go +++ b/tests/integration/api_repo_teams_test.go @@ -38,7 +38,7 @@ func TestAPIRepoTeams(t *testing.T) { if assert.Len(t, teams, 2) { assert.EqualValues(t, "Owners", teams[0].Name) assert.True(t, teams[0].CanCreateOrgRepo) - assert.True(t, util.IsEqualSlice(unit.AllUnitKeyNames(), teams[0].Units), fmt.Sprintf("%v == %v", unit.AllUnitKeyNames(), teams[0].Units)) + assert.True(t, util.SliceSortedEqual(unit.AllUnitKeyNames(), teams[0].Units), fmt.Sprintf("%v == %v", unit.AllUnitKeyNames(), teams[0].Units)) assert.EqualValues(t, "owner", teams[0].Permission) assert.EqualValues(t, "test_team", teams[1].Name)