github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runbits/commit/commit.go (about)

     1  package commit
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/ActiveState/cli/internal/errs"
     9  	"github.com/ActiveState/cli/internal/locale"
    10  	"github.com/ActiveState/cli/internal/logging"
    11  	"github.com/ActiveState/cli/internal/multilog"
    12  	"github.com/ActiveState/cli/internal/output"
    13  	gmodel "github.com/ActiveState/cli/pkg/platform/api/graphql/model"
    14  	"github.com/ActiveState/cli/pkg/platform/api/mono/mono_models"
    15  	"github.com/ActiveState/cli/pkg/platform/model"
    16  	"github.com/go-openapi/strfmt"
    17  )
    18  
    19  type commitOutput struct {
    20  	Hash              string               `locale:"hash,[HEADING]Commit[/RESET]" json:"hash"`
    21  	Author            string               `locale:"author,[HEADING]Author[/RESET]" json:"author"`
    22  	Date              string               `locale:"date,[HEADING]Date[/RESET]" json:"date"`
    23  	Revision          string               `locale:"revision,[HEADING]Revision[/RESET]" json:"revision"`
    24  	Message           string               `locale:"message,[HEADING]Message[/RESET]" json:"message"`
    25  	PlainChanges      []string             `locale:"changes,[HEADING]Changes[/RESET]" json:"-" opts:"hideDash"`
    26  	StructuredChanges []*requirementChange `opts:"hidePlain" json:"changes"`
    27  }
    28  
    29  type requirementChange struct {
    30  	Operation             string `json:"operation"`
    31  	Requirement           string `json:"requirement"`
    32  	VersionConstraintsOld string `json:"version_constraints_old,omitempty"`
    33  	VersionConstraintsNew string `json:"version_constraints_new,omitempty"`
    34  	Namespace             string `json:"namespace"`
    35  }
    36  
    37  func (o *commitOutput) MarshalOutput(format output.Format) interface{} {
    38  	return struct {
    39  		Data commitOutput `opts:"verticalTable" locale:","`
    40  	}{
    41  		Data: *o,
    42  	}
    43  }
    44  
    45  func (o *commitOutput) MarshalStructured(format output.Format) interface{} {
    46  	return o
    47  }
    48  
    49  func PrintCommit(out output.Outputer, commit *mono_models.Commit, orgs []gmodel.Organization) error {
    50  	data, err := newCommitOutput(commit, orgs, false)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	out.Print(data)
    55  	return nil
    56  }
    57  
    58  func newCommitOutput(commit *mono_models.Commit, orgs []gmodel.Organization, isLocal bool) (*commitOutput, error) {
    59  	var localTxt string
    60  	if isLocal {
    61  		localTxt = locale.Tl("commit_display_local", "[NOTICE] (local)[/RESET]")
    62  	}
    63  
    64  	var username string
    65  	var err error
    66  	if commit.Author != nil && orgs != nil {
    67  		username = usernameForID(*commit.Author, orgs)
    68  	}
    69  
    70  	plainChanges, structuredChanges := FormatChanges(commit)
    71  
    72  	commitOutput := &commitOutput{
    73  		Hash:              locale.Tl("print_commit_hash", "[ACTIONABLE]{{.V0}}[/RESET]{{.V1}}", commit.CommitID.String(), localTxt),
    74  		Author:            username,
    75  		PlainChanges:      plainChanges,
    76  		StructuredChanges: structuredChanges,
    77  	}
    78  
    79  	dt, err := time.Parse(time.RFC3339, commit.Added.String())
    80  	if err != nil {
    81  		multilog.Error("Could not parse commit time: %v", err)
    82  	}
    83  	commitOutput.Date = dt.Format(time.RFC822)
    84  
    85  	dt, err = time.Parse(time.RFC3339, commit.AtTime.String())
    86  	if err != nil {
    87  		multilog.Error("Could not parse revision time: %v", err)
    88  	}
    89  	commitOutput.Revision = dt.Format(time.RFC822)
    90  
    91  	commitOutput.Message = locale.Tl("print_commit_no_message", "[DISABLED]Not provided.[/RESET]")
    92  	if commit.Message != "" {
    93  		commitOutput.Message = commit.Message
    94  	}
    95  
    96  	return commitOutput, nil
    97  }
    98  
    99  func FormatChanges(commit *mono_models.Commit) ([]string, []*requirementChange) {
   100  	results := []string{}
   101  	requirements := []*requirementChange{}
   102  
   103  	for _, change := range commit.Changeset {
   104  		requirement := change.Requirement
   105  		versionConstraints := formatConstraints(change.VersionConstraints)
   106  		if model.NamespaceMatch(change.Namespace, model.NamespacePlatformMatch) {
   107  			requirement = locale.T("namespace_label_platform")
   108  			versionConstraints = ""
   109  		}
   110  		if model.NamespaceMatch(change.Namespace, model.NamespacePrePlatformMatch) {
   111  			requirement = locale.T("namespace_label_preplatform")
   112  			versionConstraints = ""
   113  		}
   114  
   115  		// This is a temporary fix until we start getting history in the form of build expressions
   116  		// https://activestatef.atlassian.net/browse/DX-2197
   117  		if model.NamespaceMatch(change.Namespace, model.NamespaceBuildFlagsMatch) &&
   118  			(strings.Contains(change.Requirement, "docker") || strings.Contains(change.Requirement, "installer")) {
   119  			requirement = locale.T("namespace_label_packager")
   120  			versionConstraints = ""
   121  		}
   122  
   123  		var result, oldConstraints, newConstraints string
   124  		switch change.Operation {
   125  		case string(model.OperationAdded):
   126  			result = locale.Tr("change_added", requirement, versionConstraints, change.Namespace)
   127  			newConstraints = formatConstraints(change.VersionConstraints)
   128  		case string(model.OperationRemoved):
   129  			result = locale.Tr("change_removed", requirement, change.Namespace)
   130  			oldConstraints = formatConstraints(change.VersionConstraintsOld)
   131  		case string(model.OperationUpdated):
   132  			result = locale.Tr("change_updated", requirement, formatConstraints(change.VersionConstraintsOld), versionConstraints, change.Namespace)
   133  			oldConstraints = formatConstraints(change.VersionConstraintsOld)
   134  			newConstraints = formatConstraints(change.VersionConstraints)
   135  		}
   136  		results = append(results, result)
   137  
   138  		requirements = append(requirements, &requirementChange{
   139  			Operation:             change.Operation,
   140  			Requirement:           change.Requirement,
   141  			VersionConstraintsOld: oldConstraints,
   142  			VersionConstraintsNew: newConstraints,
   143  			Namespace:             change.Namespace,
   144  		})
   145  	}
   146  
   147  	return results, requirements
   148  }
   149  
   150  func formatConstraints(constraints []*mono_models.Constraint) string {
   151  	if len(constraints) == 0 {
   152  		return locale.T("constraint_auto")
   153  	}
   154  
   155  	var result []string
   156  	for _, constraint := range constraints {
   157  		var comparator string
   158  		switch constraint.Comparator {
   159  		case "eq":
   160  			return constraint.Version
   161  		case "gt":
   162  			comparator = ">"
   163  		case "gte":
   164  			comparator = ">="
   165  		case "lt":
   166  			comparator = "<"
   167  		case "lte":
   168  			comparator = "<="
   169  		case "ne":
   170  			comparator = "!="
   171  		default:
   172  			comparator = "?"
   173  		}
   174  		result = append(result, fmt.Sprintf("%s%s", comparator, constraint.Version))
   175  	}
   176  	return strings.Join(result, ",")
   177  }
   178  
   179  func usernameForID(id strfmt.UUID, orgs []gmodel.Organization) string {
   180  	for _, org := range orgs {
   181  		if org.ID == id {
   182  			if org.DisplayName != "" {
   183  				return org.DisplayName
   184  			}
   185  			return org.URLName
   186  		}
   187  	}
   188  
   189  	placeholder := locale.Tl("deleted_username", "<deleted>")
   190  	logging.Debug("Could not determine username for commit author '%s'. Using placeholder value '%s'.", id, placeholder)
   191  	return placeholder
   192  }
   193  
   194  type commitsOutput []commitOutput
   195  
   196  func newCommitsOutput(commits []*mono_models.Commit, orgs []gmodel.Organization, lastRemoteID *strfmt.UUID) (*commitsOutput, error) {
   197  	data := make(commitsOutput, 0, len(commits))
   198  	isLocal := true // recent (and, therefore, local) commits are first
   199  
   200  	for _, c := range commits {
   201  		if isLocal && lastRemoteID != nil && c.CommitID == *lastRemoteID {
   202  			isLocal = false
   203  		}
   204  
   205  		d, err := newCommitOutput(c, orgs, isLocal)
   206  		if err != nil {
   207  			return nil, err
   208  		}
   209  		data = append(data, *d)
   210  	}
   211  
   212  	return &data, nil
   213  }
   214  
   215  func (o *commitsOutput) MarshalOutput(format output.Format) interface{} {
   216  	return struct {
   217  		Data commitsOutput `opts:"verticalTable" locale:","`
   218  	}{
   219  		Data: *o,
   220  	}
   221  }
   222  
   223  func (o *commitsOutput) MarshalStructured(format output.Format) interface{} {
   224  	return o
   225  }
   226  
   227  func PrintCommits(out output.Outputer, commits []*mono_models.Commit, orgs []gmodel.Organization, lastRemoteID *strfmt.UUID) error {
   228  	data, err := newCommitsOutput(commits, orgs, lastRemoteID)
   229  	if err != nil {
   230  		return errs.Wrap(err, "Unable to fetch commit data")
   231  	}
   232  	out.Print(data)
   233  	return nil
   234  }