| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- package git
- /*
- blob.go
- Reading a file's content at a given revision.
- This backs the rich preview in GitApp: an image or PDF that changed is shown
- as a before/after pair, and the "before" side only exists inside the object
- database, so it has to be extracted rather than read off disk.
- */
- import (
- "errors"
- "regexp"
- "strings"
- "github.com/go-git/go-git/v5/plumbing"
- )
- // maxBlobBytes caps what may be pulled into memory for a preview. Anything
- // larger is refused so a stray multi-gigabyte asset cannot exhaust the server.
- const maxBlobBytes = 8 * 1024 * 1024
- // headRevision is the revision name meaning "the current HEAD commit".
- const headRevision = "HEAD"
- // commitHashPattern matches a full 40 character SHA-1, the only hash form
- // accepted from callers.
- var commitHashPattern = regexp.MustCompile(`^[0-9a-fA-F]{40}$`)
- /*
- FileBlob returns the content of a repository-relative path at a revision.
- revision is either "HEAD" (or empty, meaning the same) or a full commit hash.
- The second return value reports whether the path existed in that revision at
- all, which lets the caller distinguish "added in this change" from a genuine
- read failure.
- */
- func (m *Manager) FileBlob(realpath string, file string, revision string) ([]byte, bool, error) {
- cleaned, err := cleanRepoPath(file)
- if err != nil {
- return nil, false, err
- }
- repo, err := m.open(realpath)
- if err != nil {
- return nil, false, err
- }
- revision = strings.TrimSpace(revision)
- var hash plumbing.Hash
- if revision == "" || strings.EqualFold(revision, headRevision) {
- head, herr := repo.Head()
- if herr != nil {
- if errors.Is(herr, plumbing.ErrReferenceNotFound) {
- //Unborn branch: nothing has been committed yet
- return nil, false, nil
- }
- return nil, false, herr
- }
- hash = head.Hash()
- } else {
- if !commitHashPattern.MatchString(revision) {
- return nil, false, errors.New("not a commit hash: " + revision)
- }
- hash = plumbing.NewHash(revision)
- }
- commit, err := repo.CommitObject(hash)
- if err != nil {
- return nil, false, err
- }
- treeFile, err := commit.File(cleaned)
- if err != nil {
- //Absent from this revision, which is a normal answer rather than a fault
- return nil, false, nil
- }
- if treeFile.Size > maxBlobBytes {
- return nil, true, errors.New("file is too large to load")
- }
- content, err := treeFile.Contents()
- if err != nil {
- return nil, true, err
- }
- return []byte(content), true, nil
- }
- /*
- PreviewMimeType maps a file name onto the media type a browser needs to render
- it, or "" when the type is not something a browser displays.
- The table is explicit rather than delegating to mime.TypeByExtension: that
- consults the Windows registry and /etc/mime.types, so the same repository could
- otherwise preview differently on two machines.
- */
- func PreviewMimeType(path string) string {
- extension := strings.ToLower(path)
- if index := strings.LastIndex(extension, "."); index >= 0 {
- extension = extension[index:]
- } else {
- return ""
- }
- switch extension {
- //Images
- case ".png":
- return "image/png"
- case ".jpg", ".jpeg":
- return "image/jpeg"
- case ".gif":
- return "image/gif"
- case ".webp":
- return "image/webp"
- case ".bmp":
- return "image/bmp"
- case ".ico":
- return "image/x-icon"
- case ".svg":
- return "image/svg+xml"
- case ".avif":
- return "image/avif"
- //Documents
- case ".pdf":
- return "application/pdf"
- //Video
- case ".mp4", ".m4v":
- return "video/mp4"
- case ".webm":
- return "video/webm"
- case ".ogv":
- return "video/ogg"
- //Audio
- case ".mp3":
- return "audio/mpeg"
- case ".wav":
- return "audio/wav"
- case ".ogg", ".oga":
- return "audio/ogg"
- case ".flac":
- return "audio/flac"
- case ".m4a":
- return "audio/mp4"
- default:
- return ""
- }
- }
- // PreviewKind groups a media type into the element the front-end should use.
- // Returns "image", "pdf", "video", "audio" or "" when there is no preview.
- func PreviewKind(path string) string {
- mimeType := PreviewMimeType(path)
- switch {
- case mimeType == "":
- return ""
- case mimeType == "application/pdf":
- return "pdf"
- case strings.HasPrefix(mimeType, "image/"):
- return "image"
- case strings.HasPrefix(mimeType, "video/"):
- return "video"
- case strings.HasPrefix(mimeType, "audio/"):
- return "audio"
- default:
- return ""
- }
- }
|