2016-11-04 01:16:01 +03:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2019-04-19 15:17:27 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2016-11-04 01:16:01 +03:00
package git
import (
2024-08-12 18:16:55 +03:00
"bufio"
2019-11-15 05:52:59 +03:00
"bytes"
2019-04-17 19:06:35 +03:00
"encoding/base64"
2016-11-04 01:16:01 +03:00
"io"
2021-06-05 15:32:19 +03:00
2024-08-12 18:16:55 +03:00
"code.gitea.io/gitea/modules/log"
2021-06-05 15:32:19 +03:00
"code.gitea.io/gitea/modules/typesniffer"
2021-10-25 00:12:43 +03:00
"code.gitea.io/gitea/modules/util"
2016-11-04 01:16:01 +03:00
)
2024-08-12 18:16:55 +03:00
// Blob represents a Git object.
type Blob struct {
ID ObjectID
gotSize bool
size int64
name string
repo * Repository
}
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
// Calling the Close function on the result will discard all unread output.
func ( b * Blob ) DataAsync ( ) ( io . ReadCloser , error ) {
2024-08-20 20:04:57 +03:00
wr , rd , cancel , err := b . repo . CatFileBatch ( b . repo . Ctx )
if err != nil {
return nil , err
}
2024-08-12 18:16:55 +03:00
2024-08-20 20:04:57 +03:00
_ , err = wr . Write ( [ ] byte ( b . ID . String ( ) + "\n" ) )
2024-08-12 18:16:55 +03:00
if err != nil {
cancel ( )
return nil , err
}
_ , _ , size , err := ReadBatchLine ( rd )
if err != nil {
cancel ( )
return nil , err
}
b . gotSize = true
b . size = size
if size < 4096 {
bs , err := io . ReadAll ( io . LimitReader ( rd , size ) )
defer cancel ( )
if err != nil {
return nil , err
}
_ , err = rd . Discard ( 1 )
return io . NopCloser ( bytes . NewReader ( bs ) ) , err
}
return & blobReader {
rd : rd ,
n : size ,
cancel : cancel ,
} , nil
}
// Size returns the uncompressed size of the blob
func ( b * Blob ) Size ( ) int64 {
if b . gotSize {
return b . size
}
2024-08-20 20:04:57 +03:00
wr , rd , cancel , err := b . repo . CatFileBatchCheck ( b . repo . Ctx )
if err != nil {
log . Debug ( "error whilst reading size for %s in %s. Error: %v" , b . ID . String ( ) , b . repo . Path , err )
return 0
}
2024-08-12 18:16:55 +03:00
defer cancel ( )
2024-08-20 20:04:57 +03:00
_ , err = wr . Write ( [ ] byte ( b . ID . String ( ) + "\n" ) )
2024-08-12 18:16:55 +03:00
if err != nil {
log . Debug ( "error whilst reading size for %s in %s. Error: %v" , b . ID . String ( ) , b . repo . Path , err )
return 0
}
_ , _ , b . size , err = ReadBatchLine ( rd )
if err != nil {
log . Debug ( "error whilst reading size for %s in %s. Error: %v" , b . ID . String ( ) , b . repo . Path , err )
return 0
}
b . gotSize = true
return b . size
}
type blobReader struct {
rd * bufio . Reader
n int64
cancel func ( )
}
func ( b * blobReader ) Read ( p [ ] byte ) ( n int , err error ) {
if b . n <= 0 {
return 0 , io . EOF
}
if int64 ( len ( p ) ) > b . n {
p = p [ 0 : b . n ]
}
n , err = b . rd . Read ( p )
b . n -= int64 ( n )
return n , err
}
// Close implements io.Closer
func ( b * blobReader ) Close ( ) error {
if b . rd == nil {
return nil
}
defer b . cancel ( )
if err := DiscardFull ( b . rd , b . n + 1 ) ; err != nil {
return err
}
b . rd = nil
return nil
}
2017-11-29 04:50:39 +03:00
2019-04-19 15:17:27 +03:00
// Name returns name of the tree entry this blob object was created from (or empty string)
func ( b * Blob ) Name ( ) string {
return b . name
2017-11-29 04:50:39 +03:00
}
2019-04-17 19:06:35 +03:00
2023-06-13 12:02:25 +03:00
// GetBlobContent Gets the limited content of the blob as raw text
func ( b * Blob ) GetBlobContent ( limit int64 ) ( string , error ) {
if limit <= 0 {
return "" , nil
}
2019-06-29 23:51:10 +03:00
dataRc , err := b . DataAsync ( )
if err != nil {
return "" , err
}
defer dataRc . Close ( )
2023-06-13 12:02:25 +03:00
buf , err := util . ReadWithLimit ( dataRc , int ( limit ) )
return string ( buf ) , err
2019-06-29 23:51:10 +03:00
}
2021-06-24 18:47:46 +03:00
// GetBlobLineCount gets line count of the blob
2019-11-15 05:52:59 +03:00
func ( b * Blob ) GetBlobLineCount ( ) ( int , error ) {
reader , err := b . DataAsync ( )
if err != nil {
return 0 , err
}
defer reader . Close ( )
buf := make ( [ ] byte , 32 * 1024 )
2021-06-24 18:47:46 +03:00
count := 1
2019-11-15 05:52:59 +03:00
lineSep := [ ] byte { '\n' }
2021-06-24 18:47:46 +03:00
c , err := reader . Read ( buf )
if c == 0 && err == io . EOF {
return 0 , nil
}
2019-11-15 05:52:59 +03:00
for {
count += bytes . Count ( buf [ : c ] , lineSep )
switch {
case err == io . EOF :
return count , nil
case err != nil :
return count , err
}
2021-06-24 18:47:46 +03:00
c , err = reader . Read ( buf )
2019-11-15 05:52:59 +03:00
}
}
2019-04-17 19:06:35 +03:00
// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string
func ( b * Blob ) GetBlobContentBase64 ( ) ( string , error ) {
dataRc , err := b . DataAsync ( )
if err != nil {
return "" , err
}
defer dataRc . Close ( )
pr , pw := io . Pipe ( )
encoder := base64 . NewEncoder ( base64 . StdEncoding , pw )
go func ( ) {
_ , err := io . Copy ( encoder , dataRc )
2019-06-12 22:41:28 +03:00
_ = encoder . Close ( )
2019-04-17 19:06:35 +03:00
if err != nil {
2019-06-12 22:41:28 +03:00
_ = pw . CloseWithError ( err )
2019-04-17 19:06:35 +03:00
} else {
2019-06-12 22:41:28 +03:00
_ = pw . Close ( )
2019-04-17 19:06:35 +03:00
}
} ( )
2021-09-22 08:38:34 +03:00
out , err := io . ReadAll ( pr )
2019-04-17 19:06:35 +03:00
if err != nil {
return "" , err
}
return string ( out ) , nil
}
2021-06-05 15:32:19 +03:00
// GuessContentType guesses the content type of the blob.
func ( b * Blob ) GuessContentType ( ) ( typesniffer . SniffedType , error ) {
r , err := b . DataAsync ( )
if err != nil {
return typesniffer . SniffedType { } , err
}
defer r . Close ( )
return typesniffer . DetectContentTypeFromReader ( r )
}
2024-08-12 18:16:55 +03:00
// GetBlob finds the blob object in the repository.
func ( repo * Repository ) GetBlob ( idStr string ) ( * Blob , error ) {
id , err := NewIDFromString ( idStr )
if err != nil {
return nil , err
}
if id . IsZero ( ) {
return nil , ErrNotExist { id . String ( ) , "" }
}
return & Blob {
ID : id ,
repo : repo ,
} , nil
}