Files
forge/services/repository/files/upload.go
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

112 lines
2.5 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package files
import (
"context"
"fmt"
"os"
"path"
"sync"
repo_model "github.com/hanzoai/git/models/repo"
user_model "github.com/hanzoai/git/models/user"
"github.com/hanzoai/git/modules/log"
)
// UploadRepoFileOptions contains the uploaded repository file options
type UploadRepoFileOptions struct {
LastCommitID string
OldBranch string
NewBranch string
TreePath string
Message string
Files []string // In UUID format.
Signoff bool
Author *IdentityOptions
Committer *IdentityOptions
}
type lazyLocalFileReader struct {
*os.File
localFilename string
counter int
mu sync.Mutex
}
var _ LazyReadSeeker = (*lazyLocalFileReader)(nil)
func (l *lazyLocalFileReader) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
if l.counter > 0 {
l.counter--
if l.counter == 0 {
if err := l.File.Close(); err != nil {
return fmt.Errorf("close file %s: %w", l.localFilename, err)
}
l.File = nil
}
return nil
}
return fmt.Errorf("file %s already closed", l.localFilename)
}
func (l *lazyLocalFileReader) OpenLazyReader() error {
l.mu.Lock()
defer l.mu.Unlock()
if l.File != nil {
l.counter++
return nil
}
file, err := os.Open(l.localFilename)
if err != nil {
return err
}
l.File = file
l.counter = 1
return nil
}
// UploadRepoFiles uploads files to the given repository
func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *UploadRepoFileOptions) error {
if len(opts.Files) == 0 {
return nil
}
uploads, err := repo_model.GetUploadsByUUIDs(ctx, opts.Files)
if err != nil {
return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %w", opts.Files, err)
}
changeOpts := &ChangeRepoFilesOptions{
LastCommitID: opts.LastCommitID,
OldBranch: opts.OldBranch,
NewBranch: opts.NewBranch,
Message: opts.Message,
Signoff: opts.Signoff,
Author: opts.Author,
Committer: opts.Committer,
}
for _, upload := range uploads {
changeOpts.Files = append(changeOpts.Files, &ChangeRepoFile{
Operation: "upload",
TreePath: path.Join(opts.TreePath, upload.Name),
ContentReader: &lazyLocalFileReader{localFilename: upload.LocalPath()},
})
}
_, err = ChangeRepoFiles(ctx, repo, doer, changeOpts)
if err != nil {
return err
}
if err := repo_model.DeleteUploads(ctx, uploads...); err != nil {
log.Error("DeleteUploads: %v", err)
}
return nil
}