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 }