mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 01:34:27 +00:00 
			
		
		
		
	Refactor Git command functions to use WithXXX methods instead of exposing RunOpts. This change simplifies reuse across gitrepo and improves consistency, encapsulation, and maintainability of command options. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			217 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package attribute
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/modules/git"
 | 
						|
	"code.gitea.io/gitea/modules/git/gitcmd"
 | 
						|
	"code.gitea.io/gitea/modules/log"
 | 
						|
)
 | 
						|
 | 
						|
// BatchChecker provides a reader for check-attribute content that can be long running
 | 
						|
type BatchChecker struct {
 | 
						|
	attributesNum int
 | 
						|
	repo          *git.Repository
 | 
						|
	stdinWriter   *os.File
 | 
						|
	stdOut        *nulSeparatedAttributeWriter
 | 
						|
	ctx           context.Context
 | 
						|
	cancel        context.CancelFunc
 | 
						|
	cmd           *gitcmd.Command
 | 
						|
}
 | 
						|
 | 
						|
// NewBatchChecker creates a check attribute reader for the current repository and provided commit ID
 | 
						|
// If treeish is empty, then it will use current working directory, otherwise it will use the provided treeish on the bare repo
 | 
						|
func NewBatchChecker(repo *git.Repository, treeish string, attributes []string) (checker *BatchChecker, returnedErr error) {
 | 
						|
	ctx, cancel := context.WithCancel(repo.Ctx)
 | 
						|
	defer func() {
 | 
						|
		if returnedErr != nil {
 | 
						|
			cancel()
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	cmd, envs, cleanup, err := checkAttrCommand(repo, treeish, nil, attributes)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	defer func() {
 | 
						|
		if returnedErr != nil {
 | 
						|
			cleanup()
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	cmd.AddArguments("--stdin")
 | 
						|
 | 
						|
	checker = &BatchChecker{
 | 
						|
		attributesNum: len(attributes),
 | 
						|
		repo:          repo,
 | 
						|
		ctx:           ctx,
 | 
						|
		cmd:           cmd,
 | 
						|
		cancel: func() {
 | 
						|
			cancel()
 | 
						|
			cleanup()
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	stdinReader, stdinWriter, err := os.Pipe()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	checker.stdinWriter = stdinWriter
 | 
						|
 | 
						|
	lw := new(nulSeparatedAttributeWriter)
 | 
						|
	lw.attributes = make(chan attributeTriple, len(attributes))
 | 
						|
	lw.closed = make(chan struct{})
 | 
						|
	checker.stdOut = lw
 | 
						|
 | 
						|
	go func() {
 | 
						|
		defer func() {
 | 
						|
			_ = stdinReader.Close()
 | 
						|
			_ = lw.Close()
 | 
						|
		}()
 | 
						|
		stdErr := new(bytes.Buffer)
 | 
						|
		err := cmd.WithEnv(envs).
 | 
						|
			WithDir(repo.Path).
 | 
						|
			WithStdin(stdinReader).
 | 
						|
			WithStdout(lw).
 | 
						|
			WithStderr(stdErr).
 | 
						|
			Run(ctx)
 | 
						|
 | 
						|
		if err != nil && !git.IsErrCanceledOrKilled(err) {
 | 
						|
			log.Error("Attribute checker for commit %s exits with error: %v", treeish, err)
 | 
						|
		}
 | 
						|
		checker.cancel()
 | 
						|
	}()
 | 
						|
 | 
						|
	return checker, nil
 | 
						|
}
 | 
						|
 | 
						|
// CheckPath check attr for given path
 | 
						|
func (c *BatchChecker) CheckPath(path string) (rs *Attributes, err error) {
 | 
						|
	defer func() {
 | 
						|
		if err != nil && err != c.ctx.Err() {
 | 
						|
			log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.repo.Path), err)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	select {
 | 
						|
	case <-c.ctx.Done():
 | 
						|
		return nil, c.ctx.Err()
 | 
						|
	default:
 | 
						|
	}
 | 
						|
 | 
						|
	if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil {
 | 
						|
		defer c.Close()
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	reportTimeout := func() error {
 | 
						|
		stdOutClosed := false
 | 
						|
		select {
 | 
						|
		case <-c.stdOut.closed:
 | 
						|
			stdOutClosed = true
 | 
						|
		default:
 | 
						|
		}
 | 
						|
		debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.repo.Path))
 | 
						|
		debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
 | 
						|
		if c.cmd != nil {
 | 
						|
			debugMsg += fmt.Sprintf(", process state: %q", c.cmd.ProcessState())
 | 
						|
		}
 | 
						|
		_ = c.Close()
 | 
						|
		return fmt.Errorf("CheckPath timeout: %s", debugMsg)
 | 
						|
	}
 | 
						|
 | 
						|
	rs = NewAttributes()
 | 
						|
	for i := 0; i < c.attributesNum; i++ {
 | 
						|
		select {
 | 
						|
		case <-time.After(5 * time.Second):
 | 
						|
			// there is no "hang" problem now. This code is just used to catch other potential problems.
 | 
						|
			return nil, reportTimeout()
 | 
						|
		case attr, ok := <-c.stdOut.ReadAttribute():
 | 
						|
			if !ok {
 | 
						|
				return nil, c.ctx.Err()
 | 
						|
			}
 | 
						|
			rs.m[attr.Attribute] = Attribute(attr.Value)
 | 
						|
		case <-c.ctx.Done():
 | 
						|
			return nil, c.ctx.Err()
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return rs, nil
 | 
						|
}
 | 
						|
 | 
						|
func (c *BatchChecker) Close() error {
 | 
						|
	c.cancel()
 | 
						|
	err := c.stdinWriter.Close()
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
type attributeTriple struct {
 | 
						|
	Filename  string
 | 
						|
	Attribute string
 | 
						|
	Value     string
 | 
						|
}
 | 
						|
 | 
						|
type nulSeparatedAttributeWriter struct {
 | 
						|
	tmp        []byte
 | 
						|
	attributes chan attributeTriple
 | 
						|
	closed     chan struct{}
 | 
						|
	working    attributeTriple
 | 
						|
	pos        int
 | 
						|
}
 | 
						|
 | 
						|
func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
 | 
						|
	l, read := len(p), 0
 | 
						|
 | 
						|
	nulIdx := bytes.IndexByte(p, '\x00')
 | 
						|
	for nulIdx >= 0 {
 | 
						|
		wr.tmp = append(wr.tmp, p[:nulIdx]...)
 | 
						|
		switch wr.pos {
 | 
						|
		case 0:
 | 
						|
			wr.working = attributeTriple{
 | 
						|
				Filename: string(wr.tmp),
 | 
						|
			}
 | 
						|
		case 1:
 | 
						|
			wr.working.Attribute = string(wr.tmp)
 | 
						|
		case 2:
 | 
						|
			wr.working.Value = string(wr.tmp)
 | 
						|
		}
 | 
						|
		wr.tmp = wr.tmp[:0]
 | 
						|
		wr.pos++
 | 
						|
		if wr.pos > 2 {
 | 
						|
			wr.attributes <- wr.working
 | 
						|
			wr.pos = 0
 | 
						|
		}
 | 
						|
		read += nulIdx + 1
 | 
						|
		if l > read {
 | 
						|
			p = p[nulIdx+1:]
 | 
						|
			nulIdx = bytes.IndexByte(p, '\x00')
 | 
						|
		} else {
 | 
						|
			return l, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	wr.tmp = append(wr.tmp, p...)
 | 
						|
	return l, nil
 | 
						|
}
 | 
						|
 | 
						|
func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
 | 
						|
	return wr.attributes
 | 
						|
}
 | 
						|
 | 
						|
func (wr *nulSeparatedAttributeWriter) Close() error {
 | 
						|
	select {
 | 
						|
	case <-wr.closed:
 | 
						|
		return nil
 | 
						|
	default:
 | 
						|
	}
 | 
						|
	close(wr.attributes)
 | 
						|
	close(wr.closed)
 | 
						|
	return nil
 | 
						|
}
 |