func runMain() (err error) { var ( task string stderr *bytes.Buffer ) defer func() { // Print error details. if err != nil { log.FailWithDetails(task, stderr) } }() // Fetch the remote repository unless we are restricted to the local branches only. if !flagLocalOnly { task = "Fetch the remote repository" log.Run(task) stderr, err = git.UpdateRemotes(config.OriginName) if err != nil { return } } // Get the list of story references. task = "Collect all story branches" log.Run(task) localRefs, remoteRefs, stderr, err := git.ListStoryRefs() if err != nil { return } var refs []string switch { case flagLocalOnly: refs = localRefs case flagRemoteOnly: refs = remoteRefs default: refs = append(localRefs, remoteRefs...) } if len(refs) == 0 { task = "" log.Println("\nNo story branches found, exiting...") return } // Collect all the story IDs. idMap := make(map[string]struct{}) for _, ref := range refs { // This cannot fail here since we got the refs using ListStoryRefs. id, _ := git.RefToStoryId(ref) idMap[id] = struct{}{} } var ids []string for id := range idMap { ids = append(ids, id) } // Get the list of active story IDs. activeIds, err := modules.GetIssueTracker().SelectActiveStoryIds(ids) if err != nil { return } ids = activeIds // Select only the refs that can be safely deleted. refs = selectInactiveRefs(refs, ids) if len(refs) == 0 { task = "" log.Println("\nThere are no branches to be deleted, exiting...") return } // Sort the refs. sort.Sort(sort.StringSlice(refs)) // Prompt the user to confirm the delete operation. var ( toDeleteLocally []string toDeleteRemotely []string ok bool ) // Go through the local branches. if strings.HasPrefix(refs[0], "refs/heads/") { fmt.Println("\n---> Local branches\n") } for len(refs) > 0 { ref := refs[0] if !strings.HasPrefix(ref, "refs/heads/") { break } branch := ref[len("refs/heads/"):] question := fmt.Sprintf("Delete local branch '%v'", branch) ok, err = prompt.Confirm(question) if err != nil { return } if ok { toDeleteLocally = append(toDeleteLocally, branch) } refs = refs[1:] } // All that is left are remote branches. if len(refs) != 0 { fmt.Println("\n---> Remote branches\n") } for _, ref := range refs { branch := ref[len("refs/remotes/origin/"):] question := fmt.Sprintf("Delete remote branch '%v'", branch) ok, err = prompt.Confirm(question) if err != nil { return } if ok { toDeleteRemotely = append(toDeleteRemotely, branch) } } fmt.Println() if len(toDeleteLocally) == 0 && len(toDeleteRemotely) == 0 { task = "" fmt.Println("No branches selected, exiting...") return } // Delete the local branches. if len(toDeleteLocally) != 0 { task = "Delete the chosen local branches" log.Run(task) // Remember the position of the branches to be deleted. // This is used in case we need to perform a rollback. var ( currentPositions []string hexsha string ) for _, branchName := range toDeleteLocally { hexsha, stderr, err = git.Hexsha("refs/heads/" + branchName) if err != nil { return } currentPositions = append(currentPositions, hexsha) } // Delete the selected local branches. args := append([]string{"-d"}, toDeleteLocally...) stderr, err = git.Branch(args...) if err != nil { return } defer func(taskMsg string) { // On error, try to restore the local branches that were deleted. if err != nil { log.Rollback(taskMsg) for i, branchName := range toDeleteLocally { out, ex := git.ResetKeep(branchName, currentPositions[i]) if ex != nil { log.FailWithDetails(task, out) } } } }(task) } // Delete the remote branches. if len(toDeleteRemotely) != 0 { task = "Delete the chosen remote branches" log.Run(task) var refs []string for _, branchName := range toDeleteRemotely { refs = append(refs, ":"+branchName) } stderr, err = git.Push(config.OriginName, refs...) } return }
func SetForBranch(ver *Version, branch string) (act action.Action, err error) { var mainTask = fmt.Sprintf("Bump version to %v for branch '%v'", ver, branch) // Make sure the repository is clean (don't check untracked files). task := "Make sure the repository is clean" if err := git.EnsureCleanWorkingTree(false); err != nil { return nil, errs.NewError(task, err) } // Remember the current branch. currentBranch, err := git.CurrentBranch() if err != nil { return nil, err } // Remember the current position of the target branch. task = fmt.Sprintf("Remember the position of branch '%v'", branch) originalPosition, err := git.Hexsha("refs/heads/" + branch) if err != nil { return nil, errs.NewError(task, err) } // Checkout the target branch. task = fmt.Sprintf("Checkout branch '%v'", branch) if err := git.Checkout(branch); err != nil { return nil, errs.NewError(task, err) } defer func() { // Checkout the original branch on return. task := fmt.Sprintf("Checkout branch '%v'", currentBranch) if ex := git.Checkout(currentBranch); ex != nil { if err == nil { err = ex } else { errs.LogError(task, ex) } } }() // Set the project version to the desired value. if err := Set(ver); err != nil { if ex, ok := err.(*scripts.ErrNotFound); ok { return nil, fmt.Errorf( "custom SalsaFlow script '%v' not found on branch '%v'", ex.ScriptName(), branch) } return nil, err } // Commit changes. _, err = git.RunCommand("commit", "-a", "-m", fmt.Sprintf("Bump version to %v", ver), "-m", fmt.Sprintf("Story-Id: %v", git.StoryIdUnassignedTagValue)) if err != nil { task := "Reset the working tree to the original state" if err := git.Reset("--keep"); err != nil { errs.LogError(task, err) } return nil, err } return action.ActionFunc(func() (err error) { // On rollback, reset the target branch to the original position. log.Rollback(mainTask) return git.ResetKeep(branch, originalPosition) }), nil }