func StoryChangesFromCommits(commits []*git.Commit) ([]*StoryChangeGroup, error) { // Group by Change-Id. changeGroups := GroupCommitsByChangeId(commits) // Fix the commit sources. if err := git.FixCommitSources(commits); err != nil { return nil, err } // Return the changes grouped by story ID. return GroupChangesByStoryId(changeGroups), nil }
func run(remoteName, pushURL string) error { // Load the git-related SalsaFlow config. gitConfig, err := git.LoadConfig() if err != nil { return err } // Load the other necessary SalsaFlow config. repoConfig, err := repo.LoadConfig() if err != nil { return err } enabledTimestamp := repoConfig.SalsaFlowEnabledTimestamp // Only check the project remote. if remoteName != gitConfig.RemoteName { log.Log( fmt.Sprintf( "Not pushing to the main project remote (%v), check skipped", gitConfig.RemoteName)) return nil } // The commits that are being pushed are listed on stdin. // The format is <local ref> <local sha1> <remote ref> <remote sha1>, // so we parse the input and collect all the local hexshas. var coreRefs = []string{ "refs/heads/" + gitConfig.TrunkBranchName, "refs/heads/" + gitConfig.ReleaseBranchName, "refs/heads/" + gitConfig.StagingBranchName, "refs/heads/" + gitConfig.StableBranchName, } parseTask := "Parse the hook input" var revRanges []*revisionRange scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { var ( line = scanner.Text() parts = strings.Split(line, " ") ) if len(parts) != 4 { return errs.NewError(parseTask, errors.New("invalid input line: "+line)) } localRef, localSha, remoteRef, remoteSha := parts[0], parts[1], parts[2], parts[3] // Skip the refs that are being deleted. if localSha == git.ZeroHash { continue } // Check only updates to the core branches, // i.e. trunk, release, client or master. var isCoreBranch bool for _, ref := range coreRefs { if remoteRef == ref { isCoreBranch = true } } if !isCoreBranch { continue } // Make sure the reference is up to date. // In this case the reference is not up to date when // the remote hash cannot be found in the local clone. if remoteSha != git.ZeroHash { task := fmt.Sprintf("Make sure remote ref '%s' is up to date", remoteRef) if _, err := git.Run("cat-file", "-t", remoteSha); err != nil { hint := fmt.Sprintf(` Commit %v does not exist locally. This is probably because '%v' is not up to date. Please update the reference from the remote repository, perhaps by executing 'git pull'. `, remoteSha, remoteRef) return errs.NewErrorWithHint(task, err, hint) } } // Append the revision range for this input line. var revRange *revisionRange if remoteSha == git.ZeroHash { // In case we are pushing a new branch, check commits up to trunk. // There is probably no better guess that we can do in general. revRange = &revisionRange{gitConfig.TrunkBranchName, localRef} } else { // Otherwise check the commits that are new compared to the remote ref. revRange = &revisionRange{remoteSha, localRef} } revRanges = append(revRanges, revRange) } if err := scanner.Err(); err != nil { return errs.NewError(parseTask, err) } // Check the missing Story-Id tags. var missing []*git.Commit for _, revRange := range revRanges { // Get the commit objects for the relevant range. task := "Get the commit objects to be pushed" commits, err := git.ShowCommitRange(fmt.Sprintf("%v..%v", revRange.From, revRange.To)) if err != nil { return errs.NewError(task, err) } // Check every commit in the range. for _, commit := range commits { // Do not check merge commits. if commit.Merge != "" { continue } // Do not check commits that happened before SalsaFlow. if commit.AuthorDate.Before(enabledTimestamp) { continue } // Check the Story-Id tag. if commit.StoryIdTag == "" { missing = append(missing, commit) } } } // Prompt for confirmation in case that is needed. if len(missing) != 0 { // Fill in the commit sources. task := "Fix commit sources" if err := git.FixCommitSources(missing); err != nil { return errs.NewError(task, err) } // Prompt the user for confirmation. task = "Prompt the user for confirmation" confirmed, err := promptUserForConfirmation(missing) if err != nil { return errs.NewError(task, err) } if !confirmed { return prompt.ErrCanceled } } return nil }
func hook() error { // There are always 3 arguments passed to this hook. prevRef, newRef, flag := os.Args[1], os.Args[2], os.Args[3] // Return in case prevRef is the zero hash since that means // that this hook is being run right after 'git clone'. if prevRef == git.ZeroHash { return nil } // Return in case flag is '0'. That signals retrieving a file from the index. if flag == "0" { return nil } // Return unless the new HEAD is a core branch. isCore, err := isCoreBranchHash(newRef) if err != nil { return err } if !isCore { return nil } // Return also in case we are doing something with a temporary branch. isNewRefTemp, err := isTempBranchHash(newRef) if err != nil { return err } isPrevRefTemp, err := isTempBranchHash(prevRef) if err != nil { return err } if isNewRefTemp || isPrevRefTemp { return nil } // Get the relevant commits. // These are the commits specified by newRef..prevRef, e.g. trunk..story/foobar. commits, err := git.ShowCommitRange(fmt.Sprintf("%v..%v", newRef, prevRef)) if err != nil { return err } // Drop commits that happened before SalsaFlow bootstrap. repoConfig, err := repo.LoadConfig() if err != nil { return err } enabledTimestamp := repoConfig.SalsaFlowEnabledTimestamp commits = git.FilterCommits(commits, func(commit *git.Commit) bool { return commit.AuthorDate.After(enabledTimestamp) }) // Collect the commits with missing Story-Id tag. missing := make([]*git.Commit, 0, len(commits)) for _, commit := range commits { // Skip merge commits. if commit.Merge != "" { continue } // Add the commit in case Story-Id tag is not set. if commit.StoryIdTag == "" { missing = append(missing, commit) } } if len(missing) == 0 { return nil } // Fix commit sources. if err := git.FixCommitSources(missing); err != nil { return err } // Print the warning. return printWarning(missing) }