Skip to content

Commit c8d1c38

Browse files
authored
Render READMEs in docs/ .gitea or .github from root (#10361)
* Render READMEs in docs/ .gitea or .github from root
1 parent 6b01972 commit c8d1c38

File tree

7 files changed

+214
-12
lines changed

7 files changed

+214
-12
lines changed

integrations/sqlite.ini

-1
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,3 @@ INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTI3OTU5ODN9.O
8080

8181
[oauth2]
8282
JWT_SECRET = KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko
83-

modules/git/error.go

+6
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ func (err ErrBadLink) Error() string {
5050
return fmt.Sprintf("%s: %s", err.Name, err.Message)
5151
}
5252

53+
// IsErrBadLink if some error is ErrBadLink
54+
func IsErrBadLink(err error) bool {
55+
_, ok := err.(ErrBadLink)
56+
return ok
57+
}
58+
5359
// ErrUnsupportedVersion error when required git version not matched
5460
type ErrUnsupportedVersion struct {
5561
Required string

modules/git/tree_entry.go

+32
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,38 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
167167
return target, nil
168168
}
169169

170+
// FollowLinks returns the entry ultimately pointed to by a symlink
171+
func (te *TreeEntry) FollowLinks() (*TreeEntry, error) {
172+
if !te.IsLink() {
173+
return nil, ErrBadLink{te.Name(), "not a symlink"}
174+
}
175+
entry := te
176+
for i := 0; i < 999; i++ {
177+
if entry.IsLink() {
178+
next, err := entry.FollowLink()
179+
if err != nil {
180+
return nil, err
181+
}
182+
if next.ID == entry.ID {
183+
return nil, ErrBadLink{
184+
entry.Name(),
185+
"recursive link",
186+
}
187+
}
188+
entry = next
189+
} else {
190+
break
191+
}
192+
}
193+
if entry.IsLink() {
194+
return nil, ErrBadLink{
195+
te.Name(),
196+
"too many levels of symbolic links",
197+
}
198+
}
199+
return entry, nil
200+
}
201+
170202
// GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory )
171203
func (te *TreeEntry) GetSubJumpablePathName() string {
172204
if te.IsSubModule() || !te.IsDir() {

options/locale/locale_en-US.ini

+1
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@ file_too_large = The file is too large to be shown.
732732
video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag.
733733
audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag.
734734
stored_lfs = Stored with Git LFS
735+
symbolic_link = Symbolic link
735736
commit_graph = Commit Graph
736737
blame = Blame
737738
normal_view = Normal View

routers/repo/view.go

+157-10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,83 @@ const (
3636
tplMigrating base.TplName = "repo/migrating"
3737
)
3838

39+
type namedBlob struct {
40+
name string
41+
isSymlink bool
42+
blob *git.Blob
43+
}
44+
45+
// FIXME: There has to be a more efficient way of doing this
46+
func getReadmeFileFromPath(commit *git.Commit, treePath string) (*namedBlob, error) {
47+
tree, err := commit.SubTree(treePath)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
entries, err := tree.ListEntries()
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
var readmeFiles [4]*namedBlob
58+
var exts = []string{".md", ".txt", ""} // sorted by priority
59+
for _, entry := range entries {
60+
if entry.IsDir() {
61+
continue
62+
}
63+
for i, ext := range exts {
64+
if markup.IsReadmeFile(entry.Name(), ext) {
65+
if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].name, entry.Blob().Name()) {
66+
name := entry.Name()
67+
isSymlink := entry.IsLink()
68+
target := entry
69+
if isSymlink {
70+
target, err = entry.FollowLinks()
71+
if err != nil && !git.IsErrBadLink(err) {
72+
return nil, err
73+
}
74+
}
75+
if target != nil && (target.IsExecutable() || target.IsRegular()) {
76+
readmeFiles[i] = &namedBlob{
77+
name,
78+
isSymlink,
79+
target.Blob(),
80+
}
81+
}
82+
}
83+
}
84+
}
85+
86+
if markup.IsReadmeFile(entry.Name()) {
87+
if readmeFiles[3] == nil || base.NaturalSortLess(readmeFiles[3].name, entry.Blob().Name()) {
88+
name := entry.Name()
89+
isSymlink := entry.IsLink()
90+
if isSymlink {
91+
entry, err = entry.FollowLinks()
92+
if err != nil && !git.IsErrBadLink(err) {
93+
return nil, err
94+
}
95+
}
96+
if entry != nil && (entry.IsExecutable() || entry.IsRegular()) {
97+
readmeFiles[3] = &namedBlob{
98+
name,
99+
isSymlink,
100+
entry.Blob(),
101+
}
102+
}
103+
}
104+
}
105+
}
106+
var readmeFile *namedBlob
107+
for _, f := range readmeFiles {
108+
if f != nil {
109+
readmeFile = f
110+
break
111+
}
112+
}
113+
return readmeFile, nil
114+
}
115+
39116
func renderDirectory(ctx *context.Context, treeLink string) {
40117
tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
41118
if err != nil {
@@ -65,38 +142,107 @@ func renderDirectory(ctx *context.Context, treeLink string) {
65142
// 3 for the extensions in exts[] in order
66143
// the last one is for a readme that doesn't
67144
// strictly match an extension
68-
var readmeFiles [4]*git.Blob
145+
var readmeFiles [4]*namedBlob
146+
var docsEntries [3]*git.TreeEntry
69147
var exts = []string{".md", ".txt", ""} // sorted by priority
70148
for _, entry := range entries {
71149
if entry.IsDir() {
150+
lowerName := strings.ToLower(entry.Name())
151+
switch lowerName {
152+
case "docs":
153+
if entry.Name() == "docs" || docsEntries[0] == nil {
154+
docsEntries[0] = entry
155+
}
156+
case ".gitea":
157+
if entry.Name() == ".gitea" || docsEntries[1] == nil {
158+
docsEntries[1] = entry
159+
}
160+
case ".github":
161+
if entry.Name() == ".github" || docsEntries[2] == nil {
162+
docsEntries[2] = entry
163+
}
164+
}
72165
continue
73166
}
74167

75168
for i, ext := range exts {
76169
if markup.IsReadmeFile(entry.Name(), ext) {
77-
readmeFiles[i] = entry.Blob()
170+
log.Debug("%s", entry.Name())
171+
name := entry.Name()
172+
isSymlink := entry.IsLink()
173+
target := entry
174+
if isSymlink {
175+
target, err = entry.FollowLinks()
176+
if err != nil && !git.IsErrBadLink(err) {
177+
ctx.ServerError("FollowLinks", err)
178+
return
179+
}
180+
}
181+
log.Debug("%t", target == nil)
182+
if target != nil && (target.IsExecutable() || target.IsRegular()) {
183+
readmeFiles[i] = &namedBlob{
184+
name,
185+
isSymlink,
186+
target.Blob(),
187+
}
188+
}
78189
}
79190
}
80191

81192
if markup.IsReadmeFile(entry.Name()) {
82-
readmeFiles[3] = entry.Blob()
193+
name := entry.Name()
194+
isSymlink := entry.IsLink()
195+
if isSymlink {
196+
entry, err = entry.FollowLinks()
197+
if err != nil && !git.IsErrBadLink(err) {
198+
ctx.ServerError("FollowLinks", err)
199+
return
200+
}
201+
}
202+
if entry != nil && (entry.IsExecutable() || entry.IsRegular()) {
203+
readmeFiles[3] = &namedBlob{
204+
name,
205+
isSymlink,
206+
entry.Blob(),
207+
}
208+
}
83209
}
84210
}
85211

86-
var readmeFile *git.Blob
212+
var readmeFile *namedBlob
213+
readmeTreelink := treeLink
87214
for _, f := range readmeFiles {
88215
if f != nil {
89216
readmeFile = f
90217
break
91218
}
92219
}
93220

221+
if ctx.Repo.TreePath == "" && readmeFile == nil {
222+
for _, entry := range docsEntries {
223+
if entry == nil {
224+
continue
225+
}
226+
readmeFile, err = getReadmeFileFromPath(ctx.Repo.Commit, entry.GetSubJumpablePathName())
227+
if err != nil {
228+
ctx.ServerError("getReadmeFileFromPath", err)
229+
return
230+
}
231+
if readmeFile != nil {
232+
readmeFile.name = entry.Name() + "/" + readmeFile.name
233+
readmeTreelink = treeLink + "/" + entry.GetSubJumpablePathName()
234+
break
235+
}
236+
}
237+
}
238+
94239
if readmeFile != nil {
95240
ctx.Data["RawFileLink"] = ""
96241
ctx.Data["ReadmeInList"] = true
97242
ctx.Data["ReadmeExist"] = true
243+
ctx.Data["FileIsSymlink"] = readmeFile.isSymlink
98244

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

110256
isTextFile := base.IsTextFile(buf)
111257
ctx.Data["FileIsText"] = isTextFile
112-
ctx.Data["FileName"] = readmeFile.Name()
258+
ctx.Data["FileName"] = readmeFile.name
113259
fileSize := int64(0)
114260
isLFSFile := false
115261
ctx.Data["IsLFSFile"] = false
@@ -151,13 +297,13 @@ func renderDirectory(ctx *context.Context, treeLink string) {
151297

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

159305
if !isLFSFile {
160-
fileSize = readmeFile.Size()
306+
fileSize = readmeFile.blob.Size()
161307
}
162308

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

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

220366
fileSize := blob.Size()
367+
ctx.Data["FileIsSymlink"] = entry.IsLink()
221368
ctx.Data["FileSize"] = fileSize
222369
ctx.Data["FileName"] = blob.Name()
223370
ctx.Data["HighlightClass"] = highlight.FileNameToHighlightClass(blob.Name())

templates/repo/view_file.tmpl

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,19 @@
22
<h4 class="file-header ui top attached header">
33
<div class="file-header-left">
44
{{if .ReadmeInList}}
5-
<i class="book icon"></i>
5+
{{if .FileIsSymlink}}
6+
<i class="icons"><i class="book icon"></i><i class="bottom left corner tiny inverted share icon"></i></i>
7+
{{else}}
8+
<i class="book icon"></i>
9+
{{end}}
610
<strong>{{.FileName}}</strong>
711
{{else}}
812
<div class="file-info text grey normal mono">
13+
{{if .FileIsSymlink}}
14+
<div class="file-info-entry">
15+
{{.i18n.Tr "repo.symbolic_link"}}
16+
</div>
17+
{{end}}
918
{{if .NumLinesSet}}
1019
<div class="file-info-entry">
1120
{{.NumLines}} {{.i18n.Tr (TrN .i18n.Lang .NumLines "repo.line" "repo.lines") }}

web_src/less/_repository.less

+8
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,14 @@
381381
font-size: 1em;
382382
}
383383

384+
.small.icon {
385+
font-size: 0.75em;
386+
}
387+
388+
.tiny.icon {
389+
font-size: 0.5em;
390+
}
391+
384392
.file-actions {
385393
margin-bottom: -5px;
386394

0 commit comments

Comments
 (0)