diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 84b6ea40753e..c4085c0278f3 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -101,6 +101,15 @@ func (te *TreeEntry) FollowLinks() (*TreeEntry, error) { return entry, nil } +// returns the subtree, or nil if this is not a tree +func (te *TreeEntry) Tree() *Tree { + t, err := te.ptree.repo.getTree(te.ID) + if err != nil { + return nil + } + return t +} + // GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory ) func (te *TreeEntry) GetSubJumpablePathName() string { if te.IsSubModule() || !te.IsDir() { diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 70f2a941be83..ca86c744d115 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -56,18 +56,15 @@ type namedBlob struct { blob *git.Blob } +// locate a README for a tree in one of the supported paths. +// +// entries is passed to reduce calls to ListEntries(), so +// this has precondition: +// +// entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries() +// // FIXME: There has to be a more efficient way of doing this -func getReadmeFileFromPath(ctx *context.Context, commit *git.Commit, treePath string) (*namedBlob, error) { - tree, err := commit.SubTree(treePath) - if err != nil { - return nil, err - } - - entries, err := tree.ListEntries() - if err != nil { - return nil, err - } - +func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry) (*namedBlob, error) { // Create a list of extensions in priority order // 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md // 2. Txt files - e.g. README.txt @@ -75,16 +72,38 @@ func getReadmeFileFromPath(ctx *context.Context, commit *git.Commit, treePath st exts := append(localizedExtensions(".md", ctx.Language()), ".txt", "") // sorted by priority extCount := len(exts) readmeFiles := make([]*namedBlob, extCount+1) + + docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/) for _, entry := range entries { if entry.IsDir() { + // as a special case for the top-level repo introduction README, + // fall back to subfolders, looking for e.g. docs/README.md, .gitea/README.zh-CN.txt, .github/README.txt, ... + // (note that docsEntries is ignored unless we are at the root) + lowerName := strings.ToLower(entry.Name()) + switch lowerName { + case "docs": + if entry.Name() == "docs" || docsEntries[0] == nil { + docsEntries[0] = entry + } + case ".gitea": + if entry.Name() == ".gitea" || docsEntries[1] == nil { + docsEntries[1] = entry + } + case ".github": + if entry.Name() == ".github" || docsEntries[2] == nil { + docsEntries[2] = entry + } + } continue } if i, ok := markup.IsReadmeFileExtension(entry.Name(), exts...); ok { + log.Debug("Potential readme file: %s", entry.Name()) if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].name, entry.Blob().Name()) { name := entry.Name() isSymlink := entry.IsLink() target := entry if isSymlink { + var err error target, err = entry.FollowLinks() if err != nil && !git.IsErrBadLink(err) { return nil, err @@ -107,6 +126,33 @@ func getReadmeFileFromPath(ctx *context.Context, commit *git.Commit, treePath st break } } + + if ctx.Repo.TreePath == "" && readmeFile == nil { + for _, subTreeEntry := range docsEntries { + if subTreeEntry == nil { + continue + } + subTree := subTreeEntry.Tree() + if subTree == nil { + // this should be impossible; if subTreeEntry exists so should this. + continue + } + var err error + childEntries, err := subTree.ListEntries() + if err != nil { + return nil, err + } + readmeFile, err = findReadmeFileInEntries(ctx, childEntries) + if err != nil && !git.IsErrNotExist(err) { + return nil, err + } + if readmeFile != nil { + readmeFile.name = subTreeEntry.Name() + "/" + readmeFile.name + break + } + } + } + return readmeFile, nil } @@ -127,12 +173,20 @@ func renderDirectory(ctx *context.Context, treeLink string) { ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived } - readmeFile, readmeTreelink := findReadmeFile(ctx, entries, treeLink) - if ctx.Written() || readmeFile == nil { + if ctx.Written() { + return + } + + readmeFile, err := findReadmeFileInEntries(ctx, entries) + if err != nil { + ctx.ServerError("findReadmeFileInEntries", err) + return + } + if readmeFile == nil { return } - renderReadmeFile(ctx, readmeFile, readmeTreelink) + renderReadmeFile(ctx, readmeFile, treeLink) } // localizedExtensions prepends the provided language code with and without a @@ -157,89 +211,6 @@ func localizedExtensions(ext, languageCode string) (localizedExts []string) { return []string{lowerLangCode + ext, ext} } -func findReadmeFile(ctx *context.Context, entries git.Entries, treeLink string) (*namedBlob, string) { - // Create a list of extensions in priority order - // 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md - // 2. Txt files - e.g. README.txt - // 3. No extension - e.g. README - exts := append(localizedExtensions(".md", ctx.Language()), ".txt", "") // sorted by priority - extCount := len(exts) - readmeFiles := make([]*namedBlob, extCount+1) - - docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/) - for _, entry := range entries { - if entry.IsDir() { - lowerName := strings.ToLower(entry.Name()) - switch lowerName { - case "docs": - if entry.Name() == "docs" || docsEntries[0] == nil { - docsEntries[0] = entry - } - case ".gitea": - if entry.Name() == ".gitea" || docsEntries[1] == nil { - docsEntries[1] = entry - } - case ".github": - if entry.Name() == ".github" || docsEntries[2] == nil { - docsEntries[2] = entry - } - } - continue - } - - if i, ok := markup.IsReadmeFileExtension(entry.Name(), exts...); ok { - log.Debug("Potential readme file: %s", entry.Name()) - name := entry.Name() - isSymlink := entry.IsLink() - target := entry - if isSymlink { - var err error - target, err = entry.FollowLinks() - if err != nil && !git.IsErrBadLink(err) { - ctx.ServerError("FollowLinks", err) - return nil, "" - } - } - if target != nil && (target.IsExecutable() || target.IsRegular()) { - readmeFiles[i] = &namedBlob{ - name, - isSymlink, - target.Blob(), - } - } - } - } - - var readmeFile *namedBlob - readmeTreelink := treeLink - for _, f := range readmeFiles { - if f != nil { - readmeFile = f - break - } - } - - if ctx.Repo.TreePath == "" && readmeFile == nil { - for _, entry := range docsEntries { - if entry == nil { - continue - } - var err error - readmeFile, err = getReadmeFileFromPath(ctx, ctx.Repo.Commit, entry.GetSubJumpablePathName()) - if err != nil { - ctx.ServerError("getReadmeFileFromPath", err) - return nil, "" - } - if readmeFile != nil { - readmeFile.name = entry.Name() + "/" + readmeFile.name - readmeTreelink = treeLink + "/" + util.PathEscapeSegments(entry.GetSubJumpablePathName()) - break - } - } - } - return readmeFile, readmeTreelink -} - type fileInfo struct { isTextFile bool isLFSFile bool @@ -342,7 +313,7 @@ func renderReadmeFile(ctx *context.Context, readmeFile *namedBlob, readmeTreelin ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ Ctx: ctx, RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.name), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path). - URLPrefix: readmeTreelink, + URLPrefix: path.Dir(readmeTreelink), Metas: ctx.Repo.Repository.ComposeDocumentMetas(), GitRepo: ctx.Repo.GitRepo, }, rd)