Преглед изворни кода

repo: support unlisted but publicly accessible repositories (#6176)

Co-authored-by: ᴜɴᴋɴᴡᴏɴ <[email protected]>
Achilleas Koutsou пре 4 година
родитељ
комит
c4360747a3

+ 2 - 0
CHANGELOG.md

@@ -6,6 +6,8 @@ All notable changes to Gogs are documented in this file.
 
 ### Added
 
+- An unlisted option is added when create or migrate a repository. Unlisted repositories are public but not being listed for users without direct access in the UI. [#5733](https://github.com/gogs/gogs/issues/5733)
+
 ### Changed
 
 - The default branch has been changed to `main`. [#6285](https://github.com/gogs/gogs/pull/6285)

+ 2 - 0
conf/locale/locale_en-US.ini

@@ -399,7 +399,9 @@ owner = Owner
 repo_name = Repository Name
 repo_name_helper = A good repository name is usually composed of short, memorable and unique keywords.
 visibility = Visibility
+unlisted = Unlisted
 visiblity_helper = This repository is <span class="ui red text">Private</span>
+unlisted_helper = This repository is <span class="ui red text">Unlisted</span>
 visiblity_helper_forced = Site admin has forced all new repositories to be <span class="ui red text">Private</span>
 visiblity_fork_helper = (Change of this value will affect all forks)
 clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!

Разлика између датотеке није приказан због своје велике величине
+ 112 - 112
internal/assets/conf/conf_gen.go


Разлика између датотеке није приказан због своје велике величине
+ 28 - 28
internal/assets/templates/templates_gen.go


+ 6 - 6
internal/db/action.go

@@ -188,7 +188,7 @@ func newRepoAction(e Engine, doer, owner *User, repo *Repository) (err error) {
 		RepoID:       repo.ID,
 		RepoUserName: repo.Owner.Name,
 		RepoName:     repo.Name,
-		IsPrivate:    repo.IsPrivate,
+		IsPrivate:    repo.IsPrivate || repo.IsUnlisted,
 	})
 }
 
@@ -205,7 +205,7 @@ func renameRepoAction(e Engine, actUser *User, oldRepoName string, repo *Reposit
 		RepoID:       repo.ID,
 		RepoUserName: repo.Owner.Name,
 		RepoName:     repo.Name,
-		IsPrivate:    repo.IsPrivate,
+		IsPrivate:    repo.IsPrivate || repo.IsUnlisted,
 		Content:      oldRepoName,
 	}); err != nil {
 		return fmt.Errorf("notify watchers: %v", err)
@@ -512,7 +512,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
 		RepoUserName: repo.MustOwner().Name,
 		RepoName:     repo.Name,
 		RefName:      refName,
-		IsPrivate:    repo.IsPrivate,
+		IsPrivate:    repo.IsPrivate || repo.IsUnlisted,
 	}
 
 	apiRepo := repo.APIFormat(nil)
@@ -628,7 +628,7 @@ func transferRepoAction(e Engine, doer, oldOwner *User, repo *Repository) (err e
 		RepoID:       repo.ID,
 		RepoUserName: repo.Owner.Name,
 		RepoName:     repo.Name,
-		IsPrivate:    repo.IsPrivate,
+		IsPrivate:    repo.IsPrivate || repo.IsUnlisted,
 		Content:      path.Join(oldOwner.Name, repo.Name),
 	}); err != nil {
 		return fmt.Errorf("notifyWatchers: %v", err)
@@ -659,7 +659,7 @@ func mergePullRequestAction(e Engine, doer *User, repo *Repository, issue *Issue
 		RepoID:       repo.ID,
 		RepoUserName: repo.Owner.Name,
 		RepoName:     repo.Name,
-		IsPrivate:    repo.IsPrivate,
+		IsPrivate:    repo.IsPrivate || repo.IsUnlisted,
 	})
 }
 
@@ -678,7 +678,7 @@ func mirrorSyncAction(opType ActionType, repo *Repository, refName string, data
 		RepoUserName: repo.MustOwner().Name,
 		RepoName:     repo.Name,
 		RefName:      refName,
-		IsPrivate:    repo.IsPrivate,
+		IsPrivate:    repo.IsPrivate || repo.IsUnlisted,
 	})
 }
 

+ 1 - 1
internal/db/org.go

@@ -517,7 +517,7 @@ func (org *User) GetUserRepositories(userID int64, page, pageSize int) ([]*Repos
 	repos := make([]*Repository, 0, pageSize)
 	if err = x.Where("owner_id = ?", org.ID).
 		And(builder.Or(
-			builder.Expr("is_private = ?", false),
+			builder.And(builder.Expr("is_private = ?", false), builder.Expr("is_unlisted = ?", false)),
 			builder.In("id", teamRepoIDs))).
 		Desc("updated_unix").
 		Limit(pageSize, (page-1)*pageSize).

+ 15 - 5
internal/db/repo.go

@@ -173,8 +173,11 @@ type Repository struct {
 	NumOpenMilestones   int `xorm:"-" gorm:"-" json:"-"`
 	NumTags             int `xorm:"-" gorm:"-" json:"-"`
 
-	IsPrivate bool
-	IsBare    bool
+	IsPrivate  bool
+	// TODO: When migrate to GORM, make sure to do a loose migration with `HasColumn` and `AddColumn`,
+	// see docs in https://gorm.io/docs/migration.html.
+	IsUnlisted bool
+	IsBare     bool
 
 	IsMirror bool
 	*Mirror  `xorm:"-" gorm:"-" json:"-"`
@@ -717,6 +720,7 @@ type MigrateRepoOptions struct {
 	Name        string
 	Description string
 	IsPrivate   bool
+	IsUnlisted  bool
 	IsMirror    bool
 	RemoteAddr  string
 }
@@ -746,6 +750,7 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository,
 		Name:        opts.Name,
 		Description: opts.Description,
 		IsPrivate:   opts.IsPrivate,
+		IsUnlisted:  opts.IsUnlisted,
 		IsMirror:    opts.IsMirror,
 	})
 	if err != nil {
@@ -920,6 +925,7 @@ type CreateRepoOptions struct {
 	License     string
 	Readme      string
 	IsPrivate   bool
+	IsUnlisted  bool
 	IsMirror    bool
 	AutoInit    bool
 }
@@ -1131,6 +1137,7 @@ func CreateRepository(doer, owner *User, opts CreateRepoOptions) (_ *Repository,
 		LowerName:    strings.ToLower(opts.Name),
 		Description:  opts.Description,
 		IsPrivate:    opts.IsPrivate,
+		IsUnlisted:   opts.IsUnlisted,
 		EnableWiki:   true,
 		EnableIssues: true,
 		EnablePulls:  true,
@@ -1478,13 +1485,14 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
 		}
 		for i := range forkRepos {
 			forkRepos[i].IsPrivate = repo.IsPrivate
+			forkRepos[i].IsUnlisted = repo.IsUnlisted
 			if err = updateRepository(e, forkRepos[i], true); err != nil {
 				return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err)
 			}
 		}
 
 		// Change visibility of generated actions
-		if _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{IsPrivate: repo.IsPrivate}); err != nil {
+		if _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{IsPrivate: repo.IsPrivate || repo.IsUnlisted}); err != nil {
 			return fmt.Errorf("change action visibility of repository: %v", err)
 		}
 	}
@@ -1687,6 +1695,7 @@ func GetUserRepositories(opts *UserRepoOptions) ([]*Repository, error) {
 	sess := x.Where("owner_id=?", opts.UserID).Desc("updated_unix")
 	if !opts.Private {
 		sess.And("is_private=?", false)
+		sess.And("is_unlisted=?", false)
 	}
 
 	if opts.Page <= 0 {
@@ -1760,11 +1769,11 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (repos []*Repository, count
 	// this does not include other people's private repositories even if opts.UserID is an admin.
 	if !opts.Private && opts.UserID > 0 {
 		sess.Join("LEFT", "access", "access.repo_id = repo.id").
-			Where("repo.owner_id = ? OR access.user_id = ? OR repo.is_private = ? OR (repo.is_private = ? AND (repo.allow_public_wiki = ? OR repo.allow_public_issues = ?))", opts.UserID, opts.UserID, false, true, true, true)
+			Where("repo.owner_id = ? OR access.user_id = ? OR (repo.is_private = ? AND repo.is_unlisted = ?) OR (repo.is_private = ? AND (repo.allow_public_wiki = ? OR repo.allow_public_issues = ?))", opts.UserID, opts.UserID, false, false, true, true, true)
 	} else {
 		// Only return public repositories if opts.Private is not set
 		if !opts.Private {
-			sess.And("repo.is_private = ? OR (repo.is_private = ? AND (repo.allow_public_wiki = ? OR repo.allow_public_issues = ?))", false, true, true, true)
+			sess.And("(repo.is_private = ? AND repo.is_unlisted = ?) OR (repo.is_private = ? AND (repo.allow_public_wiki = ? OR repo.allow_public_issues = ?))", false, false, true, true, true)
 		}
 	}
 	if len(opts.Keyword) > 0 {
@@ -2400,6 +2409,7 @@ func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string)
 		Description:   desc,
 		DefaultBranch: baseRepo.DefaultBranch,
 		IsPrivate:     baseRepo.IsPrivate,
+		IsUnlisted:    baseRepo.IsUnlisted,
 		IsFork:        true,
 		ForkID:        baseRepo.ID,
 	}

+ 3 - 0
internal/form/repo.go

@@ -26,6 +26,7 @@ type CreateRepo struct {
 	UserID      int64  `binding:"Required"`
 	RepoName    string `binding:"Required;AlphaDashDot;MaxSize(100)"`
 	Private     bool
+	Unlisted    bool
 	Description string `binding:"MaxSize(512)"`
 	AutoInit    bool
 	Gitignores  string
@@ -45,6 +46,7 @@ type MigrateRepo struct {
 	RepoName     string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
 	Mirror       bool   `json:"mirror"`
 	Private      bool   `json:"private"`
+	Unlisted     bool   `json:"unlisted"`
 	Description  string `json:"description" binding:"MaxSize(512)"`
 }
 
@@ -88,6 +90,7 @@ type RepoSetting struct {
 	Interval      int
 	MirrorAddress string
 	Private       bool
+	Unlisted      bool
 	EnablePrune   bool
 
 	// Advanced settings

+ 1 - 0
internal/route/repo/pull.go

@@ -60,6 +60,7 @@ func parseBaseRepository(c *context.Context) *db.Repository {
 	c.Data["repo_name"] = baseRepo.Name
 	c.Data["description"] = baseRepo.Description
 	c.Data["IsPrivate"] = baseRepo.IsPrivate
+	c.Data["IsUnlisted"] = baseRepo.IsUnlisted
 
 	if err = baseRepo.GetOwner(); err != nil {
 		c.Error(err, "get owner")

+ 2 - 0
internal/route/repo/repo.go

@@ -126,6 +126,7 @@ func CreatePost(c *context.Context, f form.CreateRepo) {
 		License:     f.License,
 		Readme:      f.Readme,
 		IsPrivate:   f.Private || conf.Repository.ForcePrivate,
+		IsUnlisted:  f.Unlisted,
 		AutoInit:    f.AutoInit,
 	})
 	if err == nil {
@@ -197,6 +198,7 @@ func MigratePost(c *context.Context, f form.MigrateRepo) {
 		Name:        f.RepoName,
 		Description: f.Description,
 		IsPrivate:   f.Private || conf.Repository.ForcePrivate,
+		IsUnlisted:  f.Unlisted,
 		IsMirror:    f.Mirror,
 		RemoteAddr:  remoteAddr,
 	})

+ 3 - 1
internal/route/repo/setting.go

@@ -87,10 +87,12 @@ func SettingsPost(c *context.Context, f form.RepoSetting) {
 		// Visibility of forked repository is forced sync with base repository.
 		if repo.IsFork {
 			f.Private = repo.BaseRepo.IsPrivate
+			f.Unlisted = repo.BaseRepo.IsUnlisted
 		}
 
-		visibilityChanged := repo.IsPrivate != f.Private
+		visibilityChanged := repo.IsPrivate != f.Private || repo.IsUnlisted != f.Unlisted
 		repo.IsPrivate = f.Private
+		repo.IsUnlisted = f.Unlisted
 		if err := db.UpdateRepository(repo, visibilityChanged); err != nil {
 			c.Error(err, "update repository")
 			return

+ 2 - 0
templates/explore/repo_list.tmpl

@@ -10,6 +10,8 @@
 						<a class="name" href="{{AppSubURL}}/{{if .Owner}}{{.Owner.Name}}{{else if $.Org}}{{$.Org.Name}}{{else}}{{$.Owner.Name}}{{end}}/{{.Name}}">{{if $.PageIsExplore}}{{.Owner.Name}} / {{end}}{{.Name}}</a>
 						{{if .IsPrivate}}
 							<span class="text gold"><i class="octicon octicon-lock"></i></span>
+						{{else if .IsUnlisted}}
+							<span><i class="octicon octicon-eye"></i></span>
 						{{else if .IsFork}}
 							<span><i class="octicon octicon-repo-forked"></i></span>
 						{{else if .IsMirror}}

+ 7 - 0
templates/repo/create.tmpl

@@ -50,6 +50,13 @@
 							{{end}}
 						</div>
 					</div>
+					<div class="inline field">
+						<label></label>
+						<div class="ui checkbox">
+							<input name="unlisted" type="checkbox">
+							<label>{{.i18n.Tr "repo.unlisted_helper" | Safe}}</label>
+						</div>
+					</div>
 					<div class="inline field {{if .Err_Description}}error{{end}}">
 						<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
 						<textarea class="autosize" id="description" name="description" rows="3">{{.description}}</textarea>

+ 2 - 2
templates/repo/header.tmpl

@@ -7,9 +7,9 @@
 					<div class="ui huge breadcrumb">
 						{{if .UseCustomAvatar}}
 							<img class="ui mini spaced image" src="{{.RelAvatarLink}}">
-							<i class="{{if .IsPrivate}}mega-octicon octicon-lock{{else if .IsMirror}}mega-octicon octicon-repo-clone{{else if .IsFork}}mega-octicon octicon-repo-forked{{end}}"></i>
+							<i class="{{if .IsPrivate}}mega-octicon octicon-lock{{else if .IsUnlisted}}mega-octicon octicon-eye{{else if .IsMirror}}mega-octicon octicon-repo-clone{{else if .IsFork}}mega-octicon octicon-repo-forked{{end}}"></i>
 						{{else}}
-							<i class="mega-octicon octicon-{{if .IsPrivate}}lock{{else if .IsMirror}}repo-clone{{else if .IsFork}}repo-forked{{else}}repo{{end}}"></i>
+							<i class="mega-octicon octicon-{{if .IsPrivate}}lock{{else if .IsUnlisted}}eye{{else if .IsMirror}}repo-clone{{else if .IsFork}}repo-forked{{else}}repo{{end}}"></i>
 						{{end}}
 						<a href="{{AppSubURL}}/{{.Owner.Name}}">{{.Owner.Name}}</a>
 						<div class="divider"> / </div>

+ 7 - 0
templates/repo/migrate.tmpl

@@ -80,6 +80,13 @@
 							{{end}}
 						</div>
 					</div>
+					<div class="inline field">
+						<label></label>
+						<div class="ui checkbox">
+							<input name="unlisted" type="checkbox">
+							<label>{{.i18n.Tr "repo.unlisted_helper" | Safe}}</label>
+						</div>
+					</div>
 					<div class="inline field">
 						<label>{{.i18n.Tr "repo.migrate_type"}}</label>
 						<div class="ui checkbox">

+ 7 - 0
templates/repo/pulls/fork.tmpl

@@ -49,6 +49,13 @@
 							<input type="checkbox" {{if .IsPrivate}}checked{{end}}>
 							<label>{{.i18n.Tr "repo.visiblity_helper" | Safe}}</label>
 						</div>
+					</div>
+					<div class="inline field">
+						<label></label>
+						<div class="ui read-only checkbox">
+							<input type="checkbox" {{if .IsUnlisted}}checked{{end}}>
+							<label>{{.i18n.Tr "repo.unlisted_helper" | Safe}}</label>
+						</div>
 						<span class="help">{{.i18n.Tr "repo.fork_visiblity_helper"}}</span>
 					</div>
 					<div class="inline field {{if .Err_Description}}error{{end}}">

+ 13 - 3
templates/repo/settings/options.tmpl

@@ -31,10 +31,20 @@
 						{{if not .Repository.IsFork}}
 							<div class="inline field">
 								<label>{{.i18n.Tr "repo.visibility"}}</label>
-								<div class="ui checkbox">
-									<input name="private" type="checkbox" {{if .Repository.IsPrivate}}checked{{end}}>
-									<label>{{.i18n.Tr "repo.visiblity_helper" | Safe}} {{if .Repository.NumForks}}<span class="text red">{{.i18n.Tr "repo.visiblity_fork_helper"}}</span>{{end}}</label>
+							<div class="ui segment">
+								<div class="field">
+									<div class="ui checkbox">
+										<input name="private" type="checkbox" {{if .Repository.IsPrivate}}checked{{end}}>
+										<label>{{.i18n.Tr "repo.visiblity_helper" | Safe}} {{if .Repository.NumForks}}<span class="text red">{{.i18n.Tr "repo.visiblity_fork_helper"}}</span>{{end}}</label>
+									</div>
 								</div>
+								<div class="field">
+									<div class="ui checkbox">
+										<input name="unlisted" type="checkbox" {{if .Repository.IsUnlisted}}checked{{end}}>
+										<label>{{.i18n.Tr "repo.unlisted_helper" | Safe}} {{if .Repository.NumForks}}<span class="text red">{{.i18n.Tr "repo.visiblity_fork_helper"}}</span>{{end}}</label>
+									</div>
+								</div>
+							</div>
 							</div>
 						{{end}}
 

Неке датотеке нису приказане због велике количине промена