github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/localconn.go (about)

     1  package git
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"os/exec"
     9  )
    10  
    11  // A localConn is like an ssh conn, but it communicates locally
    12  // over a pipe rather than running git-upload-pack remotely over
    13  // ssh.
    14  type localConn struct {
    15  	// Add functionality shared amongst all types of remotes
    16  	*sharedRemoteConn
    17  
    18  	stdin  io.ReadCloser
    19  	stdout io.WriteCloser
    20  	cmd    *exec.Cmd
    21  }
    22  
    23  var _ RemoteConn = &localConn{}
    24  
    25  func (s *localConn) OpenConn(srv GitService) error {
    26  	var cmd *exec.Cmd
    27  	log.Println("Connecting locally via", s.uri.Path)
    28  	if s.service == "" {
    29  		cmd = exec.Command(s.service, s.uri.Path)
    30  	} else {
    31  		switch srv {
    32  		case UploadPackService:
    33  			cmd = exec.Command("git-upload-pack", s.uri.Path)
    34  		case ReceivePackService:
    35  			cmd = exec.Command("git-receive-pack", s.uri.Path)
    36  		default:
    37  			return fmt.Errorf("Unhandled service")
    38  		}
    39  	}
    40  	cmd.Stderr = os.Stderr
    41  	cmdIn, err := cmd.StdinPipe()
    42  	if err != nil {
    43  		return err
    44  	}
    45  	s.stdout = cmdIn
    46  	cmdOut, err := cmd.StdoutPipe()
    47  	if err != nil {
    48  		return err
    49  	}
    50  	s.stdin = cmdOut
    51  
    52  	// We don't check error on setenv because if it failed we'll just fall
    53  	// back on protocol v1
    54  	os.Setenv("GIT_PROTOCOL", "version=2")
    55  
    56  	if err := cmd.Start(); err != nil {
    57  		return err
    58  	}
    59  
    60  	v, cap, refs, err := parseRemoteInitialConnection(s.stdin, false)
    61  	if err != nil {
    62  		s.stdin.Close()
    63  		s.stdout.Close()
    64  		return cmd.Wait()
    65  	}
    66  	s.cmd = cmd
    67  	s.packProtocolReader = &packProtocolReader{conn: s.stdin, state: PktLineMode}
    68  
    69  	s.protocolversion = v
    70  	s.capabilities = cap
    71  	s.refs = refs
    72  	return nil
    73  }
    74  
    75  func (s localConn) Close() error {
    76  	fmt.Fprintf(s.stdout, "0000")
    77  	s.stdout.Close()
    78  	s.stdin.Close()
    79  	return s.cmd.Wait()
    80  }
    81  
    82  func (s localConn) GetRefs(opts LsRemoteOptions, patterns []string) ([]Ref, error) {
    83  	switch s.protocolversion {
    84  	case 1:
    85  		return getRefsV1(s.refs, opts, patterns)
    86  	case 2:
    87  		s.SetWriteMode(PktLineMode)
    88  		cmd, err := buildLsRefsCmdV2(opts, patterns)
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  		fmt.Fprintf(s.stdout, cmd)
    93  		line := make([]byte, 65536)
    94  		var vals []Ref
    95  		for {
    96  			n, err := s.Read(line)
    97  			switch err {
    98  			case flushPkt:
    99  				return vals, nil
   100  			case nil: // Nothing
   101  			default:
   102  				return nil, err
   103  			}
   104  			refstr := string(line[0:n])
   105  			ref, err := parseLsRef(refstr)
   106  			if err != nil {
   107  				return nil, err
   108  			}
   109  			vals = append(vals, ref)
   110  		}
   111  	default:
   112  		return nil, fmt.Errorf("Protocol version not supported")
   113  	}
   114  }
   115  
   116  func (s localConn) Flush() error {
   117  	fmt.Fprintf(s.stdout, "0000")
   118  	return nil
   119  }
   120  
   121  func (s localConn) Delim() error {
   122  	fmt.Fprintf(s.stdout, "0001")
   123  	return nil
   124  }
   125  
   126  func (s localConn) Write(data []byte) (int, error) {
   127  	switch s.writemode {
   128  	case PktLineMode:
   129  		l, err := PktLineEncodeNoNl(data)
   130  		if err != nil {
   131  			return 0, err
   132  		}
   133  		fmt.Fprintf(s.stdout, "%s", l)
   134  		return len(data), nil
   135  	case DirectMode:
   136  		return s.stdout.Write(data)
   137  	default:
   138  		return 0, fmt.Errorf("Invalid write mode")
   139  	}
   140  }