Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render READMEs in docs/ .gitea or .github from root #10361

Merged
merged 9 commits into from
Feb 21, 2020
1 change: 0 additions & 1 deletion integrations/sqlite.ini
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,3 @@ INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTI3OTU5ODN9.O

[oauth2]
JWT_SECRET = KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko
zeripath marked this conversation as resolved.
Show resolved Hide resolved

6 changes: 6 additions & 0 deletions modules/git/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ func (err ErrBadLink) Error() string {
return fmt.Sprintf("%s: %s", err.Name, err.Message)
}

// IsErrBadLink if some error is ErrBadLink
func IsErrBadLink(err error) bool {
_, ok := err.(ErrBadLink)
return ok
}

// ErrUnsupportedVersion error when required git version not matched
type ErrUnsupportedVersion struct {
Required string
Expand Down
32 changes: 32 additions & 0 deletions modules/git/tree_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,38 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
return target, nil
}

// FollowLinks returns the entry ultimately pointed to by a symlink
func (te *TreeEntry) FollowLinks() (*TreeEntry, error) {
if !te.IsLink() {
return nil, ErrBadLink{te.Name(), "not a symlink"}
}
entry := te
for i := 0; i < 999; i++ {
if entry.IsLink() {
next, err := entry.FollowLink()
if err != nil {
return nil, err
}
if next.ID == entry.ID {
return nil, ErrBadLink{
entry.Name(),
"recursive link",
}
}
entry = next
} else {
break
}
}
if entry.IsLink() {
return nil, ErrBadLink{
te.Name(),
"too many levels of symbolic links",
}
}
return entry, nil
}

// GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory )
func (te *TreeEntry) GetSubJumpablePathName() string {
if te.IsSubModule() || !te.IsDir() {
Expand Down
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ file_too_large = The file is too large to be shown.
video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag.
audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag.
stored_lfs = Stored with Git LFS
symbolic_link = Symbolic link
commit_graph = Commit Graph
blame = Blame
normal_view = Normal View
Expand Down
167 changes: 157 additions & 10 deletions routers/repo/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,83 @@ const (
tplMigrating base.TplName = "repo/migrating"
)

type namedBlob struct {
name string
isSymlink bool
blob *git.Blob
}

// FIXME: There has to be a more efficient way of doing this
lafriks marked this conversation as resolved.
Show resolved Hide resolved
func getReadmeFileFromPath(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
}

var readmeFiles [4]*namedBlob
var exts = []string{".md", ".txt", ""} // sorted by priority
for _, entry := range entries {
if entry.IsDir() {
zeripath marked this conversation as resolved.
Show resolved Hide resolved
continue
}
for i, ext := range exts {
if markup.IsReadmeFile(entry.Name(), ext) {
if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].name, entry.Blob().Name()) {
name := entry.Name()
isSymlink := entry.IsLink()
target := entry
if isSymlink {
target, err = entry.FollowLinks()
if err != nil && !git.IsErrBadLink(err) {
return nil, err
}
}
if target != nil && (target.IsExecutable() || target.IsRegular()) {
readmeFiles[i] = &namedBlob{
name,
isSymlink,
target.Blob(),
}
}
}
}
}

if markup.IsReadmeFile(entry.Name()) {
if readmeFiles[3] == nil || base.NaturalSortLess(readmeFiles[3].name, entry.Blob().Name()) {
name := entry.Name()
isSymlink := entry.IsLink()
if isSymlink {
entry, err = entry.FollowLinks()
if err != nil && !git.IsErrBadLink(err) {
return nil, err
}
}
if entry != nil && (entry.IsExecutable() || entry.IsRegular()) {
readmeFiles[3] = &namedBlob{
name,
isSymlink,
entry.Blob(),
}
}
}
}
}
var readmeFile *namedBlob
for _, f := range readmeFiles {
if f != nil {
readmeFile = f
break
}
}
return readmeFile, nil
}

func renderDirectory(ctx *context.Context, treeLink string) {
tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
if err != nil {
Expand Down Expand Up @@ -65,38 +142,107 @@ func renderDirectory(ctx *context.Context, treeLink string) {
// 3 for the extensions in exts[] in order
// the last one is for a readme that doesn't
// strictly match an extension
var readmeFiles [4]*git.Blob
var readmeFiles [4]*namedBlob
var docsEntries [3]*git.TreeEntry
zeripath marked this conversation as resolved.
Show resolved Hide resolved
var exts = []string{".md", ".txt", ""} // sorted by priority
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
}

zeripath marked this conversation as resolved.
Show resolved Hide resolved
for i, ext := range exts {
if markup.IsReadmeFile(entry.Name(), ext) {
readmeFiles[i] = entry.Blob()
log.Debug("%s", entry.Name())
name := entry.Name()
isSymlink := entry.IsLink()
target := entry
if isSymlink {
target, err = entry.FollowLinks()
if err != nil && !git.IsErrBadLink(err) {
ctx.ServerError("FollowLinks", err)
return
}
}
log.Debug("%t", target == nil)
if target != nil && (target.IsExecutable() || target.IsRegular()) {
readmeFiles[i] = &namedBlob{
name,
isSymlink,
target.Blob(),
}
}
}
}

if markup.IsReadmeFile(entry.Name()) {
readmeFiles[3] = entry.Blob()
name := entry.Name()
isSymlink := entry.IsLink()
if isSymlink {
entry, err = entry.FollowLinks()
if err != nil && !git.IsErrBadLink(err) {
ctx.ServerError("FollowLinks", err)
return
}
}
if entry != nil && (entry.IsExecutable() || entry.IsRegular()) {
readmeFiles[3] = &namedBlob{
name,
isSymlink,
entry.Blob(),
}
}
}
}

var readmeFile *git.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
}
readmeFile, err = getReadmeFileFromPath(ctx.Repo.Commit, entry.GetSubJumpablePathName())
if err != nil {
ctx.ServerError("getReadmeFileFromPath", err)
return
}
if readmeFile != nil {
readmeFile.name = entry.Name() + "/" + readmeFile.name
readmeTreelink = treeLink + "/" + entry.GetSubJumpablePathName()
break
}
}
}

if readmeFile != nil {
ctx.Data["RawFileLink"] = ""
ctx.Data["ReadmeInList"] = true
ctx.Data["ReadmeExist"] = true
ctx.Data["FileIsSymlink"] = readmeFile.isSymlink

dataRc, err := readmeFile.DataAsync()
dataRc, err := readmeFile.blob.DataAsync()
if err != nil {
ctx.ServerError("Data", err)
return
Expand All @@ -109,7 +255,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {

isTextFile := base.IsTextFile(buf)
ctx.Data["FileIsText"] = isTextFile
ctx.Data["FileName"] = readmeFile.Name()
ctx.Data["FileName"] = readmeFile.name
fileSize := int64(0)
isLFSFile := false
ctx.Data["IsLFSFile"] = false
Expand Down Expand Up @@ -151,13 +297,13 @@ func renderDirectory(ctx *context.Context, treeLink string) {

fileSize = meta.Size
ctx.Data["FileSize"] = meta.Size
filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.Name()))
filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.name))
ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s.git/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), meta.Oid, filenameBase64)
}
}

if !isLFSFile {
fileSize = readmeFile.Size()
fileSize = readmeFile.blob.Size()
}

if isTextFile {
Expand All @@ -170,10 +316,10 @@ func renderDirectory(ctx *context.Context, treeLink string) {
d, _ := ioutil.ReadAll(dataRc)
buf = charset.ToUTF8WithFallback(append(buf, d...))

if markupType := markup.Type(readmeFile.Name()); markupType != "" {
if markupType := markup.Type(readmeFile.name); markupType != "" {
ctx.Data["IsMarkup"] = true
ctx.Data["MarkupType"] = string(markupType)
ctx.Data["FileContent"] = string(markup.Render(readmeFile.Name(), buf, treeLink, ctx.Repo.Repository.ComposeMetas()))
ctx.Data["FileContent"] = string(markup.Render(readmeFile.name, buf, readmeTreelink, ctx.Repo.Repository.ComposeMetas()))
} else {
ctx.Data["IsRenderedHTML"] = true
ctx.Data["FileContent"] = strings.Replace(
Expand Down Expand Up @@ -218,6 +364,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["Title"] = ctx.Data["Title"].(string) + " - " + ctx.Repo.TreePath + " at " + ctx.Repo.BranchName

fileSize := blob.Size()
ctx.Data["FileIsSymlink"] = entry.IsLink()
ctx.Data["FileSize"] = fileSize
ctx.Data["FileName"] = blob.Name()
ctx.Data["HighlightClass"] = highlight.FileNameToHighlightClass(blob.Name())
Expand Down
11 changes: 10 additions & 1 deletion templates/repo/view_file.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@
<h4 class="file-header ui top attached header">
<div class="file-header-left">
{{if .ReadmeInList}}
<i class="book icon"></i>
{{if .FileIsSymlink}}
<i class="icons"><i class="book icon"></i><i class="bottom left corner tiny inverted share icon"></i></i>
{{else}}
<i class="book icon"></i>
{{end}}
<strong>{{.FileName}}</strong>
{{else}}
<div class="file-info text grey normal mono">
{{if .FileIsSymlink}}
<div class="file-info-entry">
{{.i18n.Tr "repo.symbolic_link"}}
</div>
{{end}}
{{if .NumLinesSet}}
<div class="file-info-entry">
{{.NumLines}} {{.i18n.Tr (TrN .i18n.Lang .NumLines "repo.line" "repo.lines") }}
Expand Down
8 changes: 8 additions & 0 deletions web_src/less/_repository.less
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,14 @@
font-size: 1em;
}

.small.icon {
font-size: 0.75em;
}

.tiny.icon {
font-size: 0.5em;
}

.file-actions {
margin-bottom: -5px;

Expand Down