// Copyright 2023 Harness, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package api import ( "bytes" "context" "fmt" "io" "regexp" "strconv" "strings" "github.com/harness/gitness/errors" "github.com/harness/gitness/git/command" "github.com/harness/gitness/types/enum" "github.com/rs/zerolog/log" ) var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`) func (g *Git) InfoRefs( ctx context.Context, repoPath string, service string, w io.Writer, env ...string, ) error { stdout := &bytes.Buffer{} cmd := command.New(service, command.WithFlag("--stateless-rpc"), command.WithFlag("--advertise-refs"), command.WithArg("."), ) if err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(stdout), command.WithEnvs(env...), ); err != nil { return errors.Internal(err, "InfoRefs service %s failed", service) } if _, err := w.Write(packetWrite("# service=git-" + service + "\n")); err != nil { return errors.Internal(err, "failed to write pktLine in InfoRefs %s service", service) } if _, err := w.Write([]byte("0000")); err != nil { return errors.Internal(err, "failed to flush data in InfoRefs %s service", service) } if _, err := io.Copy(w, stdout); err != nil { return errors.Internal(err, "streaming InfoRefs %s service failed", service) } return nil } type ServicePackConfig struct { UploadPackHook string } type ServicePackOptions struct { Service enum.GitServiceType Timeout int // seconds StatelessRPC bool Stdout io.Writer Stdin io.Reader Stderr io.Writer Env []string Protocol string Config ServicePackConfig } func (g *Git) ServicePack( ctx context.Context, repoPath string, options ServicePackOptions, ) error { cmd := command.New(string(options.Service), command.WithArg(repoPath), command.WithEnv("SSH_ORIGINAL_COMMAND", string(options.Service)), ) if options.StatelessRPC { cmd.Add(command.WithFlag("--stateless-rpc")) } if options.Protocol != "" && safeGitProtocolHeader.MatchString(options.Protocol) { cmd.Add(command.WithEnv("GIT_PROTOCOL", options.Protocol)) } if options.Config.UploadPackHook != "" { cmd.Add(command.WithConfig("uploadpack.packObjectsHook", options.Config.UploadPackHook)) } err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(options.Stdout), command.WithStdin(options.Stdin), command.WithStderr(options.Stderr), command.WithEnvs(options.Env...), ) if err != nil && err.Error() != "signal: killed" { log.Ctx(ctx).Err(err).Msgf("Fail to serve RPC(%s) in %s: %v", options.Service, repoPath, err) } return err } func packetWrite(str string) []byte { s := strconv.FormatInt(int64(len(str)+4), 16) if len(s)%4 != 0 { s = strings.Repeat("0", 4-len(s)%4) + s } return []byte(s + str) } func PktError(w io.Writer, err error) { strErr := fmt.Sprintf("ERR %s\n", err.Error()) _, _ = w.Write(packetWrite(strErr)) }