github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/model/vcs.go (about) 1 package model 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" 7 "strings" 8 9 "github.com/ActiveState/cli/internal/errs" 10 "github.com/ActiveState/cli/internal/locale" 11 "github.com/ActiveState/cli/internal/logging" 12 "github.com/ActiveState/cli/internal/multilog" 13 "github.com/ActiveState/cli/pkg/platform/api" 14 "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" 15 gqlModel "github.com/ActiveState/cli/pkg/platform/api/graphql/model" 16 "github.com/ActiveState/cli/pkg/platform/api/mediator/model" 17 "github.com/ActiveState/cli/pkg/platform/api/mono" 18 "github.com/ActiveState/cli/pkg/platform/api/mono/mono_client/version_control" 19 vcsClient "github.com/ActiveState/cli/pkg/platform/api/mono/mono_client/version_control" 20 "github.com/ActiveState/cli/pkg/platform/api/mono/mono_models" 21 "github.com/ActiveState/cli/pkg/platform/authentication" 22 bpModel "github.com/ActiveState/cli/pkg/platform/model/buildplanner" 23 "github.com/go-openapi/strfmt" 24 ) 25 26 var ( 27 ErrCommitCountUnknowable = errs.New("Commit count is unknowable") 28 29 ErrMergeFastForward = errs.New("No merge required") 30 31 ErrMergeCommitInHistory = errs.New("Can't merge commit thats already in target commits history") 32 33 ErrCommitNotInHistory = errs.New("Target commit is not in history") 34 ) 35 36 var ErrOrderForbidden = errs.New("no permission to retrieve order") 37 38 type ErrUpdateBranchAuth struct{ *locale.LocalizedError } 39 40 type ProjectInfo interface { 41 Owner() string 42 Name() string 43 CommitUUID() strfmt.UUID 44 BranchName() string 45 } 46 47 // Operation is the action to be taken in a commit 48 type Operation string 49 50 const ( 51 // OperationAdded is for adding a new requirement 52 OperationAdded = Operation(mono_models.CommitChangeEditableOperationAdded) 53 // OperationUpdated is for updating an existing requirement 54 OperationUpdated = Operation(mono_models.CommitChangeEditableOperationUpdated) 55 // OperationRemoved is for removing an existing requirement 56 OperationRemoved = Operation(mono_models.CommitChangeEditableOperationRemoved) 57 ) 58 59 // NamespaceMatchable represents regular expression strings used for defining matchable 60 // requirements. 61 type NamespaceMatchable string 62 63 const ( 64 // NamespacePlatformMatch is the namespace used for platform requirements 65 NamespacePlatformMatch NamespaceMatchable = `^platform$` 66 67 // NamespaceLanguageMatch is the namespace used for language requirements 68 NamespaceLanguageMatch = `^language$` 69 70 // NamespacePackageMatch is the namespace used for language package requirements 71 NamespacePackageMatch = `^language\/(\w+)$` 72 73 // NamespacePackageMatch is the namespace used for language package requirements 74 NamespaceBuilderMatch = `^builder(-lib){0,1}$` 75 76 // NamespaceBundlesMatch is the namespace used for bundle package requirements 77 NamespaceBundlesMatch = `^bundles\/(\w+)$` 78 79 // NamespacePrePlatformMatch is the namespace used for pre-platform bits 80 NamespacePrePlatformMatch = `^pre-platform-installer$` 81 82 // NamespaceCamelFlagsMatch is the namespace used for passing camel flags 83 NamespaceCamelFlagsMatch = `^camel-flags$` 84 85 // NamespaceOrgMatch is the namespace used for org specific requirements 86 NamespaceOrgMatch = `^org\/` 87 88 // NamespaceBuildFlagsMatch is the namespace used for passing build flags 89 NamespaceBuildFlagsMatch = `^build-flags$` 90 ) 91 92 type TrackingType string 93 94 const ( 95 // TrackingNotify represents the notify tracking type for branches and will 96 // notify the project owner of upstream changes 97 TrackingNotify TrackingType = TrackingType(mono_models.BranchEditableTrackingTypeNotify) 98 // TrackingIgnore represents the ignore tracking type for branches and will 99 // ignore upstream changes 100 TrackingIgnore = TrackingType(mono_models.BranchEditableTrackingTypeIgnore) 101 // TrackingAutoUpdate represents the auto update tracking type for branches and will 102 // auto update the branch with any upstream changes 103 TrackingAutoUpdate = TrackingType(mono_models.BranchEditableTrackingTypeAutoUpdate) 104 ) 105 106 func (t TrackingType) String() string { 107 switch t { 108 case TrackingNotify: 109 return mono_models.BranchEditableTrackingTypeNotify 110 case TrackingAutoUpdate: 111 return mono_models.BranchEditableTrackingTypeAutoUpdate 112 default: 113 return mono_models.BranchEditableTrackingTypeIgnore 114 } 115 } 116 117 // NamespaceMatch Checks if the given namespace query matches the given namespace 118 func NamespaceMatch(query string, namespace NamespaceMatchable) bool { 119 match, err := regexp.Match(string(namespace), []byte(query)) 120 if err != nil { 121 multilog.Error("Could not match regex for %v, query: %s, error: %v", namespace, query, err) 122 } 123 return match 124 } 125 126 type NamespaceType struct { 127 name string 128 prefix string 129 matchable NamespaceMatchable 130 } 131 132 var ( 133 NamespacePackage = NamespaceType{"package", "language", NamespacePackageMatch} // these values should match the namespace prefix 134 NamespaceBundle = NamespaceType{"bundle", "bundles", NamespaceBundlesMatch} 135 NamespaceLanguage = NamespaceType{"language", "", NamespaceLanguageMatch} 136 NamespacePlatform = NamespaceType{"platform", "", NamespacePlatformMatch} 137 NamespaceOrg = NamespaceType{"org", "org", NamespaceOrgMatch} 138 NamespaceRaw = NamespaceType{"raw", "", ""} 139 NamespaceBlank = NamespaceType{"", "", ""} 140 ) 141 142 func (t NamespaceType) String() string { 143 return t.name 144 } 145 146 func (t NamespaceType) Prefix() string { 147 return t.prefix 148 } 149 150 func (t NamespaceType) Matchable() NamespaceMatchable { 151 return t.matchable 152 } 153 154 // Namespace is the type used for communicating namespaces, mainly just allows for self documenting code 155 type Namespace struct { 156 nsType NamespaceType 157 value string 158 } 159 160 func (n Namespace) IsValid() bool { 161 return n.nsType.name != "" && n.nsType != NamespaceBlank && n.value != "" 162 } 163 164 func (n Namespace) Type() NamespaceType { 165 return n.nsType 166 } 167 168 func (n Namespace) String() string { 169 return n.value 170 } 171 172 func NewNamespacePkgOrBundle(language string, nstype NamespaceType) Namespace { 173 if nstype == NamespaceBundle { 174 return NewNamespaceBundle(language) 175 } 176 return NewNamespacePackage(language) 177 } 178 179 // NewNamespacePackage creates a new package namespace 180 func NewNamespacePackage(language string) Namespace { 181 return Namespace{NamespacePackage, fmt.Sprintf("language/%s", language)} 182 } 183 184 func NewRawNamespace(value string) Namespace { 185 return Namespace{NamespaceRaw, value} 186 } 187 188 func NewBlankNamespace() Namespace { 189 return Namespace{NamespaceBlank, ""} 190 } 191 192 // NewNamespaceBundle creates a new bundles namespace 193 func NewNamespaceBundle(language string) Namespace { 194 return Namespace{NamespaceBundle, fmt.Sprintf("bundles/%s", language)} 195 } 196 197 // NewNamespaceLanguage provides the base language namespace. 198 func NewNamespaceLanguage() Namespace { 199 return Namespace{NamespaceLanguage, "language"} 200 } 201 202 // NewNamespacePlatform provides the base platform namespace. 203 func NewNamespacePlatform() Namespace { 204 return Namespace{NamespacePlatform, "platform"} 205 } 206 207 func NewOrgNamespace(orgName string) Namespace { 208 return Namespace{ 209 nsType: NamespaceOrg, 210 value: fmt.Sprintf("private/%s", orgName), 211 } 212 } 213 214 func LanguageFromNamespace(ns string) string { 215 values := strings.Split(ns, "/") 216 if len(values) != 2 { 217 return "" 218 } 219 return values[1] 220 } 221 222 // FilterSupportedIngredients filters a list of ingredients, returning only those that are currently supported (such that they can be built) by the Platform 223 func FilterSupportedIngredients(supported []model.SupportedLanguage, ingredients []*IngredientAndVersion) ([]*IngredientAndVersion, error) { 224 var res []*IngredientAndVersion 225 226 for _, i := range ingredients { 227 language := LanguageFromNamespace(*i.Ingredient.PrimaryNamespace) 228 229 for _, l := range supported { 230 if l.Name != language { 231 continue 232 } 233 res = append(res, i) 234 break 235 } 236 } 237 238 return res, nil 239 } 240 241 // BranchCommitID returns the latest commit id by owner and project names. It 242 // is possible for a nil commit id to be returned without failure. 243 func BranchCommitID(ownerName, projectName, branchName string) (*strfmt.UUID, error) { 244 proj, err := LegacyFetchProjectByName(ownerName, projectName) 245 if err != nil { 246 return nil, err 247 } 248 249 branch, err := BranchForProjectByName(proj, branchName) 250 if err != nil { 251 return nil, err 252 } 253 254 if branch.CommitID == nil { 255 return nil, locale.NewInputError( 256 "err_project_no_commit", 257 "Your project does not have any commits yet, head over to {{.V0}} to set up your project.", api.GetPlatformURL(fmt.Sprintf("%s/%s", ownerName, projectName)).String()) 258 } 259 260 return branch.CommitID, nil 261 } 262 263 func CommitBelongsToBranch(ownerName, projectName, branchName string, commitID strfmt.UUID, auth *authentication.Auth) (bool, error) { 264 latestCID, err := BranchCommitID(ownerName, projectName, branchName) 265 if err != nil { 266 return false, errs.Wrap(err, "Could not get latest commit ID of branch") 267 } 268 269 return CommitWithinCommitHistory(*latestCID, commitID, auth) 270 } 271 272 func CommitWithinCommitHistory(latestCommitID, searchCommitID strfmt.UUID, auth *authentication.Auth) (bool, error) { 273 history, err := CommitHistoryFromID(latestCommitID, auth) 274 if err != nil { 275 return false, errs.Wrap(err, "Could not get commit history from commit ID") 276 } 277 278 for _, commit := range history { 279 if commit.CommitID == searchCommitID { 280 return true, nil 281 } 282 } 283 284 return false, nil 285 } 286 287 // CommitHistory will return the commit history for the given owner / project 288 func CommitHistory(ownerName, projectName, branchName string, auth *authentication.Auth) ([]*mono_models.Commit, error) { 289 latestCID, err := BranchCommitID(ownerName, projectName, branchName) 290 if err != nil { 291 return nil, err 292 } 293 return commitHistory(*latestCID, auth) 294 } 295 296 // CommitHistoryFromID will return the commit history from the given commitID 297 func CommitHistoryFromID(commitID strfmt.UUID, auth *authentication.Auth) ([]*mono_models.Commit, error) { 298 return commitHistory(commitID, auth) 299 } 300 301 func commitHistory(commitID strfmt.UUID, auth *authentication.Auth) ([]*mono_models.Commit, error) { 302 offset := int64(0) 303 limit := int64(100) 304 var commits []*mono_models.Commit 305 306 cont := true 307 for cont { 308 payload, err := CommitHistoryPaged(commitID, offset, limit, auth) 309 if err != nil { 310 return commits, err 311 } 312 commits = append(commits, payload.Commits...) 313 offset += limit 314 cont = payload.TotalCommits > offset 315 } 316 317 return commits, nil 318 } 319 320 // CommitHistoryPaged will return the commit history for the given owner / project 321 func CommitHistoryPaged(commitID strfmt.UUID, offset, limit int64, auth *authentication.Auth) (*mono_models.CommitHistoryInfo, error) { 322 params := vcsClient.NewGetCommitHistoryParams() 323 params.SetCommitID(commitID) 324 params.Limit = &limit 325 params.Offset = &offset 326 327 var res *vcsClient.GetCommitHistoryOK 328 var err error 329 if auth.Authenticated() { 330 authClient, err := auth.Client() 331 if err != nil { 332 return nil, errs.Wrap(err, "Could not get auth client") 333 } 334 res, err = authClient.VersionControl.GetCommitHistory(params, auth.ClientAuth()) 335 if err != nil { 336 return nil, locale.WrapError(err, "err_get_commit_history", "", api.ErrorMessageFromPayload(err)) 337 } 338 } else { 339 res, err = mono.New().VersionControl.GetCommitHistory(params, nil) 340 if err != nil { 341 return nil, locale.WrapError(err, "err_get_commit_history", "", api.ErrorMessageFromPayload(err)) 342 } 343 } 344 345 return res.Payload, nil 346 } 347 348 // CommonParent returns the first commit id which both provided commit id 349 // histories have in common. 350 func CommonParent(commit1, commit2 *strfmt.UUID, auth *authentication.Auth) (*strfmt.UUID, error) { 351 if commit1 == nil || commit2 == nil { 352 return nil, nil 353 } 354 355 if *commit1 == *commit2 { 356 return commit1, nil 357 } 358 359 history1, err := CommitHistoryFromID(*commit1, auth) 360 if err != nil { 361 return nil, errs.Wrap(err, "Could not get commit history for %s", commit1.String()) 362 } 363 364 history2, err := CommitHistoryFromID(*commit2, auth) 365 if err != nil { 366 return nil, errs.Wrap(err, "Could not get commit history for %s", commit2.String()) 367 } 368 369 return commonParentWithHistory(commit1, commit2, history1, history2), nil 370 } 371 372 func commonParentWithHistory(commit1, commit2 *strfmt.UUID, history1, history2 []*mono_models.Commit) *strfmt.UUID { 373 if commit1 == nil || commit2 == nil { 374 return nil 375 } 376 377 if *commit1 == *commit2 { 378 return commit1 379 } 380 381 for _, c := range history1 { 382 if c.CommitID == *commit2 { 383 return commit2 // commit1 history contains commit2 384 } 385 for _, c2 := range history2 { 386 if c.CommitID == c2.CommitID { 387 return &c.CommitID // commit1 and commit2 have a common parent 388 } 389 } 390 } 391 392 for _, c2 := range history2 { 393 if c2.CommitID == *commit1 { 394 return commit1 // commit2 history contains commit1 395 } 396 } 397 398 return nil 399 } 400 401 // CommitsBehind compares the provided commit id with the latest commit 402 // id and returns the count of commits it is behind. A negative return value 403 // indicates the provided commit id is ahead of the latest commit id (that is, 404 // there are local commits). 405 func CommitsBehind(latestCID, currentCommitID strfmt.UUID, auth *authentication.Auth) (int, error) { 406 if latestCID == "" { 407 if currentCommitID == "" { 408 return 0, nil // ok, nothing to do 409 } 410 return 0, locale.NewError("err_commit_count_no_latest_with_commit") 411 } 412 413 if latestCID.String() == currentCommitID.String() { 414 return 0, nil 415 } 416 417 // Assume current is behind or equal to latest. 418 commits, err := CommitHistoryFromID(latestCID, auth) 419 if err != nil { 420 return 0, locale.WrapError(err, "err_get_commit_history", "", err.Error()) 421 } 422 423 indexed := makeIndexedCommits(commits) 424 if behind, err := indexed.countBetween(currentCommitID.String(), latestCID.String()); err == nil { 425 return behind, nil 426 } 427 428 // Assume current is ahead of latest. 429 commits, err = CommitHistoryFromID(currentCommitID, auth) 430 if err != nil { 431 return 0, locale.WrapError(err, "err_get_commit_history", "", err.Error()) 432 } 433 434 indexed = makeIndexedCommits(commits) 435 ahead, err := indexed.countBetween(latestCID.String(), currentCommitID.String()) 436 return -ahead, err 437 } 438 439 // Changeset aliases for eased usage and to act as a disconnect from the underlying dep. 440 type Changeset = []*mono_models.CommitChangeEditable 441 442 func UpdateBranchForProject(pj ProjectInfo, commitID strfmt.UUID, auth *authentication.Auth) error { 443 pjm, err := LegacyFetchProjectByName(pj.Owner(), pj.Name()) 444 if err != nil { 445 return errs.Wrap(err, "Could not fetch project") 446 } 447 448 branch, err := BranchForProjectByName(pjm, pj.BranchName()) 449 if err != nil { 450 return errs.Wrap(err, "Could not fetch branch: %s", pj.BranchName()) 451 } 452 453 err = UpdateBranchCommit(branch.BranchID, commitID, auth) 454 if err != nil { 455 return errs.Wrap(err, "Could no update branch to commit %s", commitID.String()) 456 } 457 458 return nil 459 } 460 461 // UpdateBranchCommit updates the commit that a branch is pointed at 462 func UpdateBranchCommit(branchID strfmt.UUID, commitID strfmt.UUID, auth *authentication.Auth) error { 463 changeset := &mono_models.BranchEditable{ 464 CommitID: &commitID, 465 } 466 467 return updateBranch(branchID, changeset, auth) 468 } 469 470 // UpdateBranchTracking updates the tracking information for the given branch 471 func UpdateBranchTracking(branchID, commitID, trackingBranchID strfmt.UUID, trackingType TrackingType, auth *authentication.Auth) error { 472 tracking := trackingType.String() 473 changeset := &mono_models.BranchEditable{ 474 CommitID: &commitID, 475 TrackingType: &tracking, 476 Tracks: &trackingBranchID, 477 } 478 479 return updateBranch(branchID, changeset, auth) 480 } 481 482 func updateBranch(branchID strfmt.UUID, changeset *mono_models.BranchEditable, auth *authentication.Auth) error { 483 authClient, err := auth.Client() 484 if err != nil { 485 return errs.Wrap(err, "Could not get auth client") 486 } 487 488 params := vcsClient.NewUpdateBranchParams() 489 params.SetBranchID(branchID) 490 params.SetBranch(changeset) 491 492 _, err = authClient.VersionControl.UpdateBranch(params, auth.ClientAuth()) 493 if err != nil { 494 if _, ok := err.(*version_control.UpdateBranchForbidden); ok { 495 return &ErrUpdateBranchAuth{locale.NewExternalError("err_branch_update_auth", "Branch update failed with authentication error")} 496 } 497 return locale.NewError("err_update_branch", "", api.ErrorMessageFromPayload(err)) 498 } 499 return nil 500 } 501 502 func DeleteBranch(branchID strfmt.UUID, auth *authentication.Auth) error { 503 authClient, err := auth.Client() 504 if err != nil { 505 return errs.Wrap(err, "Could not get auth client") 506 } 507 508 params := vcsClient.NewDeleteBranchParams() 509 params.SetBranchID(branchID) 510 511 _, err = authClient.VersionControl.DeleteBranch(params, auth.ClientAuth()) 512 if err != nil { 513 return locale.WrapError(err, "err_delete_branch", "Could not delete branch") 514 } 515 516 return nil 517 } 518 519 // UpdateProjectBranchCommitByName updates the vcs branch for a project given by its namespace with a new commitID 520 func UpdateProjectBranchCommit(pj ProjectInfo, commitID strfmt.UUID, auth *authentication.Auth) error { 521 pjm, err := LegacyFetchProjectByName(pj.Owner(), pj.Name()) 522 if err != nil { 523 return errs.Wrap(err, "Could not fetch project") 524 } 525 526 return UpdateProjectBranchCommitWithModel(pjm, pj.BranchName(), commitID, auth) 527 } 528 529 // UpdateProjectBranchCommitByName updates the vcs branch for a project given by its namespace with a new commitID 530 func UpdateProjectBranchCommitWithModel(pjm *mono_models.Project, branchName string, commitID strfmt.UUID, auth *authentication.Auth) error { 531 branch, err := BranchForProjectByName(pjm, branchName) 532 if err != nil { 533 return errs.Wrap(err, "Could not fetch branch: %s", branchName) 534 } 535 536 err = UpdateBranchCommit(branch.BranchID, commitID, auth) 537 if err != nil { 538 return errs.Wrap(err, "Could update branch %s to commitID %s", branchName, commitID.String()) 539 } 540 return nil 541 } 542 543 // CommitInitial creates a root commit for a new branch 544 func CommitInitial(hostPlatform string, langName, langVersion string, auth *authentication.Auth) (strfmt.UUID, error) { 545 platformID, err := hostPlatformToPlatformID(hostPlatform) 546 if err != nil { 547 return "", err 548 } 549 550 var changes []*mono_models.CommitChangeEditable 551 552 if langName != "" { 553 versionConstraints, err := versionStringToConstraints(langVersion) 554 if err != nil { 555 return "", errs.Wrap(err, "Could not process version string into constraints") 556 } 557 c := &mono_models.CommitChangeEditable{ 558 Operation: string(OperationAdded), 559 Namespace: NewNamespaceLanguage().String(), 560 Requirement: langName, 561 VersionConstraints: versionConstraints, 562 } 563 changes = append(changes, c) 564 } 565 566 c := &mono_models.CommitChangeEditable{ 567 Operation: string(OperationAdded), 568 Namespace: NewNamespacePlatform().String(), 569 Requirement: platformID, 570 } 571 changes = append(changes, c) 572 573 commit := &mono_models.CommitEditable{ 574 Changeset: changes, 575 Message: locale.T("commit_message_add_initial"), 576 } 577 params := vcsClient.NewAddCommitParams() 578 params.SetCommit(commit) 579 580 res, err := mono.New().VersionControl.AddCommit(params, auth.ClientAuth()) 581 if err != nil { 582 return "", locale.WrapError(err, "err_add_commit", "", api.ErrorMessageFromPayload(err)) 583 } 584 585 return res.Payload.CommitID, nil 586 } 587 588 func versionStringToConstraints(version string) ([]*mono_models.Constraint, error) { 589 requirements, err := bpModel.VersionStringToRequirements(version) 590 if err != nil { 591 return nil, errs.Wrap(err, "Unable to process version string into requirements") 592 } 593 594 constraints := make([]*mono_models.Constraint, len(requirements)) 595 for i, constraint := range requirements { 596 constraints[i] = &mono_models.Constraint{ 597 Comparator: constraint[types.VersionRequirementComparatorKey], 598 Version: constraint[types.VersionRequirementVersionKey], 599 } 600 } 601 return constraints, nil 602 } 603 604 type indexedCommits map[string]string // key == commit id / val == parent id 605 606 func makeIndexedCommits(cs []*mono_models.Commit) indexedCommits { 607 m := make(indexedCommits) 608 609 for _, c := range cs { 610 m[string(c.CommitID)] = string(c.ParentCommitID) 611 } 612 613 return m 614 } 615 616 // countBetween returns 0 if same or if unable to determine the count. 617 // Caution: Currently, the logic does not verify that the first commit is "before" the last commit. 618 func (cs indexedCommits) countBetween(first, last string) (int, error) { 619 if first == last { 620 return 0, nil 621 } 622 623 if last == "" { 624 return 0, locale.NewError("err_commit_count_missing_last") 625 } 626 627 if first != "" { 628 if _, ok := cs[first]; !ok { 629 return 0, locale.WrapError(ErrCommitCountUnknowable, "err_commit_count_cannot_find_first", "", first) 630 } 631 } 632 633 next := last 634 var ct int 635 for ct <= len(cs) { 636 if next == first { 637 return ct, nil 638 } 639 640 ct++ 641 642 var ok bool 643 next, ok = cs[next] 644 if !ok { 645 return 0, locale.WrapError(ErrCommitCountUnknowable, "err_commit_count_cannot_find", next) 646 } 647 } 648 649 return ct, nil 650 } 651 652 func ResolveRequirementNameAndVersion(name, version string, word int, namespace Namespace, auth *authentication.Auth) (string, string, error) { 653 if namespace.Type() == NamespacePlatform { 654 platform, err := FetchPlatformByDetails(name, version, word, auth) 655 if err != nil { 656 return "", "", errs.Wrap(err, "Could not fetch platform") 657 } 658 name = platform.PlatformID.String() 659 version = "" 660 } 661 662 return name, version, nil 663 } 664 665 func ChangesetFromRequirements(op Operation, reqs []*gqlModel.Requirement) Changeset { 666 var changeset Changeset 667 668 for _, req := range reqs { 669 change := &mono_models.CommitChangeEditable{ 670 Operation: string(op), 671 Namespace: req.Namespace, 672 Requirement: req.Requirement, 673 VersionConstraint: req.VersionConstraint, 674 } 675 676 changeset = append(changeset, change) 677 } 678 679 return changeset 680 } 681 682 func TrackBranch(source, target *mono_models.Project, auth *authentication.Auth) error { 683 authClient, err := auth.Client() 684 if err != nil { 685 return errs.Wrap(err, "Could not get auth client") 686 } 687 688 sourceBranch, err := DefaultBranchForProject(source) 689 if err != nil { 690 return err 691 } 692 693 targetBranch, err := DefaultBranchForProject(target) 694 if err != nil { 695 return err 696 } 697 698 trackingType := mono_models.BranchEditableTrackingTypeNotify 699 700 updateParams := vcsClient.NewUpdateBranchParams() 701 branch := &mono_models.BranchEditable{ 702 TrackingType: &trackingType, 703 Tracks: &sourceBranch.BranchID, 704 } 705 updateParams.SetBranch(branch) 706 updateParams.SetBranchID(targetBranch.BranchID) 707 708 _, err = authClient.VersionControl.UpdateBranch(updateParams, auth.ClientAuth()) 709 if err != nil { 710 msg := api.ErrorMessageFromPayload(err) 711 return locale.WrapError(err, msg) 712 } 713 return nil 714 } 715 716 func GetRootBranches(branches mono_models.Branches) mono_models.Branches { 717 var rootBranches mono_models.Branches 718 for _, branch := range branches { 719 // Account for forked projects where the root branches contain 720 // a tracking ID that is not in the current project's branches 721 if branch.Tracks != nil && containsBranch(branch.Tracks, branches) { 722 continue 723 } 724 rootBranches = append(rootBranches, branch) 725 } 726 return rootBranches 727 } 728 729 func containsBranch(id *strfmt.UUID, branches mono_models.Branches) bool { 730 for _, branch := range branches { 731 if branch.BranchID.String() == id.String() { 732 return true 733 } 734 } 735 return false 736 } 737 738 // GetBranchChildren returns the direct children of the given branch 739 func GetBranchChildren(branch *mono_models.Branch, branches mono_models.Branches) mono_models.Branches { 740 var children mono_models.Branches 741 if branch == nil { 742 return children 743 } 744 745 for _, b := range branches { 746 if b.Tracks != nil && b.Tracks.String() == branch.BranchID.String() { 747 children = append(children, b) 748 } 749 } 750 return children 751 } 752 753 func GetRevertCommit(from, to strfmt.UUID, auth *authentication.Auth) (*mono_models.Commit, error) { 754 params := vcsClient.NewGetRevertCommitParams() 755 params.SetCommitFromID(from) 756 params.SetCommitToID(to) 757 758 client := mono.New() 759 var err error 760 if auth.Authenticated() { 761 client, err = auth.Client() 762 if err != nil { 763 return nil, errs.Wrap(err, "Could not get auth client") 764 } 765 } 766 res, err := client.VersionControl.GetRevertCommit(params, auth.ClientAuth()) 767 if err != nil { 768 return nil, locale.WrapError(err, "err_get_revert_commit", "Could not revert from commit ID {{.V0}} to {{.V1}}", from.String(), to.String()) 769 } 770 771 return res.Payload, nil 772 } 773 774 func RevertCommitWithinHistory(from, to, latest strfmt.UUID, auth *authentication.Auth) (*mono_models.Commit, error) { 775 targetCommit := from 776 preposition := "" 777 if from == latest { // reverting to 778 targetCommit = to 779 preposition = " to" // need leading whitespace 780 } 781 ok, err := CommitWithinCommitHistory(latest, targetCommit, auth) 782 if err != nil { 783 return nil, errs.Wrap(err, "API communication failed.") 784 } 785 if !ok { 786 return nil, locale.WrapError(err, "err_revert_commit_within_history_not_in", "The commit being reverted{{.V0}} is not within the current commit's history.", preposition) 787 } 788 789 return RevertCommit(from, to, latest, auth) 790 } 791 792 func RevertCommit(from, to, latest strfmt.UUID, auth *authentication.Auth) (*mono_models.Commit, error) { 793 revertCommit, err := GetRevertCommit(from, to, auth) 794 if err != nil { 795 return nil, err 796 } 797 if from != latest { 798 // The platform assumes revert commits are reverting to a particular commit, rather than 799 // reverting the changes in a commit. As a result, commit messages are of the form "Revert to 800 // commit X" and parent commit IDs are X. Change the message to reflect the fact we're 801 // reverting changes from X and change the parent to be the latest commit so that the revert 802 // commit applies to the latest project commit. 803 revertCommit.Message = locale.Tl("revert_commit", "Revert commit {{.V0}}", from.String()) 804 revertCommit.ParentCommitID = latest 805 } 806 807 addCommit, err := AddRevertCommit(revertCommit, auth) 808 if err != nil { 809 return nil, err 810 } 811 return addCommit, nil 812 } 813 814 func MergeCommit(commitReceiving, commitWithChanges strfmt.UUID) (*mono_models.MergeStrategies, error) { 815 params := vcsClient.NewMergeCommitsParams() 816 params.SetCommitReceivingChanges(commitReceiving) 817 params.SetCommitWithChanges(commitWithChanges) 818 params.SetHTTPClient(api.NewHTTPClient()) 819 820 res, noContent, err := mono.New().VersionControl.MergeCommits(params) 821 if err != nil { 822 if api.ErrorCodeFromPayload(err) == 409 { 823 logging.Debug("Received 409 from MergeCommit: %s", err.Error()) 824 return nil, ErrMergeCommitInHistory 825 } 826 return nil, locale.WrapError(err, "err_api_mergecommit", api.ErrorMessageFromPayload(err)) 827 } 828 if noContent != nil { 829 return nil, ErrMergeFastForward 830 } 831 832 return res.Payload, nil 833 } 834 835 func MergeRequired(commitReceiving, commitWithChanges strfmt.UUID) (bool, error) { 836 _, err := MergeCommit(commitReceiving, commitWithChanges) 837 if err != nil { 838 if errors.Is(err, ErrMergeFastForward) || errors.Is(err, ErrMergeCommitInHistory) { 839 return false, nil 840 } 841 return false, err 842 } 843 return true, nil 844 } 845 846 func GetCommit(commitID strfmt.UUID, auth *authentication.Auth) (*mono_models.Commit, error) { 847 params := vcsClient.NewGetCommitParams() 848 params.SetCommitID(commitID) 849 params.SetHTTPClient(api.NewHTTPClient()) 850 851 client := mono.New() 852 var err error 853 if auth.Authenticated() { 854 client, err = auth.Client() 855 if err != nil { 856 return nil, errs.Wrap(err, "Could not get auth client") 857 } 858 } 859 res, err := client.VersionControl.GetCommit(params, auth.ClientAuth()) 860 if err != nil { 861 return nil, locale.WrapError(err, "err_get_commit", "Could not get commit from ID: {{.V0}}", commitID.String()) 862 } 863 return res.Payload, nil 864 } 865 866 func GetCommitWithinCommitHistory(currentCommitID, targetCommitID strfmt.UUID, auth *authentication.Auth) (*mono_models.Commit, error) { 867 commit, err := GetCommit(targetCommitID, auth) 868 if err != nil { 869 return nil, err 870 } 871 872 ok, err := CommitWithinCommitHistory(currentCommitID, targetCommitID, auth) 873 if err != nil { 874 return nil, errs.Wrap(err, "API communication failed.") 875 } 876 if !ok { 877 return nil, ErrCommitNotInHistory 878 } 879 880 return commit, nil 881 } 882 883 func AddRevertCommit(commit *mono_models.Commit, auth *authentication.Auth) (*mono_models.Commit, error) { 884 params := vcsClient.NewAddCommitParams() 885 886 editableCommit, err := commitToCommitEditable(commit) 887 if err != nil { 888 return nil, locale.WrapError(err, "err_convert_commit", "Could not convert commit data") 889 } 890 params.SetCommit(editableCommit) 891 892 res, err := mono.New().VersionControl.AddCommit(params, auth.ClientAuth()) 893 if err != nil { 894 return nil, locale.WrapError(err, "err_add_revert_commit", "Could not add revert commit") 895 } 896 return res.Payload, nil 897 } 898 899 func commitToCommitEditable(from *mono_models.Commit) (*mono_models.CommitEditable, error) { 900 editableData, err := from.MarshalBinary() 901 if err != nil { 902 return nil, locale.WrapError(err, "err_commit_marshal", "Could not marshall commit data") 903 } 904 905 commit := &mono_models.CommitEditable{} 906 err = commit.UnmarshalBinary(editableData) 907 if err != nil { 908 return nil, locale.WrapError(err, "err_commit_unmarshal", "Could not unmarshal commit data") 909 } 910 return commit, nil 911 }