mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Render READMEs in docs/ .gitea or .github from root (#10361)
* Render READMEs in docs/ .gitea or .github from root
This commit is contained in:
		| @@ -80,4 +80,3 @@ INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTI3OTU5ODN9.O | ||||
|  | ||||
| [oauth2] | ||||
| JWT_SECRET = KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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() { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
| 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() { | ||||
| 			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 { | ||||
| @@ -65,25 +142,75 @@ 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 | ||||
| 	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 | ||||
| 		} | ||||
|  | ||||
| 		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 | ||||
| @@ -91,12 +218,31 @@ func renderDirectory(ctx *context.Context, treeLink string) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| @@ -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 | ||||
| @@ -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 { | ||||
| @@ -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( | ||||
| @@ -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()) | ||||
|   | ||||
| @@ -2,10 +2,19 @@ | ||||
| 	<h4 class="file-header ui top attached header"> | ||||
| 		<div class="file-header-left"> | ||||
| 			{{if .ReadmeInList}} | ||||
| 				{{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") }} | ||||
|   | ||||
| @@ -381,6 +381,14 @@ | ||||
|                     font-size: 1em; | ||||
|                 } | ||||
|  | ||||
|                 .small.icon { | ||||
|                     font-size: 0.75em; | ||||
|                 } | ||||
|  | ||||
|                 .tiny.icon { | ||||
|                     font-size: 0.5em; | ||||
|                 } | ||||
|  | ||||
|                 .file-actions { | ||||
|                     margin-bottom: -5px; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user