From 4884fe093fdee2c55e888af546ef4074c9e27ce7 Mon Sep 17 00:00:00 2001 From: hang Date: Tue, 16 Nov 2021 11:13:39 +0800 Subject: [PATCH] add: wikiPages --- modules/git/repo_commit.go | 13 ++++ modules/structs/wiki.go | 44 ++++++++++++ routers/api/v1/api.go | 9 +++ routers/api/v1/repo/wiki.go | 121 +++++++++++++++++++++++++++++++++ routers/api/v1/swagger/repo.go | 5 ++ routers/web/repo/wiki.go | 12 ++-- templates/swagger/v1_json.tmpl | 33 +++++++++ 7 files changed, 231 insertions(+), 6 deletions(-) create mode 100644 modules/structs/wiki.go create mode 100644 routers/api/v1/repo/wiki.go diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 16ee5b2fd..8efbb601f 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -200,6 +200,19 @@ func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) { return CommitsCountFiles(repo.Path, []string{revision}, []string{file}) } +// GetFirstAndLastCommitByPath returns the first commit and the last commit of relative path. +func (repo *Repository) GetFirstAndLastCommitByPath(revision, relpath string) (*Commit, *Commit, error) { + stdout, err := NewCommand("log", revision, prettyLogFormat, "--", relpath).RunInDirBytes(repo.Path) + if err != nil { + return nil, nil, err + } + commits, err := repo.parsePrettyFormatLogToList(stdout) + if err != nil { + return nil, nil, err + } + return commits.Front().Value.(*Commit), commits.Back().Value.(*Commit), nil +} + // CommitsByFileAndRange return the commits according revision file and the page func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (*list.List, error) { skip := (page - 1) * setting.Git.CommitsRangeSize diff --git a/modules/structs/wiki.go b/modules/structs/wiki.go new file mode 100644 index 000000000..a555c1de1 --- /dev/null +++ b/modules/structs/wiki.go @@ -0,0 +1,44 @@ +package structs + +type WikiesResponse struct { + WikiMeta + WikiCloneLink CloneLink `json:"wiki_clone_link"` +} + +type WikiMeta struct { + Name string `json:"name"` + Commit WikiCommit `json:"commit"` + FirstCommit WikiCommit `json:"-"` + //WikiCloneLink CloneLink `json:"wiki_clone_link"` +} + +type WikiCommit struct { + ID string `json:"id"` + Message string `json:"message"` + Author WikiUser `json:"author"` + Commiter WikiUser `json:"-"` +} + +type WikiUser struct { + Name string `json:"name"` + Email string `json:"email"` + When int64 `json:"when"` +} + +type WikiResponse struct { + WikiMeta + CommitCounts int64 `json:"commit_counts"` + MdContent string `json:"md_content"` + SimpleContent string `json:"simple_content"` + WikiCloneLink CloneLink `json:"wiki_clone_link"` +} + +type WikiOption struct { + Name string `json:"name"` + Content string `json:"content"` + CommitMessage string `json:"commit_message"` +} +type CloneLink struct { + SSH string `json:"ssh"` + HTTPS string `json:"https"` +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 62452f658..a866b2bc4 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -782,6 +782,15 @@ func Routes() *web.Route { m.Delete("", repo.DeleteBranchProtection) }) }, reqToken(), reqAdmin()) + m.Group("/wikies", func() { + m.Combo("").Get(repo.ListWikiPages) + // Post(bind(api.WikiOption{}), repo.CreateWiki) + // m.Group("/:page", func() { + // m.Combo("").Get(repo.GetWiki). + // Patch(bind(api.WikiOption{}), repo.EditWiki). + // Delete(repo.DeleteWiki) + // }) + }) m.Group("/tags", func() { m.Get("", repo.ListTags) m.Get("/*", repo.GetTag) diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go new file mode 100644 index 000000000..af95bbf51 --- /dev/null +++ b/routers/api/v1/repo/wiki.go @@ -0,0 +1,121 @@ +package repo + +import ( + "net/http" + "sort" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + webWiki "code.gitea.io/gitea/routers/web/repo" + wiki_service "code.gitea.io/gitea/services/wiki" +) + +func ListWikiPages(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/wikies repository repoWikiList + // --- + // summary: List the wikies in a repository + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/WikiList" + + if !ctx.Repo.Repository.HasWiki() { + return + } + + wikiCloneWiki := ctx.Repo.Repository.WikiCloneLink() + wikiRepo, commit, err := webWiki.FindWikiRepoCommit(ctx.Context) + if err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } + return + } + defer func() { + if wikiRepo != nil { + wikiRepo.Close() + } + }() + + entries, err := commit.ListEntries() + if err != nil { + ctx.ServerError("entries", err) + return + } + + pages := make([]api.WikiesResponse, 0, len(entries)) + for _, entry := range entries { + if !entry.IsRegular() { + continue + } + lastCommit, firstCommit, _ := wikiRepo.GetFirstAndLastCommitByPath("master", entry.Name()) + if err != nil { + ctx.ServerError("GetCommitByPath", err) + return + } + wikiName, err := wiki_service.FilenameToName(entry.Name()) + if err != nil { + if models.IsErrWikiInvalidFileName(err) { + continue + } + ctx.ServerError("FilenameToName", err) + return + } + pages = append(pages, api.WikiesResponse{ + WikiCloneLink: api.CloneLink{ + HTTPS: wikiCloneWiki.HTTPS, + SSH: wikiCloneWiki.SSH, + }, + WikiMeta: api.WikiMeta{ + Name: wikiName, + Commit: api.WikiCommit{ + Author: api.WikiUser{ + Name: lastCommit.Author.Name, + Email: lastCommit.Author.Email, + When: lastCommit.Author.When.Unix(), + }, + Commiter: api.WikiUser{ + Name: lastCommit.Committer.Name, + Email: lastCommit.Committer.Email, + When: lastCommit.Author.When.Unix(), + }, + ID: lastCommit.ID.String(), + Message: lastCommit.Message(), + }, + FirstCommit: api.WikiCommit{ + Author: api.WikiUser{ + Name: firstCommit.Author.Name, + Email: firstCommit.Author.Email, + When: firstCommit.Author.When.Unix(), + }, + Commiter: api.WikiUser{ + Name: firstCommit.Committer.Name, + Email: firstCommit.Committer.Email, + When: firstCommit.Author.When.Unix(), + }, + ID: firstCommit.ID.String(), + Message: firstCommit.Message(), + }, + }, + }) + } + //sort by time + sort.Slice(pages, func(i, j int) bool { + return pages[i].FirstCommit.Author.When > pages[j].FirstCommit.Author.When + }) + ctx.JSON(http.StatusOK, pages) + +} diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index d539bcb9f..230e49992 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -50,6 +50,11 @@ type swaggerResponseBranchProtectionList struct { Body []api.BranchProtection `json:"body"` } +type swaggerResponseWikiList struct { + // in:body + Body api.WikiesResponse `json:"body"` +} + // TagList // swagger:response TagList type swaggerResponseTagList struct { diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 523fc6ed0..3c2cc6b61 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -87,7 +87,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) return commit.GetTreeEntryByPath(unescapedTarget) } -func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) { +func FindWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) { wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath()) if err != nil { ctx.ServerError("OpenRepository", err) @@ -133,7 +133,7 @@ func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName strin } func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { - wikiRepo, commit, err := findWikiRepoCommit(ctx) + wikiRepo, commit, err := FindWikiRepoCommit(ctx) if err != nil { if wikiRepo != nil { wikiRepo.Close() @@ -263,7 +263,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { } func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { - wikiRepo, commit, err := findWikiRepoCommit(ctx) + wikiRepo, commit, err := FindWikiRepoCommit(ctx) if err != nil { if wikiRepo != nil { wikiRepo.Close() @@ -337,7 +337,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) } func renderEditPage(ctx *context.Context) { - wikiRepo, commit, err := findWikiRepoCommit(ctx) + wikiRepo, commit, err := FindWikiRepoCommit(ctx) if err != nil { if wikiRepo != nil { wikiRepo.Close() @@ -472,7 +472,7 @@ func WikiPages(ctx *context.Context) { ctx.Data["PageIsWiki"] = true ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) && !ctx.Repo.Repository.IsArchived - wikiRepo, commit, err := findWikiRepoCommit(ctx) + wikiRepo, commit, err := FindWikiRepoCommit(ctx) if err != nil { if wikiRepo != nil { wikiRepo.Close() @@ -521,7 +521,7 @@ func WikiPages(ctx *context.Context) { // WikiRaw outputs raw blob requested by user (image for example) func WikiRaw(ctx *context.Context) { - wikiRepo, commit, err := findWikiRepoCommit(ctx) + wikiRepo, commit, err := FindWikiRepoCommit(ctx) defer func() { if wikiRepo != nil { wikiRepo.Close() diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 68f25e0e5..3903b84cb 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -9777,6 +9777,39 @@ } } }, + "/repos/{owner}/{repo}/wikies": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "List the wikies in a repository", + "operationId": "repoWikiList", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/WikiList" + } + } + } + }, "/repos/{template_owner}/{template_repo}/generate": { "post": { "consumes": [