func resolveCommitParent(commit *git.Commit) result.Result { parent := commit.Parent(0) if parent == nil { return result.NewFailure(errors.New(noParentError)) } return result.NewSuccess(parent) }
// Parse commits from commitish string, populating a CommitRange. If a // single commit is matched, it is paired with its first parent commit // or resolves to an error // // return result.Result<*git.Commit, error> func ResolveCommits(repo *git.Repository, commitish string) result.Result { return result.NewResult(repo.Revparse(commitish)).FlatMap(func(value interface{}) result.Result { spec := value.(*git.Revspec) return resolveCommit(repo, spec.From()).FlatMap(func(f interface{}) result.Result { fromCommit := f.(*git.Commit) return resolveCommit(repo, spec.To()).Analysis(func(t interface{}) result.Result { toCommit := t.(*git.Commit) return result.NewSuccess(&CommitRange{fromCommit, toCommit}) }, func(err error) result.Result { return resolveCommitParent(fromCommit).FlatMap(func(parent interface{}) result.Result { return result.NewSuccess(&CommitRange{parent.(*git.Commit), fromCommit}) }) }) }) }) }
// Count comments on commit // @return result.Result<uint16, error> func CommentCountOnCommit(repo *git.Repository, commit string) result.Result { var count uint16 = 0 return gg.CommitCommentRefIterator(repo, commit, func(ref *git.Reference) { count += 1 }).FlatMap(func(value interface{}) result.Result { return result.NewSuccess(count) }) }
// Add a fetch refspec to a remote. Return true if added. // @return result.Result<bool, error> func AddFetch(repo *git.Repository, remote *git.Remote, fetchRef string) result.Result { f := result.NewResult(remote.FetchRefspecs()) return f.FlatMap(func(fetches interface{}) result.Result { if !contains(fetches.([]string), fetchRef) { return BoolResult(true, repo.Remotes.AddFetch(remote.Name(), fetchRef)) } return result.NewSuccess(false) }) }
// Parse a property string and create a person. The expected format is: // ``` // Name <*****@*****.**> // ``` // If a valid person cannot be created, an error is returned instead // @return result.Result<*Person, error> func CreatePerson(properties string) result.Result { fullRe := regexp.MustCompile(`(?:(.*)\s)?<(.*@.*)>(?:\s([0-9]+)\s([\-+][0-9]{4}))?`) match := fullRe.FindStringSubmatch(properties) invalidErr := result.NewFailure(errors.New(invalidPersonError)) if len(match) == 0 { return invalidErr } name, email := match[1], match[2] timestamp := result.NewResult(strconv.ParseInt(match[3], 10, 64)) return timestamp.Analysis(func(value interface{}) result.Result { stamp := time.Unix(value.(int64), 0) person := &Person{match[1], match[2], stamp, match[4]} return result.NewSuccess(person) }, func(err error) result.Result { timestamp := time.Now() return result.NewSuccess(&Person{name, email, timestamp, timestamp.Format("-0700")}) }) }
// Finds a comment by ID // @return result.Result<*Comment, error> func CommentByID(repo *git.Repository, identifier string) result.Result { return gg.LookupBlob(repo, identifier, commentNotFoundError).FlatMap(func(blob interface{}) result.Result { return DeserializeComment(string(blob.(*git.Blob).Contents())) }).FlatMap(func(c interface{}) result.Result { comment := c.(*Comment) comment.ID = &identifier return result.NewSuccess(comment) }) }
func (p *Printer) PrintCommentsMatching(wd, text string) result.Result { return CommentsWithContent(wd, text).FlatMap(func(matches interface{}) result.Result { for _, comment := range matches.([]*gc.Comment) { p.pager.AddContent(p.formatter.FormatComment(comment, text)) } p.pager.Finish() return result.NewSuccess(true) }) }
// Add a push refspec to a remote. Return true if added. // @return result.Result<bool, error> func AddPush(repo *git.Repository, remote *git.Remote, pushRef string) result.Result { p := result.NewResult(remote.PushRefspecs()) return p.FlatMap(func(pushes interface{}) result.Result { if !contains(pushes.([]string), pushRef) { return BoolResult(true, repo.Remotes.AddPush(remote.Name(), pushRef)) } return result.NewSuccess(false) }) }
func comparisonStatus(code int) result.Result { switch code { case -1: return result.NewFailure(errors.New(upgradeToolError)) case 1: return result.NewFailure(errors.New(upgradeRepoError)) default: return result.NewSuccess(VersionStatusEqual) } }
// @return result.Result<*Diff, error> func diffCommits(repo *git.Repository, commitRange *gg.CommitRange, contextLines uint32) result.Result { comments := CommentsOnCommits(repo, commitRange.Commits()) diff := diffRange(repo, commitRange, contextLines) return result.Combine(func(values ...interface{}) result.Result { parentID := commitRange.Parent.Id().String() childID := commitRange.Child.Id().String() files := parseDiffForLines(values[0].(*git.Diff), values[1].(CommentSlice)) return result.NewSuccess(&Diff{files, parentID, childID}) }, diff, comments) }
// @return result.Result<VersionStatus, error> func writeVersion(repo *git.Repository, version string) result.Result { oid := result.NewResult(repo.CreateBlobFromBuffer([]byte(version))) return oid.FlatMap(func(oid interface{}) result.Result { path := filepath.Join(gg.CommentRefBase, versionRef) return result.NewResult(repo.References.Create(path, oid.(*git.Oid), false, upgradeMessage)) }).FlatMap(func(ref interface{}) result.Result { return result.NewSuccess(VersionStatusEqual) }) }
func validatedCommitForComment(repo *git.Repository, commit string) result.Result { return gg.ResolveSingleCommitHash(repo, commit).FlatMap(func(hash interface{}) result.Result { return CommentCountOnCommit(repo, *(hash.(*string))).FlatMap(func(count interface{}) result.Result { if count.(uint16) >= maxCommentsOnCommit { return result.NewFailure(errors.New(maxCommentError)) } return result.NewSuccess(hash) }) }) }
// @return result.Result<string, error> func readVersion(repo *git.Repository) result.Result { path := filepath.Join(gg.CommentRefBase, versionRef) ref := result.NewResult(repo.References.Lookup(path)) return ref.FlatMap(func(ref interface{}) result.Result { oid := ref.(*git.Reference).Target() return result.NewResult(repo.LookupBlob(oid)) }).FlatMap(func(blob interface{}) result.Result { contents := blob.(*git.Blob).Contents() return result.NewSuccess(string(contents)) }) }
// Finds all comments on a commit // @return result.Result<[]*Comment, error> func commentsOnCommit(repo *git.Repository, commit *git.Commit) result.Result { var comments []interface{} return gg.CommitCommentRefIterator(repo, commit.Id().String(), func(ref *git.Reference) { CommentFromRef(repo, ref.Name()).FlatMap(func(comment interface{}) result.Result { comments = append(comments, comment) return result.Result{} }) }).FlatMap(func(value interface{}) result.Result { return result.NewSuccess(comments) }) }
func DeserializeComment(content string) result.Result { const serializationErrorMessage = "Could not deserialize object into comment" blob := CreatePropertyBlob(content) comment := &Comment{} comment.Content = blob.Message comment.Commit = blob.Get(commitKey) comment.Author = blob.GetPerson(authorKey) comment.Amender = blob.GetPerson(amenderKey) comment.FileRef = blob.GetFileRef(fileRefKey) return result.NewSuccess(comment) }
// Configure a remote to fetch and push comment changes by default // @return result.Result<bool, error> func ConfigureRemoteForComments(repoPath, remoteName string) result.Result { return gg.WithRemote(repoPath, remoteName, func(remote *git.Remote) result.Result { success := func(values ...interface{}) result.Result { return result.NewSuccess(true) } return gg.WithRepository(repoPath, func(repo *git.Repository) result.Result { pushRef := commentDefaultPush fetchRef := fmt.Sprintf(commentDefaultFetch, remoteName) return result.Combine(success, gg.AddPush(repo, remote, pushRef), gg.AddFetch(repo, remote, fetchRef)) }) }) }
// Create a new comment on a commit, optionally with a file and line // @return result.Result<*string, error> func CreateComment(repoPath, commit, author, message string, fileRef *FileRef) result.Result { return gg.WithRepository(repoPath, func(repo *git.Repository) result.Result { return validatedCommitForComment(repo, commit).FlatMap(func(hash interface{}) result.Result { return commentAuthor(repoPath, author).FlatMap(func(author interface{}) result.Result { return NewComment(message, *(hash).(*string), fileRef, author.(*Person)) }).FlatMap(func(value interface{}) result.Result { comment := value.(*Comment) success := writeCommentToDisk(repo, comment) return success.FlatMap(func(value interface{}) result.Result { return result.NewSuccess(comment.ID) }) }) }) }) }
// Finds all comments on an array of commits // @return result.Result<[]*Comment, error> func CommentsOnCommits(repo *git.Repository, commits []*git.Commit) result.Result { results := make([]result.Result, len(commits)) for index, commit := range commits { results[index] = commentsOnCommit(repo, commit) } return result.Combine(func(values ...interface{}) result.Result { comments := make(CommentSlice, 0) for _, list := range values { for _, comment := range list.([]interface{}) { comments = append(comments, comment.(*Comment)) } } sort.Stable(comments) return result.NewSuccess(comments) }, results...) }
// Find all comments matching text // @return result.Result<[]*Comment, error> func CommentsWithContent(repoPath, content string) result.Result { return openIndex(repoPath, func(repo *git.Repository, index bleve.Index) result.Result { query := bleve.NewQueryStringQuery(content) request := bleve.NewSearchRequest(query) return result.NewResult(index.Search(request)).FlatMap(func(match interface{}) result.Result { hits := match.(*bleve.SearchResult).Hits comments := make([]*gc.Comment, len(hits)) for idx, hit := range hits { gc.CommentByID(repo, hit.ID).FlatMap(func(comment interface{}) result.Result { comments[idx] = comment.(*gc.Comment) return result.Result{} }) } return result.NewSuccess(comments) }) }) }
// @return result.Result<bool, error> func IndexComments(repoPath string) result.Result { return openIndex(repoPath, func(repo *git.Repository, index bleve.Index) result.Result { results := make([]result.Result, 0) batch := index.NewBatch() return gg.CommentRefIterator(repo, func(ref *git.Reference) { gc.CommentFromRef(repo, ref.Name()).FlatMap(func(c interface{}) result.Result { comment := c.(*gc.Comment) err := batch.Index(*comment.ID, commentIndex(comment)) results = append(results, gg.BoolResult(true, err)) return result.NewSuccess(true) }) }).FlatMap(func(value interface{}) result.Result { return result.Combine(func(values ...interface{}) result.Result { return gg.BoolResult(true, index.Batch(batch)) }, results...) }) }) }
// Write git object for a given comment and update the // comment refs // @return result.Result<*Comment, error> func writeCommentToDisk(repo *git.Repository, comment *Comment) result.Result { if comment.ID != nil { if err := deleteReference(repo, comment, *comment.ID); err != nil { return result.NewFailure(err) } } return gg.CreateBlob(repo, comment.Serialize()).FlatMap(func(oid interface{}) result.Result { id := fmt.Sprintf("%v", oid) return RefPath(comment, id).FlatMap(func(file interface{}) result.Result { commit := *comment.Commit message := fmt.Sprintf(defaultMessageFormat, commit[:7], id[:7]) return result.NewResult(repo.References.Create(file.(string), oid.(*git.Oid), false, message)) }).FlatMap(func(value interface{}) result.Result { comment.ID = &id return result.NewSuccess(comment) }) }) }
// Creates a new comment using provided content and author func NewComment(message string, commit string, fileRef *FileRef, author *Person) result.Result { const missingContentMessage = "No message content provided" const missingCommitMessage = "No commit provided" if len(message) == 0 { return result.NewFailure(errors.New(missingContentMessage)) } else if len(commit) == 0 { return result.NewFailure(errors.New(missingCommitMessage)) } return result.NewSuccess(&Comment{ author, message, author, &commit, nil, false, fileRef, }) }
// Converts an error into a failure result, // or nil into a success result func BoolResult(value bool, err error) result.Result { if err != nil { return result.NewFailure(err) } return result.NewSuccess(value) }
// Generate the path within refs for a given comment // // Comment refs are nested under refs/comments. The // format is as follows: // // ``` // refs/comments/[<commit prefix>]/[<rest of commit>]/[<comment id>] // ``` // // @return result.Result<string, error> func RefPath(comment *Comment, identifier string) result.Result { return gg.CommitRefDir(*comment.Commit).FlatMap(func(dir interface{}) result.Result { return result.NewSuccess(path.Join(dir.(string), identifier)) }) }
func getObjectId(value interface{}) result.Result { id := value.(git.Object).Id().String() return result.NewSuccess(&id) }