You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: revisions.go
    path of file: ./repos/porter/internal/porter_app
    
    the code of the file until where you have to start completion: package porter_app

import (
	"context"
	"encoding/base64"
	"errors"
	"time"

	"connectrpc.com/connect"
	"github.com/google/uuid"
	"github.com/porter-dev/api-contracts/generated/go/helpers"
	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
	"github.com/porter-dev/api-contracts/generated/go/porter/v1/porterv1connect"
	"github.com/porter-dev/porter/internal/deployment_target"
	"github.com/porter-dev/porter/internal/kubernetes"
	"github.com/porter-dev/porter/internal/kubernetes/environment_groups"
	"github.com/porter-dev/porter/internal/models"
	"github.com/porter-dev/porter/internal/repository"
	"github.com/porter-dev/porter/internal/telemetry"
)

// Revision represents the data for a single revision
type Revision struct {
	// ID is the revision id
	ID string `json:"id"`
	// B64AppProto is the base64 encoded app proto definition
	B64AppProto string `json:"b64_app_proto"`
	// Status is the status of the revision
	Status models.AppRevisionStatus `json:"status"`
	// RevisionNumber is the revision number with respect to the app and deployment target
	RevisionNumber uint64 `json:"revision_number"`
	// CreatedAt is the time the revision was created
	CreatedAt time.Time `json:"created_at"`
	// UpdatedAt is the time the revision was updated
	UpdatedAt time.Time `json:"updated_at"`
	// DeploymentTargetID is the id of the deployment target the revision is associated with
	DeploymentTarget DeploymentTarget `json:"deployment_target"`
	// Env is the environment variables for the revision
	Env environment_groups.EnvironmentGroup `json:"env,omitempty"`
	// AppInstanceID is the id of the app instance the revision is associated with
	AppInstanceID uuid.UUID `json:"app_instance_id"`
}

// AppInstance represents the data for an app instance
type App
    
func AttachEnvToRevision(ctx context.Context, inp AttachEnvToRevisionInput) (Revision, error) {
	ctx, span := telemetry.NewSpan(ctx, "attach-env-to-revision")
	defer span.End()

	revision := inp.Revision

	if inp.ProjectID == 0 {
		return revision, telemetry.Error(ctx, span, nil, "must provide a project id")
	}
	if inp.ClusterID == 0 {
		return revision, telemetry.Error(ctx, span, nil, "must provide a cluster id")
	}
	if inp.K8SAgent == nil {
		return revision, telemetry.Error(ctx, span, nil, "k8s agent is nil")
	}

	decoded, err := base64.StdEncoding.DecodeString(revision.B64AppProto)
	if err != nil {
		return revision, telemetry.Error(ctx, span, err, "error decoding app proto")
	}

	appDef := &porterv1.PorterApp{}
	err = helpers.UnmarshalContractObject(decoded, appDef)
	if err != nil {
		return revision, telemetry.Error(ctx, span, err, "error unmarshalling app proto")
	}

	envName, err := AppEnvGroupName(ctx, appDef.Name, inp.Revision.DeploymentTarget.ID, uint(inp.ClusterID), inp.PorterAppRepository)
	if err != nil {
		return revision, telemetry.Error(ctx, span, err, "error getting app env group name")
	}
	envNameFilter := []string{envName}

	envFromProtoInp := AppEnvironmentFromProtoInput{
		ProjectID:        inp.ProjectID,
		ClusterID:        inp.ClusterID,
		App:              appDef,
		K8SAgent:         inp.K8SAgent,
		DeploymentTarget: inp.DeploymentTarget,
	}
	envGroups, err := AppEnvironmentFromProto(ctx, envFromProtoInp, WithEnvGroupFilter(envNameFilter), WithSecrets())
	if err != nil {
		return revision, telemetry.Error(ctx, span, err, "error getting app environment from revision")
	}

	if len(envGroups) > 1 {
		return revision, telemetry.Error(ctx, span, err, "multiple app envs groups returned for same name")
	}
	if len(envGroups) == 1 {
		revision.Env = envGroups[0]
	}

	return revision, nil
}