func()

in internal/gitaly/service/operations/apply_patch.go [59:218]


func (s *Server) userApplyPatch(ctx context.Context, header *gitalypb.UserApplyPatchRequest_Header, stream gitalypb.OperationService_UserApplyPatchServer) (returnedErr error) {
	path, err := s.locator.GetRepoPath(ctx, header.GetRepository())
	if err != nil {
		return err
	}

	branchCreated := false
	targetBranch := git.NewReferenceNameFromBranchName(string(header.GetTargetBranch()))

	repo := s.localRepoFactory.Build(header.GetRepository())

	objectHash, err := repo.ObjectHash(ctx)
	if err != nil {
		return fmt.Errorf("detecting object hash: %w", err)
	}

	parentCommitID, err := repo.ResolveRevision(ctx, targetBranch.Revision()+"^{commit}")
	if err != nil {
		if !errors.Is(err, git.ErrReferenceNotFound) {
			return fmt.Errorf("resolve target branch: %w", err)
		}

		defaultBranch, err := repo.GetDefaultBranch(ctx)
		if err != nil {
			return fmt.Errorf("default branch name: %w", err)
		}

		parentCommitID, err = repo.ResolveRevision(ctx, defaultBranch.Revision()+"^{commit}")
		if errors.Is(err, git.ErrReferenceNotFound) {
			return errNoDefaultBranch
		} else if err != nil {
			return fmt.Errorf("resolve default branch commit: %w", err)
		}

		branchCreated = true
	}

	committerSignature, err := git.SignatureFromRequest(header)
	if err != nil {
		return structerr.NewInvalidArgument("%w", err)
	}

	worktreePath := newWorktreePath(path, "am-")
	if err := s.addWorktree(ctx, repo, worktreePath, parentCommitID.String()); err != nil {
		return fmt.Errorf("add worktree: %w", err)
	}

	// When transactions are not used, the worktree is added to the actual repository and needs to be removed.
	// When transaction are used, the worktree ends up in the snapshot, and is removed with it. The snapshot
	// is removed before this removal operations runs. Don't remove the tree here with transactions.
	if storage.ExtractTransaction(ctx) == nil {
		defer func() {
			ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second)
			defer cancel()

			worktreeName := filepath.Base(worktreePath)
			if err := s.removeWorktree(ctx, repo, worktreeName); err != nil {
				returnedErr = errors.Join(returnedErr,
					structerr.NewInternal("failed to remove worktree: %w", err).WithMetadata("worktree_name", worktreeName),
				)
			}
		}()
	}

	var stdout, stderr bytes.Buffer
	if err := repo.ExecAndWait(ctx,
		gitcmd.Command{
			Name: "am",
			Flags: []gitcmd.Option{
				gitcmd.Flag{Name: "--quiet"},
				gitcmd.Flag{Name: "--3way"},
			},
		},
		gitcmd.WithEnv(
			"GIT_COMMITTER_NAME="+committerSignature.Name,
			"GIT_COMMITTER_EMAIL="+committerSignature.Email,
			"GIT_COMMITTER_DATE="+git.FormatTime(committerSignature.When),
		),
		gitcmd.WithStdin(streamio.NewReader(func() ([]byte, error) {
			req, err := stream.Recv()
			return req.GetPatches(), err
		})),
		gitcmd.WithStdout(&stdout),
		gitcmd.WithStderr(&stderr),
		gitcmd.WithRefTxHook(objectHash, repo),
		gitcmd.WithWorktree(worktreePath),
	); err != nil {
		// The Ruby implementation doesn't include stderr in errors, which makes
		// it difficult to determine the cause of an error. This special cases the
		// user facing patching error which is returned usually to maintain test
		// compatibility but returns the error and stderr otherwise. Once the Ruby
		// implementation is removed, this should probably be dropped.
		if bytes.HasPrefix(stdout.Bytes(), []byte("Patch failed at")) {
			return structerr.NewFailedPrecondition("%s", stdout.String())
		}

		return fmt.Errorf("apply patch: %w, stderr: %q", err, &stderr)
	}

	var revParseStdout, revParseStderr bytes.Buffer
	if err := repo.ExecAndWait(ctx,
		gitcmd.Command{
			Name: "rev-parse",
			Flags: []gitcmd.Option{
				gitcmd.Flag{Name: "--quiet"},
				gitcmd.Flag{Name: "--verify"},
			},
			Args: []string{"HEAD^{commit}"},
		},
		gitcmd.WithStdout(&revParseStdout),
		gitcmd.WithStderr(&revParseStderr),
		gitcmd.WithWorktree(worktreePath),
	); err != nil {
		return fmt.Errorf("get patched commit: %w", gitError{ErrMsg: revParseStderr.String(), Err: err})
	}

	patchedCommit, err := objectHash.FromHex(text.ChompBytes(revParseStdout.Bytes()))
	if err != nil {
		return fmt.Errorf("parse patched commit oid: %w", err)
	}

	currentCommit := parentCommitID
	if branchCreated {
		currentCommit = objectHash.ZeroOID
	}

	// If the client provides an expected old object ID, we should use that to prevent any race
	// conditions wherein the ref was concurrently updated by different processes.
	if expectedOldOID := header.GetExpectedOldOid(); expectedOldOID != "" {
		objectHash, err := repo.ObjectHash(ctx)
		if err != nil {
			return fmt.Errorf("detecting object hash: %w", err)
		}

		currentCommit, err = objectHash.FromHex(expectedOldOID)
		if err != nil {
			return fmt.Errorf("expected old object id not expected SHA format: %w", err)
		}

		currentCommit, err = resolveRevision(ctx, repo, currentCommit)
		if err != nil {
			return fmt.Errorf("expected old object cannot be resolved: %w", err)
		}
	}

	if err := s.updateReferenceWithHooks(ctx, header.GetRepository(), header.GetUser(), nil, targetBranch, patchedCommit, currentCommit); err != nil {
		return fmt.Errorf("update reference: %w", err)
	}

	if err := stream.SendAndClose(&gitalypb.UserApplyPatchResponse{
		BranchUpdate: &gitalypb.OperationBranchUpdate{
			CommitId:      patchedCommit.String(),
			BranchCreated: branchCreated,
		},
	}); err != nil {
		return fmt.Errorf("send and close: %w", err)
	}

	return nil
}