mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	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.
			
			
This commit is contained in:
		| @@ -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") | ||||
|   | ||||
							
								
								
									
										11
									
								
								cmd/dump.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								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 | ||||
| 				} | ||||
|   | ||||
| @@ -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) | ||||
| 		} | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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) | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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) | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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] | ||||
| } | ||||
|   | ||||
							
								
								
									
										88
									
								
								modules/util/slice_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								modules/util/slice_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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}) | ||||
| } | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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 "" | ||||
| 	} | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -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, | ||||
| 		} | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jason Song
					Jason Song