// GetFirstCommit returns the first commit that is included in the review
func (review differentialReview) GetFirstCommit(repo repository.Repo) *repository.Revision {
	var commits []string
	for _, hashPair := range review.Hashes {
		// We only care about the hashes for commits, which have exactly two
		// elements, the first of which is "gtcm".
		if len(hashPair) == 2 && hashPair[0] == commitHashType {
			commits = append(commits, hashPair[1])
		}
	}
	var commitTimestamps []int
	commitsByTimestamp := make(map[int]string)
	for _, commit := range commits {
		details, err := repo.GetDetails(repository.Revision(commit))
		if err == nil {
			timestamp, err := strconv.Atoi(details.Time)
			if err == nil {
				commitTimestamps = append(commitTimestamps, timestamp)
				// If there are multiple, equally old commits, then the last one wins.
				commitsByTimestamp[timestamp] = commit
			}
		}
	}
	if len(commitTimestamps) == 0 {
		return nil
	}
	sort.Ints(commitTimestamps)
	revision := repository.Revision(commitsByTimestamp[commitTimestamps[0]])
	return &revision
}
func (arc Arcanist) mirrorCommentsIntoReview(repo repository.Repo, review differentialReview, comments comment.CommentMap) {
	existingComments := review.LoadComments()
	newComments := comments.FilterOverlapping(existingComments)

	var lastCommitForLastDiff string
	var latestDiffForReview int
	commitToDiffMap := make(map[string]string)
	for _, diffIDString := range review.Diffs {
		lastCommit := findCommitForDiff(diffIDString)
		commitToDiffMap[lastCommit] = diffIDString
		diffID, err := strconv.Atoi(diffIDString)
		if err == nil && diffID > latestDiffForReview {
			lastCommitForLastDiff = lastCommit
			latestDiffForReview = diffID
		}
	}
	report := ci.GetLatestCIReport(repo.GetNotes(ci.Ref, repository.Revision(lastCommitForLastDiff)))

	log.Printf("The latest CI report for diff %d is %+v ", latestDiffForReview, report)
	if report.URL != "" {
		unitDiffProperty := differentialUnitDiffProperty{
			Name:   report.Agent,
			Link:   report.URL,
			Result: translateReportStatusToDifferentialUnitResult(report.Status),
		}
		// Note that although the unit tests property is a JSON object, Phabricator
		// expects there to be a list of such objects for any given diff. Therefore
		// we wrap the object in a list before marshaling it to send to the server.
		// TODO(ojarjur): We should take advantage of the fact that this is a list,
		// and include the latest CI report for each agent. That would allow us to
		// display results from multiple test runners in a code review.
		propertyBytes, err := json.Marshal([]differentialUnitDiffProperty{unitDiffProperty})
		if err == nil {
			err = arc.setDiffProperty(latestDiffForReview, unitDiffPropertyName, string(propertyBytes))
		}
		if err != nil {
			log.Fatal(err.Error())
		}
	}

	inlineRequests, commentRequests := review.buildCommentRequests(newComments, commitToDiffMap)
	for _, request := range inlineRequests {
		var response createInlineResponse
		runArcCommandOrDie("differential.createinline", request, &response)
		if response.Error != "" {
			log.Println(response.ErrorMessage)
		}
	}
	for _, request := range commentRequests {
		var response createCommentResponse
		runArcCommandOrDie("differential.createcomment", request, &response)
		if response.Error != "" {
			log.Println(response.ErrorMessage)
		}
	}
}
// updateReviewDiffs updates the status of a differential review so that it matches the state of the repo.
//
// This consists of making sure the latest commit pushed to the review ref has a corresponding
// diff in the differential review.
func (arc Arcanist) updateReviewDiffs(repo repository.Repo, review differentialReview, headCommit string, req request.Request, comments map[string]comment.Comment) {
	if review.isClosed() {
		return
	}

	headRevision := repository.Revision(headCommit)
	mergeBase, err := repo.GetMergeBase(repository.Revision(req.TargetRef), headRevision)
	if err != nil {
		// This can happen if the target ref has been deleted while we were performing the updates.
		return
	}

	for _, hashPair := range review.Hashes {
		if len(hashPair) == 2 && hashPair[0] == commitHashType && hashPair[1] == headCommit {
			// The review already has the hash of the HEAD commit, so we have nothing to do beyond mirroring comments
			// and build status if applicable
			arc.mirrorCommentsIntoReview(repo, review, comments)
			return
		}
	}

	diff, err := arc.createDifferentialDiff(repo, mergeBase, headRevision, req, review.Diffs)
	if err != nil {
		log.Fatal(err)
	}
	if diff == nil {
		// This means that phabricator silently refused to create the diff. Just move on.
		return
	}

	updateRequest := differentialUpdateRevisionRequest{ID: review.ID, DiffID: strconv.Itoa(diff.ID)}
	var updateResponse differentialUpdateRevisionResponse
	runArcCommandOrDie("differential.updaterevision", updateRequest, &updateResponse)
	if updateResponse.Error != "" {
		log.Fatal(updateResponse.ErrorMessage)
	}
}
func (arc Arcanist) mirrorCommentsIntoReview(repo repository.Repo, review differentialReview, comments comment.CommentMap) {
	existingComments := review.LoadComments()
	newComments := comments.FilterOverlapping(existingComments)

	var lastCommitForLastDiff string
	var latestDiffForReview string
	commitToDiffMap := make(map[string]string)
	for _, diffIDString := range review.Diffs {
		lastCommit := findCommitForDiff(diffIDString)
		commitToDiffMap[lastCommit] = diffIDString
		if diffIDString > latestDiffForReview {
			lastCommitForLastDiff = lastCommit
			latestDiffForReview = diffIDString
		}
	}
	report := ci.GetLatestCIReport(repo.GetNotes(ci.Ref, repository.Revision(lastCommitForLastDiff)))

	log.Printf("The latest CI report for diff %s is %+v ", latestDiffForReview, report)
	if report.URL != "" {
		updateUnitResultsRequest := differentialUpdateUnitResultsRequest{
			DiffID: latestDiffForReview,
			Result: report.Status,
			Link:   report.URL,
			//TODO(ckerur): Link does not work for some reason. Remove putting URL in Message once it does
			Message: report.URL,
		}
		var unitResultsResponse differentialUpdateUnitResultsResponse
		runArcCommandOrDie("differential.updateunitresults", updateUnitResultsRequest, &unitResultsResponse)
		if unitResultsResponse.Error != "" {
			log.Fatal(unitResultsResponse.ErrorMessage)
		}
	}

	inlineRequests, commentRequests := review.buildCommentRequests(newComments, commitToDiffMap)
	for _, request := range inlineRequests {
		var response createInlineResponse
		runArcCommandOrDie("differential.createinline", request, &response)
		if response.Error != "" {
			log.Println(response.ErrorMessage)
		}
	}
	for _, request := range commentRequests {
		var response createCommentResponse
		runArcCommandOrDie("differential.createcomment", request, &response)
		if response.Error != "" {
			log.Println(response.ErrorMessage)
		}
	}
}
func TestMirrorRepo(t *testing.T) {
	repo := mockRepo{
		Notes: map[repository.Revision][]repository.Note{
			repository.Revision(testRevision): []repository.Note{
				repository.Note(testReviewNote),
			},
		},
	}
	tool := mockReviewTool{make(map[repository.Revision]request.Request)}
	syncToRemote := true
	mirrorRepoToReview(repo, &tool, syncToRemote)
	if len(tool.Requests) != 1 || !validateExpectedRequest(expectedRequest, tool.Requests[testRevision]) {
		t.Errorf("Review requests are not what we expected: %v", tool.Requests)
	}
}
func (repo mockRepo) GetMergeBase(from, to repository.Revision) (repository.Revision, error) {
	return repository.Revision(""), nil
}
func (repo mockRepo) ListNotedRevisions(ref repository.NotesRef) []repository.Revision {
	return []repository.Revision{
		repository.Revision(testRevision),
	}
}
// EnsureRequestExists runs the "arcanist" command-line tool to create a Differential diff for the given request, if one does not already exist.
func (arc Arcanist) EnsureRequestExists(repo repository.Repo, revision repository.Revision, req request.Request, comments map[string]comment.Comment) {

	// If this revision has been previously closed shortcut all processing
	if closedRevisionsMap[revision] {
		return
	}

	mergeBase, err := repo.GetMergeBase(repository.Revision(req.TargetRef), revision)
	if err != nil {
		// There are lots of reasons that we might not be able to compute a merge base,
		// (e.g. the revision already being merged in, or being dropped and garbage collected),
		// but they all indicate that the review request is no longer valid.
		log.Printf("Ignoring review request '%v', because we could not compute a merge base", req)
		return
	}

	existingReviews := arc.listDifferentialReviewsOrDie(req.ReviewRef, revision)
	if mergeBase == revision {
		// The change has already been merged in, so we should simply close any open reviews.
		for _, review := range existingReviews {
			if !review.isClosed() {
				review.close()
			}
		}
		closedRevisionsMap[revision] = true
		return
	}

	headDetails, err := repo.GetDetails(repository.Revision(req.ReviewRef))
	if err != nil {
		// The given review ref has been deleted (or never existed), but the change wasn't merged.
		// TODO(ojarjur): We should mark the existing reviews as abandoned.
		log.Printf("Ignoring review because the review ref '%s' does not exist", req.ReviewRef)
		return
	}

	if len(existingReviews) > 0 {
		// The change is still pending, but we already have existing reviews, so we should just update those.
		for _, review := range existingReviews {
			arc.updateReviewDiffs(repo, review, headDetails.Commit, req, comments)
		}
		return
	}

	diff, err := arc.createDifferentialDiff(repo, mergeBase, revision, req, []string{})
	if err != nil {
		log.Fatal(err)
	}
	if diff == nil {
		// The revision is already merged in, ignore it.
		return
	}
	rev, err := arc.createDifferentialRevision(repo, revision, diff.ID, req)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Created diff %v and revision %v for the review of %s", diff, rev, revision)

	// If the review already contains multiple commits by the time we mirror it, then
	// we need to ensure that at least the first and last ones are added.
	existingReviews = arc.listDifferentialReviewsOrDie(req.ReviewRef, revision)
	for _, review := range existingReviews {
		arc.updateReviewDiffs(repo, review, headDetails.Commit, req, comments)
	}
}