// Refresh advises the review tool that the code being reviewed has changed, and to reload it.
//
// This corresponds to calling the diffusion.looksoon API.
func (arc Arcanist) Refresh(repo repository.Repo) {
	// We cannot determine the repo's callsign (the identifier Phabricator uses for the repo)
	// in all cases, but we can figure it out in the case that the mirror runs on the same
	// directories that Phabricator is using. In that scenario, the repo directories default
	// to being named "/var/repo/<CALLSIGN>", so if the repo path starts with that prefix then
	// we can try to strip out that prefix and use the rest as a callsign.
	if strings.HasPrefix(repo.GetPath(), defaultRepoDirPrefix) {
		possibleCallsign := strings.TrimPrefix(repo.GetPath(), defaultRepoDirPrefix)
		request := lookSoonRequest{Callsigns: []string{possibleCallsign}}
		response := make(map[string]interface{})
		runArcCommandOrDie("diffusion.looksoon", request, &response)
	}
}
Example #2
0
// LaunchEditor launches the default editor configured for the given repo. This
// method blocks until the editor command has returned.
//
// The specified filename should be a temporary file and provided as a relative path
// from the repo (e.g. "FILENAME" will be converted to ".git/FILENAME"). This file
// will be deleted after the editor is closed and its contents have been read.
//
// This method returns the text that was read from the temporary file, or
// an error if any step in the process failed.
func LaunchEditor(repo repository.Repo, fileName string) (string, error) {
	editor, err := repo.GetCoreEditor()
	if err != nil {
		return "", fmt.Errorf("Unable to detect default git editor: %v\n", err)
	}

	path := fmt.Sprintf("%s/.git/%s", repo.GetPath(), fileName)

	cmd, err := startInlineCommand(editor, path)
	if err != nil {
		// Running the editor directly did not work. This might mean that
		// the editor string is not a path to an executable, but rather
		// a shell command (e.g. "emacsclient --tty"). As such, we'll try
		// to run the command through bash, and if that fails, try with sh
		args := []string{"-c", fmt.Sprintf("%s %q", editor, path)}
		cmd, err = startInlineCommand("bash", args...)
		if err != nil {
			cmd, err = startInlineCommand("sh", args...)
		}
	}
	if err != nil {
		return "", fmt.Errorf("Unable to start editor: %v\n", err)
	}

	if err := cmd.Wait(); err != nil {
		return "", fmt.Errorf("Editing finished with error: %v\n", err)
	}

	output, err := ioutil.ReadFile(path)
	if err != nil {
		os.Remove(path)
		return "", fmt.Errorf("Error reading edited file: %v\n", err)
	}
	os.Remove(path)
	return string(output), err
}
Example #3
0
// createDifferentialDiff generates a Phabricator resource that represents a diff between two revisions.
//
// The generated resource includes metadata about how the diff was generated, and a JSON representation
// of the changes from the diff, as parsed by Phabricator.
func (arc Arcanist) createDifferentialDiff(repo repository.Repo, mergeBase, revision string, req request.Request, priorDiffs []string) (*differentialDiff, error) {
	repoCommitDetails, err := repo.GetCommitDetails(revision)
	if err != nil {
		return nil, err
	}
	var revisionDetails CommitDetails
	revisionDetails.Commit = revision
	revisionDetails.Author = repoCommitDetails.Author
	revisionDetails.AuthorEmail = repoCommitDetails.AuthorEmail
	revisionDetails.Tree = repoCommitDetails.Tree
	revisionDetails.Time = repoCommitDetails.Time
	revisionDetails.Parents = repoCommitDetails.Parents
	revisionDetails.Summary = repoCommitDetails.Summary
	changes, err := arc.getDiffChanges(repo, mergeBase, revision)
	if err != nil {
		return nil, err
	}
	createRequest := differentialCreateDiffRequest{
		Branch:                    abbreviateRefName(req.ReviewRef),
		SourceControlSystem:       "git",
		SourceControlBaseRevision: string(mergeBase),
		SourcePath:                repo.GetPath(),
		LintStatus:                "skip",
		UnitStatus:                "skip",
		Changes:                   changes,
	}
	var createResponse differentialCreateDiffResponse
	runArcCommandOrDie("differential.creatediff", createRequest, &createResponse)
	if createResponse.Error != "" {
		return nil, fmt.Errorf(createResponse.ErrorMessage)
	}

	localCommits := make(map[string]interface{})
	for _, priorDiff := range priorDiffs {
		diffID, err := strconv.Atoi(priorDiff)
		if err != nil {
			return nil, err
		}
		queryRequest := differentialQueryDiffsRequest{[]int{diffID}}
		var queryResponse differentialQueryDiffsResponse
		runArcCommandOrDie("differential.querydiffs", queryRequest, &queryResponse)
		if queryResponse.Error != "" {
			return nil, fmt.Errorf(queryResponse.ErrorMessage)
		}
		priorProperty := queryResponse.Response[priorDiff].Properties
		if priorPropertyMap, ok := priorProperty.(map[string]interface{}); ok {
			if localCommitsProperty, ok := priorPropertyMap["local:commits"]; ok {
				if priorLocalCommits, ok := localCommitsProperty.(map[string]interface{}); ok {
					for id, val := range priorLocalCommits {
						localCommits[id] = val
					}
				}
			}
		}
	}
	localCommits[string(revision)] = revisionDetails
	localCommitsProperty, err := json.Marshal(localCommits)
	if err != nil {
		return nil, err
	}
	if err := arc.setDiffProperty(createResponse.Response.ID, "local:commits", string(localCommitsProperty)); err != nil {
		return nil, err
	}

	return &createResponse.Response, nil
}
Example #4
0
func mirrorRepoToReview(repo repository.Repo, tool review_utils.Tool, syncToRemote bool) {
	if syncToRemote {
		repo.PullNotes("origin", "refs/notes/devtools/*")
	}

	stateHash, err := repo.GetRepoStateHash()
	if err != nil {
		log.Fatal(err)
	}
	if processedStates[repo.GetPath()] != stateHash {
		log.Print("Mirroring repo: ", repo)
		for _, r := range review.ListAll(repo) {
			reviewJson, err := r.GetJSON()
			if err != nil {
				log.Fatal(err)
			}
			log.Println("Mirroring review: ", reviewJson)
			existingComments[r.Revision] = r.Comments
			reviewDetails, err := r.Details()
			if err == nil {
				tool.EnsureRequestExists(repo, *reviewDetails)
			}
		}
		openReviews[repo.GetPath()] = tool.ListOpenReviews(repo)
		processedStates[repo.GetPath()] = stateHash
		tool.Refresh(repo)
	}
ReviewLoop:
	for _, phabricatorReview := range openReviews[repo.GetPath()] {
		if reviewCommit := phabricatorReview.GetFirstCommit(repo); reviewCommit != "" {
			log.Println("Processing review: ", reviewCommit)
			r, err := review.GetSummary(repo, reviewCommit)
			if err != nil {
				log.Fatal(err)
			} else if r == nil {
				log.Printf("Skipping unknown review %q", reviewCommit)
				continue ReviewLoop
			}
			revisionComments := existingComments[reviewCommit]
			log.Printf("Loaded %d comments for %v\n", len(revisionComments), reviewCommit)
			for _, c := range phabricatorReview.LoadComments() {
				if !hasOverlap(c, revisionComments) {
					// The comment is new.
					note, err := c.Write()
					if err != nil {
						log.Fatal(err)
					}
					log.Printf("Appending a comment: %s", string(note))
					repo.AppendNote(comment.Ref, reviewCommit, note)
				} else {
					log.Printf("Skipping '%v', as it has already been written\n", c)
				}
			}
		}
	}
	if syncToRemote {
		if err := repo.PushNotes("origin", "refs/notes/devtools/*"); err != nil {
			log.Printf("Failed to push updates to the repo %v: %v\n", repo, err)
		}
	}
}
Example #5
0
// commentOnReview adds a comment to the current code review.
func commentOnReview(repo repository.Repo, args []string) error {
	commentFlagSet.Parse(args)
	args = commentFlagSet.Args()

	var r *review.Review
	var err error
	if len(args) > 1 {
		return errors.New("Only accepting a single review is supported.")
	}

	if len(args) == 1 {
		r, err = review.Get(repo, args[0])
	} else {
		r, err = review.GetCurrent(repo)
	}

	if err != nil {
		return fmt.Errorf("Failed to load the review: %v\n", err)
	}
	if r == nil {
		return errors.New("There is no matching review.")
	}

	if *commentLgtm && *commentNmw {
		return errors.New("You cannot combine the flags -lgtm and -nmw.")
	}
	if *commentLine != 0 && *commentFile == "" {
		return errors.New("Specifying a line number with the -l flag requires that you also specify a file name with the -f flag.")
	}
	if *commentParent != "" && !commentHashExists(*commentParent, r.Comments) {
		return errors.New("There is no matching parent comment.")
	}

	if *commentMessage == "" {
		editor, err := repo.GetCoreEditor()
		if err != nil {
			return fmt.Errorf("Unable to detect default git editor: %v\n", err)
		}

		path := fmt.Sprintf("%s/.git/%s", repo.GetPath(), commentFilename)

		cmd := exec.Command(editor, path)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err = cmd.Start()
		if err != nil {
			return fmt.Errorf("Unable to start editor: %v\n", err)
		}

		err = cmd.Wait()
		if err != nil {
			return fmt.Errorf("Editing finished with error: %v\n", err)
		}

		comment, err := ioutil.ReadFile(path)
		if err != nil {
			os.Remove(path)
			return fmt.Errorf("Error reading comment file: %v\n", err)
		}
		*commentMessage = string(comment)
		os.Remove(path)
	}

	commentedUponCommit, err := r.GetHeadCommit()
	if err != nil {
		return err
	}
	location := comment.Location{
		Commit: commentedUponCommit,
	}
	if *commentFile != "" {
		location.Path = *commentFile
		if *commentLine != 0 {
			location.Range = &comment.Range{
				StartLine: uint32(*commentLine),
			}
		}
	}

	userEmail, err := repo.GetUserEmail()
	if err != nil {
		return err
	}
	c := comment.New(userEmail, *commentMessage)
	c.Location = &location
	c.Parent = *commentParent
	if *commentLgtm || *commentNmw {
		resolved := *commentLgtm
		c.Resolved = &resolved
	}
	return r.AddComment(c)
}
Example #6
0
// rejectReview adds an NMW comment to the current code review.
func rejectReview(repo repository.Repo, args []string) error {
	rejectFlagSet.Parse(args)
	args = rejectFlagSet.Args()

	var r *review.Review
	var err error
	if len(args) > 1 {
		return errors.New("Only rejecting a single review is supported.")
	}

	if len(args) == 1 {
		r, err = review.Get(repo, args[0])
	} else {
		r, err = review.GetCurrent(repo)
	}

	if err != nil {
		return fmt.Errorf("Failed to load the review: %v\n", err)
	}
	if r == nil {
		return errors.New("There is no matching review.")
	}

	if *rejectMessage == "" {
		editor, err := repo.GetCoreEditor()
		if err != nil {
			return fmt.Errorf("Unable to detect default git editor: %v\n", err)
		}

		path := fmt.Sprintf("%s/.git/%s", repo.GetPath(), rejectFilename)

		cmd := exec.Command(editor, path)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err = cmd.Start()
		if err != nil {
			return fmt.Errorf("Unable to start editor: %v\n", err)
		}

		err = cmd.Wait()
		if err != nil {
			return fmt.Errorf("Editing finished with error: %v\n", err)
		}

		comment, err := ioutil.ReadFile(path)
		if err != nil {
			os.Remove(path)
			return fmt.Errorf("Error reading comment file: %v\n", err)
		}
		*rejectMessage = string(comment)
		os.Remove(path)
	}

	rejectedCommit, err := r.GetHeadCommit()
	if err != nil {
		return err
	}
	location := comment.Location{
		Commit: rejectedCommit,
	}
	resolved := false
	userEmail, err := repo.GetUserEmail()
	if err != nil {
		return err
	}
	c := comment.New(userEmail, *rejectMessage)
	c.Location = &location
	c.Resolved = &resolved
	return r.AddComment(c)
}