Skip to content

Commit 0b51072

Browse files
kolaentetechknowlogick
authored andcommitted
Feature: Archive repos (#5009)
1 parent 6ad834e commit 0b51072

30 files changed

+436
-243
lines changed

models/repo.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,9 @@ type Repository struct {
186186
NumOpenMilestones int `xorm:"-"`
187187
NumReleases int `xorm:"-"`
188188

189-
IsPrivate bool `xorm:"INDEX"`
190-
IsEmpty bool `xorm:"INDEX"`
189+
IsPrivate bool `xorm:"INDEX"`
190+
IsEmpty bool `xorm:"INDEX"`
191+
IsArchived bool `xorm:"INDEX"`
191192

192193
IsMirror bool `xorm:"INDEX"`
193194
*Mirror `xorm:"-"`
@@ -292,6 +293,7 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
292293
Description: repo.Description,
293294
Private: repo.IsPrivate,
294295
Empty: repo.IsEmpty,
296+
Archived: repo.IsArchived,
295297
Size: int(repo.Size / 1024),
296298
Fork: repo.IsFork,
297299
Parent: parent,
@@ -2341,6 +2343,13 @@ func CheckRepoStats() {
23412343
// ***** END: Repository.NumForks *****
23422344
}
23432345

2346+
// SetArchiveRepoState sets if a repo is archived
2347+
func (repo *Repository) SetArchiveRepoState(isArchived bool) (err error) {
2348+
repo.IsArchived = isArchived
2349+
_, err = x.Where("id = ?", repo.ID).Cols("is_archived").Update(repo)
2350+
return
2351+
}
2352+
23442353
// ___________ __
23452354
// \_ _____/__________| | __
23462355
// | __)/ _ \_ __ \ |/ /

modules/auth/repo_form.go

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ type RepoSettingForm struct {
117117
EnableTimetracker bool
118118
AllowOnlyContributorsToTrackTime bool
119119
EnableIssueDependencies bool
120+
IsArchived bool
120121

121122
// Admin settings
122123
EnableHealthCheck bool

modules/context/repo.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,23 @@ type Repository struct {
5656

5757
// CanEnableEditor returns true if repository is editable and user has proper access level.
5858
func (r *Repository) CanEnableEditor() bool {
59-
return r.Permission.CanWrite(models.UnitTypeCode) && r.Repository.CanEnableEditor() && r.IsViewBranch
59+
return r.Permission.CanWrite(models.UnitTypeCode) && r.Repository.CanEnableEditor() && r.IsViewBranch && !r.Repository.IsArchived
6060
}
6161

6262
// CanCreateBranch returns true if repository is editable and user has proper access level.
6363
func (r *Repository) CanCreateBranch() bool {
6464
return r.Permission.CanWrite(models.UnitTypeCode) && r.Repository.CanCreateBranch()
6565
}
6666

67+
// RepoMustNotBeArchived checks if a repo is archived
68+
func RepoMustNotBeArchived() macaron.Handler {
69+
return func(ctx *Context) {
70+
if ctx.Repo.Repository.IsArchived {
71+
ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title")))
72+
}
73+
}
74+
}
75+
6776
// CanCommitToBranch returns true if repository is editable and user has proper access level
6877
// and branch is not protected for push
6978
func (r *Repository) CanCommitToBranch(doer *models.User) (bool, error) {

options/locale/locale_en-US.ini

+16
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,10 @@ forks = Forks
542542
pick_reaction = Pick your reaction
543543
reactions_more = and %d more
544544
545+
archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
546+
archive.issue.nocomment = This repo is archived. You cannot comment on issues.
547+
archive.pull.nocomment = This repo is archived. You cannot comment on pull requests.
548+
545549
form.reach_limit_of_creation = You have already reached your limit of %d repositories.
546550
form.name_reserved = The repository name '%s' is reserved.
547551
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
@@ -1176,6 +1180,18 @@ settings.choose_branch = Choose a branch…
11761180
settings.no_protected_branch = There are no protected branches.
11771181
settings.edit_protected_branch = Edit
11781182
settings.protected_branch_required_approvals_min = Required approvals cannot be negative.
1183+
settings.archive.button = Archive Repo
1184+
settings.archive.header = Archive This Repo
1185+
settings.archive.text = Archiving the repo will make it entirely read-only. It is hidden from the dashboard, cannot be committed to and no issues or pull-requests can be created.
1186+
settings.archive.success = The repo was successfully archived.
1187+
settings.archive.error = An error occured while trying to archive the repo. See the log for more details.
1188+
settings.archive.error_ismirror = You cannot archive a mirrored repo.
1189+
settings.archive.branchsettings_unavailable = Branch settings are not available if the repo is archived.
1190+
settings.unarchive.button = Un-Archive Repo
1191+
settings.unarchive.header = Un-Archive This Repo
1192+
settings.unarchive.text = Un-Archiving the repo will restore its ability to recieve commits and pushes, as well as new issues and pull-requests.
1193+
settings.unarchive.success = The repo was successfully un-archived.
1194+
settings.unarchive.error = An error occured while trying to un-archive the repo. See the log for more details.
11791195
11801196
diff.browse_source = Browse Source
11811197
diff.parent = parent

public/css/index.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/less/_base.less

+8
Original file line numberDiff line numberDiff line change
@@ -629,3 +629,11 @@ footer {
629629
.heatmap-color-5 {
630630
background-color: #2f6b1b;
631631
}
632+
633+
.archived-icon{
634+
color: lighten(#000, 70%) !important;
635+
}
636+
637+
.archived-icon{
638+
color: lighten(#000, 70%) !important;
639+
}

routers/repo/http.go

+6
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ func HTTP(ctx *context.Context) {
9595
return
9696
}
9797

98+
// Don't allow pushing if the repo is archived
99+
if repo.IsArchived && !isPull {
100+
ctx.HandleText(http.StatusForbidden, "This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.")
101+
return
102+
}
103+
98104
// Only public pull don't need auth.
99105
isPublicPull := !repo.IsPrivate && isPull
100106
var (

routers/repo/release.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func Releases(ctx *context.Context) {
6767
}
6868

6969
writeAccess := ctx.Repo.CanWrite(models.UnitTypeReleases)
70-
ctx.Data["CanCreateRelease"] = writeAccess
70+
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
7171

7272
opts := models.FindReleasesOptions{
7373
IncludeDrafts: writeAccess,

routers/repo/setting.go

+41
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,47 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
354354
ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
355355
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
356356

357+
case "archive":
358+
if !ctx.Repo.IsOwner() {
359+
ctx.Error(403)
360+
return
361+
}
362+
363+
if repo.IsMirror {
364+
ctx.Flash.Error(ctx.Tr("repo.settings.archive.error_ismirror"))
365+
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
366+
return
367+
}
368+
369+
if err := repo.SetArchiveRepoState(true); err != nil {
370+
log.Error(4, "Tried to archive a repo: %s", err)
371+
ctx.Flash.Error(ctx.Tr("repo.settings.archive.error"))
372+
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
373+
return
374+
}
375+
376+
ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
377+
378+
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
379+
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
380+
case "unarchive":
381+
if !ctx.Repo.IsOwner() {
382+
ctx.Error(403)
383+
return
384+
}
385+
386+
if err := repo.SetArchiveRepoState(false); err != nil {
387+
log.Error(4, "Tried to unarchive a repo: %s", err)
388+
ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error"))
389+
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
390+
return
391+
}
392+
393+
ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
394+
395+
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
396+
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
397+
357398
default:
358399
ctx.NotFound("", nil)
359400
}

routers/repo/view.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ func renderDirectory(ctx *context.Context, treeLink string) {
151151

152152
// Check permission to add or upload new file.
153153
if ctx.Repo.CanWrite(models.UnitTypeCode) && ctx.Repo.IsViewBranch {
154-
ctx.Data["CanAddFile"] = true
155-
ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled
154+
ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived
155+
ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived
156156
}
157157
}
158158

routers/repo/wiki.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *gi
203203
// Wiki renders single wiki page
204204
func Wiki(ctx *context.Context) {
205205
ctx.Data["PageIsWiki"] = true
206-
ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki)
206+
ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) && !ctx.Repo.Repository.IsArchived
207207

208208
if !ctx.Repo.Repository.HasWiki() {
209209
ctx.Data["Title"] = ctx.Tr("repo.wiki")
@@ -246,7 +246,7 @@ func WikiPages(ctx *context.Context) {
246246

247247
ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
248248
ctx.Data["PageIsWiki"] = true
249-
ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki)
249+
ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) && !ctx.Repo.Repository.IsArchived
250250

251251
wikiRepo, commit, err := findWikiRepoCommit(ctx)
252252
if err != nil {

routers/routes/routes.go

+17-17
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ func RegisterRoutes(m *macaron.Macaron) {
492492
m.Group("/branches", func() {
493493
m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
494494
m.Combo("/*").Get(repo.SettingsProtectedBranch).
495-
Post(bindIgnErr(auth.ProtectBranchForm{}), repo.SettingsProtectedBranchPost)
495+
Post(bindIgnErr(auth.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo.SettingsProtectedBranchPost)
496496
}, repo.MustBeNotEmpty)
497497

498498
m.Group("/hooks", func() {
@@ -530,13 +530,13 @@ func RegisterRoutes(m *macaron.Macaron) {
530530
})
531531
}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.UnitTypes(), context.RepoRef())
532532

533-
m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
533+
m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.RepoMustNotBeArchived(), repo.Action)
534534

535535
m.Group("/:username/:reponame", func() {
536536
m.Group("/issues", func() {
537537
m.Combo("/new").Get(context.RepoRef(), repo.NewIssue).
538538
Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
539-
}, reqRepoIssueReader)
539+
}, context.RepoMustNotBeArchived(), reqRepoIssueReader)
540540
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
541541
// So they can apply their own enable/disable logic on routers.
542542
m.Group("/issues", func() {
@@ -557,32 +557,32 @@ func RegisterRoutes(m *macaron.Macaron) {
557557
})
558558
})
559559
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
560-
})
560+
}, context.RepoMustNotBeArchived())
561561

562562
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
563563
m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone)
564564
m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee)
565565
m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
566-
})
566+
}, context.RepoMustNotBeArchived())
567567
m.Group("/comments/:id", func() {
568568
m.Post("", repo.UpdateCommentContent)
569569
m.Post("/delete", repo.DeleteComment)
570570
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction)
571-
})
571+
}, context.RepoMustNotBeArchived())
572572
m.Group("/labels", func() {
573573
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
574574
m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
575575
m.Post("/delete", repo.DeleteLabel)
576576
m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels)
577-
}, reqRepoIssuesOrPullsWriter, context.RepoRef())
577+
}, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef())
578578
m.Group("/milestones", func() {
579579
m.Combo("/new").Get(repo.NewMilestone).
580580
Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
581581
m.Get("/:id/edit", repo.EditMilestone)
582582
m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
583583
m.Get("/:id/:action", repo.ChangeMilestonStatus)
584584
m.Post("/delete", repo.DeleteMilestone)
585-
}, reqRepoIssuesOrPullsWriter, context.RepoRef())
585+
}, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef())
586586
m.Group("/milestone", func() {
587587
m.Get("/:id", repo.MilestoneIssuesAndPulls)
588588
}, reqRepoIssuesOrPullsWriter, context.RepoRef())
@@ -607,7 +607,7 @@ func RegisterRoutes(m *macaron.Macaron) {
607607
m.Post("/upload-file", repo.UploadFileToServer)
608608
m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
609609
}, context.RepoRef(), repo.MustBeEditable, repo.MustBeAbleToUpload)
610-
}, reqRepoCodeWriter, repo.MustBeNotEmpty)
610+
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
611611

612612
m.Group("/branches", func() {
613613
m.Group("/_new/", func() {
@@ -617,7 +617,7 @@ func RegisterRoutes(m *macaron.Macaron) {
617617
}, bindIgnErr(auth.NewBranchForm{}))
618618
m.Post("/delete", repo.DeleteBranchPost)
619619
m.Post("/restore", repo.RestoreBranchPost)
620-
}, reqRepoCodeWriter, repo.MustBeNotEmpty)
620+
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
621621

622622
}, reqSignIn, context.RepoAssignment(), context.UnitTypes())
623623

@@ -630,11 +630,11 @@ func RegisterRoutes(m *macaron.Macaron) {
630630
m.Get("/new", repo.NewRelease)
631631
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
632632
m.Post("/delete", repo.DeleteRelease)
633-
}, reqSignIn, repo.MustBeNotEmpty, reqRepoReleaseWriter, context.RepoRef())
633+
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef())
634634
m.Group("/releases", func() {
635635
m.Get("/edit/*", repo.EditRelease)
636636
m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
637-
}, reqSignIn, repo.MustBeNotEmpty, reqRepoReleaseWriter, func(ctx *context.Context) {
637+
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, func(ctx *context.Context) {
638638
var err error
639639
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
640640
if err != nil {
@@ -652,7 +652,7 @@ func RegisterRoutes(m *macaron.Macaron) {
652652

653653
m.Group("/:username/:reponame", func() {
654654
m.Post("/topics", repo.TopicsPost)
655-
}, context.RepoAssignment(), reqRepoAdmin)
655+
}, context.RepoMustNotBeArchived(), context.RepoAssignment(), reqRepoAdmin)
656656

657657
m.Group("/:username/:reponame", func() {
658658
m.Group("", func() {
@@ -672,7 +672,7 @@ func RegisterRoutes(m *macaron.Macaron) {
672672
m.Combo("/:page/_edit").Get(repo.EditWiki).
673673
Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost)
674674
m.Post("/:page/delete", repo.DeleteWikiPagePost)
675-
}, reqSignIn, reqRepoWikiWriter)
675+
}, context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter)
676676
}, repo.MustEnableWiki, context.RepoRef())
677677

678678
m.Group("/wiki", func() {
@@ -694,14 +694,14 @@ func RegisterRoutes(m *macaron.Macaron) {
694694
m.Get(".diff", repo.DownloadPullDiff)
695695
m.Get(".patch", repo.DownloadPullPatch)
696696
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
697-
m.Post("/merge", reqRepoPullsWriter, bindIgnErr(auth.MergePullRequestForm{}), repo.MergePullRequest)
698-
m.Post("/cleanup", context.RepoRef(), repo.CleanUpPullRequest)
697+
m.Post("/merge", context.RepoMustNotBeArchived(), reqRepoPullsWriter, bindIgnErr(auth.MergePullRequestForm{}), repo.MergePullRequest)
698+
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
699699
m.Group("/files", func() {
700700
m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles)
701701
m.Group("/reviews", func() {
702702
m.Post("/comments", bindIgnErr(auth.CodeCommentForm{}), repo.CreateCodeComment)
703703
m.Post("/submit", bindIgnErr(auth.SubmitReviewForm{}), repo.SubmitReview)
704-
})
704+
}, context.RepoMustNotBeArchived())
705705
})
706706
}, repo.MustAllowPulls)
707707

templates/explore/repo_list.tmpl

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
{{range .Repos}}
33
<div class="item">
44
<div class="ui header">
5-
<a class="name" href="{{.Link}}">{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}</a>
5+
<a class="name" href="{{.Link}}">
6+
{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}
7+
{{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}}
8+
</a>
69
{{if .IsPrivate}}
710
<span class="text gold"><i class="octicon octicon-lock"></i></span>
811
{{else if .IsFork}}

0 commit comments

Comments
 (0)