Enhance rbt post to automatically determine the existing review request

Review Request #4699 — Created Oct. 8, 2013 and submitted

edwlee
RBTools
master
4662
rbtools, students

Enhance rbt post to automatically determine the existing review request

rbt -u automatically determines the existing review request to update. It
suggests a pending review request by the current user in the current
repository, based on the request summary and description.

If the summary and description exactly match those of an existing request,
the request is immediately updated. Otherwise, the user is prompted to confirm
a suggestion from a ranked list of potential matches.

Reviewed at https://reviews.reviewboard.org/r/4699

-Tested with Git, Mercurial, and Bazaar using, with and without 'revision range'.
-Unit tests pass.

Description From Last Updated

difflib belongs in the group above, as it's shipped with Python.

chipx86chipx86

That's kind of a long full name. How about just --update?

chipx86chipx86

Any modifications to existing command options, or behavior need to be updated in the documentation as well (This includes adding …

SM smacleod

This seems like it could be useful outside of post, such as in your command for generating configuration. I could …

SM smacleod

This is wrapped very strangely. Should be more like: has_... = ( self.options.guess_fields or (self.options.summary and self.options.description) ) Other valid …

chipx86chipx86

Blank line before.

chipx86chipx86

I'm not sure if this is true. Let's assume we don't have --guess-fields set, and we aren't provided a summary …

SM smacleod

Summary should be on the """ line.

chipx86chipx86

You can avoid keeping track of the index by doing: for seq_a, seq_b in zip(sequences_a, sequences_b):

chipx86chipx86

Blank line before.

chipx86chipx86

Blank line before an if. These two can be one statement, though.

chipx86chipx86

Blank line before.

chipx86chipx86

I think this might be better suited outside of "post_request()". It would be nice if we could minimize the complexity …

SM smacleod

This should also filter by repository. It's not valid to update a review request on a different repository.

chipx86chipx86

Blank line before.

chipx86chipx86

Should be: print ('....' % user.username)

chipx86chipx86

Just return. No exit. Same below.

chipx86chipx86

Blank line before.

chipx86chipx86

Like I mentioned in r/4662 , API resources are paged, so just looping through the first page will not retrieve …

SM smacleod

Blank line before.

chipx86chipx86

Wrapping is best done as: question = ('...' % (request.id, request.summary)) Also, space after ":"

chipx86chipx86

I don't like always having to be asked. The majority of the time, things will be identical, and I can …

chipx86chipx86

Blank line before.

chipx86chipx86

Sounds more human if you say ".. the existing review request"

chipx86chipx86

Needs two colons at the end, to trigger the code block for the next line. (It's a ReST construct.)

chipx86chipx86

"Determine the existing review request to update."

chipx86chipx86

"Guess" doesn't really say what it's doing with this. I'd specifically say "Return the summary ..."

chipx86chipx86

Same here.

chipx86chipx86

"Return the revision ..."

chipx86chipx86

You can just return in the above, instead of at the end.

chipx86chipx86

Blank line between these.

chipx86chipx86

What happened with this logic?

chipx86chipx86

There's a lot of code repeated here. I'd suggest having the conditionals compute the revision only, and then do the …

chipx86chipx86

Same here.

chipx86chipx86

"Returns the ..."

chipx86chipx86

"Returns the ..." Make sure the rest of the change is in that format. "Gets" doesn't mean it's returning anything …

chipx86chipx86

Blank line before this.

chipx86chipx86

Should follow the standard format of: """One-line summary. Multi-line description. """

chipx86chipx86

Since r1 and r2 aren't used, you can just do: revision = self._extract_revisions(...)[1]

chipx86chipx86

Same here.

chipx86chipx86

Might as well store as rev1, and not rename a variable.

chipx86chipx86

Parent branch of what? The docs should be explicit, so there's no uncertainty about what it's based off of.

chipx86chipx86

"Returns" Can you explain more what this function does? From the description, it's not immediately obvious what this data is …

chipx86chipx86

You don't need the `` in the summaries. It's best not to specify the variable names in summaries, actually. Say …

chipx86chipx86

Blank line before this.

chipx86chipx86

Terminology is "review requests", specifically. "requests" usually means something else, like an HTTP request. Can you also update the variable …

chipx86chipx86

Comments should be in sentence casing, ending with a period.

chipx86chipx86

Can you use "ID" instead of "id" in the docstring?

chipx86chipx86

Blank line before this.

chipx86chipx86

You can just do: if not requests:

chipx86chipx86

This sounds like an error. We should fail without a successful return code.

chipx86chipx86

No need for the parens.

chipx86chipx86

The comments shouldn't go between the conditionals. Try rewriting to describe what's happening when it hits that conditional, and put …

chipx86chipx86

Same comment about review_request vs. request.

chipx86chipx86

This is also an error, so we need to fail with an error.

chipx86chipx86

Can be one statement.

chipx86chipx86

Should be one line.

chipx86chipx86

Should be review_request, rather than request.

chipx86chipx86

review_requests

chipx86chipx86

Shouldn't be any blank lines before an except: line.

chipx86chipx86

I know this was old code, but can you change it to "please reopen the review request" ?

chipx86chipx86

"No review_request_id"

chipx86chipx86

It's a pretty unlikely situation, but we might want to verify that there's only one exact match before automatically choosing …

daviddavid
ED
ED
chipx86
  1. 
      
  2. rbtools/commands/post.py (Diff revision 2)
     
     

    difflib belongs in the group above, as it's shipped with Python.

  3. rbtools/commands/post.py (Diff revision 2)
     
     

    That's kind of a long full name. How about just --update?

  4. rbtools/commands/post.py (Diff revision 2)
     
     
     

    This is wrapped very strangely. Should be more like:

    has_... = (
    self.options.guess_fields or
    (self.options.summary and self.options.description)
    )

    Other valid options, though, are --guess-summary + --guess-description, so that should be handled too. Those predate --guess-fields, so people may have those set in scripts.

    In fact, setting --guess-fields just sets self.options.guess_summary and guess_description, so just check for those.

  5. rbtools/commands/post.py (Diff revision 2)
     
     

    Blank line before.

  6. rbtools/commands/post.py (Diff revision 2)
     
     
     

    Summary should be on the """ line.

  7. rbtools/commands/post.py (Diff revision 2)
     
     
     
     
     
     

    I'm not sure how I feel about using diff logic for this. It's very easy to get false-positives/negatives. I'd like to know what other people think.

    1. I agree about the false-positives/negatives. I think the matching of description
      might be overkill as well for now.

      Initially I think a good way to do this would be to look for exact Summary matches
      only (Or, trivially different, e.g. allow added whitespace, Capitalization, period,
      on end or beginning). If we don't find an exact match, we just fall back to asking.

      Then, beyond that maybe add diffing, but only use it to provide a ranked list in
      the event of falling back. Give the user 1-5 options or something, in ranked order,
      and ask if it is any of them. If that fails, just tell the user they need to specify
      manually.

      Is that more along the lines of what you were thinking Christian?

    2. Yeah, I think we're basically thinking the same thing.

      My thought about comparing the description is that if both are a match, then we're 100% sure,
      and it avoids the "fixed a bug" summaries that might be identical.

      I like the ranked idea, with 1-5 for choosing, and falling back to asking for an ID.
      Could be very useful.

  8. rbtools/commands/post.py (Diff revision 2)
     
     
     
     

    You can avoid keeping track of the index by doing:

    for seq_a, seq_b in zip(sequences_a, sequences_b):

  9. rbtools/commands/post.py (Diff revision 2)
     
     

    Blank line before.

  10. rbtools/commands/post.py (Diff revision 2)
     
     
     
     

    Blank line before an if.

    These two can be one statement, though.

  11. rbtools/commands/post.py (Diff revision 2)
     
     

    Blank line before.

  12. rbtools/commands/post.py (Diff revision 2)
     
     
     

    This should also filter by repository. It's not valid to update a review request on a different repository.

  13. rbtools/commands/post.py (Diff revision 2)
     
     

    Blank line before.

  14. rbtools/commands/post.py (Diff revision 2)
     
     
     

    Should be:

    print ('....'
    % user.username)

  15. rbtools/commands/post.py (Diff revision 2)
     
     

    Just return. No exit.

    Same below.

    1. A good rule to follow, is the command execution should only ever stop in one of 4 ways:

      1. return is called
      2. a CommandExit() is raised
      3. a CommandError() is raised
      4. main() finishes executing completely
  16. rbtools/commands/post.py (Diff revision 2)
     
     

    Blank line before.

  17. rbtools/commands/post.py (Diff revision 2)
     
     

    Blank line before.

  18. rbtools/commands/post.py (Diff revision 2)
     
     
     

    Wrapping is best done as:

    question = ('...'
    % (request.id, request.summary))

    Also, space after ":"

  19. rbtools/commands/post.py (Diff revision 2)
     
     
     
     

    I don't like always having to be asked. The majority of the time, things will be identical, and I can feel safe about that. Instead, I'd want to know if they're an exact match, or a fuzzy match.

    Thinking about this more, I think I'd prefer the following logic:

    1. If the sets of fields are identical, assume it's the correct one.
    2. If the summary is identical, but the description is different, prompt.

    That way, we can be pretty sure in the common case that nothing has changed, and in the case of the description being augmented, we can confirm. The user has a clear expectation of what will happen in each case, and doesn't have to worry about how different the summary/description is.

    That also avoids things like a summary of "Fixes bug 123 in my module" from appearing to be a valid option when compared against "Fixes bug 124 in my module".

  20. rbtools/commands/post.py (Diff revision 2)
     
     

    Blank line before.

  21. rbtools/commands/post.py (Diff revision 2)
     
     

    Sounds more human if you say ".. the existing review request"

  22. 
      
SM
  1. 
      
  2. rbtools/commands/post.py (Diff revision 2)
     
     
     
     
     
     

    Any modifications to existing command options, or behavior need to be updated in the documentation as well (This includes adding commands such as in your other review requests).

    see "rbtools/docs/rbtools/rbt/commands/post.txt"

  3. rbtools/commands/post.py (Diff revision 2)
     
     

    This seems like it could be useful outside of post, such as in your command for generating configuration. I could imagine uses in other commands, or new commands as well.

    How about we move this out to utils or something?

    1. I was actually considering moving this to a separate file since it's also defined in my patch for r/4662. Should I do this in both patches, or is there a better way of handling refactoring the same things from multiple pending requests?

    2. You have a couple options. a) you could put this functionality in to it's own patch that both depend on, or b) implement the functionality generally in one of the patches, and then rebase your other patch on top of it (What I'd prefer).

      The patch based on top of the other won't be able to go in until the other is finished, so I suggest implementing this generally in whichever patch you plan to finish first.

      Also, when you have your changes rebased like this, the way to post them is "rbt post" for the base branch, and then "rbt post --parent=<base-feature-branch>" for the change on top (where <base-feature-branch> is replaced by the name of the branch you have the second feature on top of).

      Just ping me in IRC if you want help with the rebasing or posting of patches like this.

  4. rbtools/commands/post.py (Diff revision 2)
     
     
     
     
     

    I'm not sure if this is true. Let's assume we don't have --guess-fields set, and we aren't provided a summary or description:

    We could still detect they'd like to update, and run the code to guess the fields to generate them, but we just won't update the fields when we update the request.

  5. rbtools/commands/post.py (Diff revision 2)
     
     

    I think this might be better suited outside of "post_request()".

    It would be nice if we could minimize the complexity of the method, and instead just have the guessing code live somewhere else that possibly runs first.

    How about we refactor post_request() to take the review request ID as an argument, defaulted to None. If it is None, we create the new review request, and if it's provided we update that one?

  6. rbtools/commands/post.py (Diff revision 2)
     
     

    Like I mentioned in r/4662 , API resources are paged, so just looping through the first page will not retrieve every resource. See my comment in the other review request.

  7. 
      
ED
ED
ED
ED
ED
ED
chipx86
  1. 
      
  2. docs/rbtools/rbt/commands/post.txt (Diff revision 6)
     
     

    Needs two colons at the end, to trigger the code block for the next line. (It's a ReST construct.)

  3. docs/rbtools/rbt/commands/post.txt (Diff revision 6)
     
     

    "Determine the existing review request to update."

  4. rbtools/clients/__init__.py (Diff revision 6)
     
     

    "Guess" doesn't really say what it's doing with this. I'd specifically say "Return the summary ..."

  5. rbtools/clients/__init__.py (Diff revision 6)
     
     

    Same here.

  6. rbtools/clients/bazaar.py (Diff revision 6)
     
     

    "Return the revision ..."

  7. rbtools/clients/bazaar.py (Diff revision 6)
     
     
     
     
     
     

    You can just return in the above, instead of at the end.

  8. rbtools/clients/bazaar.py (Diff revision 6)
     
     
     

    Blank line between these.

  9. rbtools/clients/bazaar.py (Diff revision 6)
     
     
     

    What happened with this logic?

    1. This logic actually never gets called, so I removed it -- but I can see why this is not preferred. I'm having some problems adding it back in, however. This is a bit hard to explain, so I'll try my best. Basically, I can't use revision_range alone as a boolean flag to distinguish 3 conditions.

      Consider these two cases:

      1) When extract_description() is called own its own (not a part of the get_diff() call) and revision_range is None, we actually want to set revision_range to a default, not None value, and use it in command.extend(...).

      2) When extract_description() is called as part of get_diff() and revision_range is not None when passed into get_diff(), revision_range could become None when it's passed into extract_description(). In this case, we don't want to use revision_range in command.extend(...), instead we want command.extend(["missing", "-q", "--mine-only"]). This is a case that never happens in the current logic.

      So the 3 conditions for extract_description() are:
      1) revision_range is not None
      3) revision_range is None, but it should be changed to a default value
      2) revision_range is None

      Since revision_range is the only parameter to extract_description(), I can't use it alone to distinguish these 3 conditions without adding an additional flag inside revision_range, which would be messy. I could add a new parameter, but I also don't like the idea of changing the method signature to accommodate Bazaar's implementation.

      Any suggestions?

    2. I agree, we shouldn't change it just for Bazaar's unique case.

      If this logic was never being invoked before, then I'm fine removing it.

  10. rbtools/clients/git.py (Diff revision 6)
     
     
     
     
     
     
     
     
     
     
     
     
     

    There's a lot of code repeated here. I'd suggest having the conditionals compute the revision only, and then do the whole execute () at the end of the function.

    1. This looks to still be the same.

    2. I did make changes to this code. Can you take another look?

  11. rbtools/clients/git.py (Diff revision 6)
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     

    Same here.

    1. Here too.

    2. I made changes to this as well.

  12. rbtools/clients/git.py (Diff revision 6)
     
     

    "Returns the ..."

  13. rbtools/clients/git.py (Diff revision 6)
     
     

    "Returns the ..."

    Make sure the rest of the change is in that format. "Gets" doesn't mean it's returning anything it just means it's getting something from somewhere.

  14. rbtools/clients/git.py (Diff revision 6)
     
     

    Blank line before this.

  15. rbtools/clients/git.py (Diff revision 6)
     
     
     
     
     

    Should follow the standard format of:

    """One-line summary.
    
    Multi-line description.
    """
    
  16. rbtools/clients/mercurial.py (Diff revision 6)
     
     
     

    Since r1 and r2 aren't used, you can just do:

    revision = self._extract_revisions(...)[1]
    
  17. rbtools/clients/mercurial.py (Diff revision 6)
     
     
     

    Same here.

  18. rbtools/clients/mercurial.py (Diff revision 6)
     
     
     
     

    Might as well store as rev1, and not rename a variable.

  19. rbtools/clients/mercurial.py (Diff revision 6)
     
     

    Parent branch of what? The docs should be explicit, so there's no uncertainty about what it's based off of.

  20. rbtools/clients/mercurial.py (Diff revision 6)
     
     

    "Returns"

    Can you explain more what this function does? From the description, it's not immediately obvious what this data is or where the information is coming from.

  21. rbtools/clients/mercurial.py (Diff revision 6)
     
     

    You don't need the `` in the summaries. It's best not to specify the variable names in summaries, actually. Say something like "the provided revision range."

  22. rbtools/clients/mercurial.py (Diff revision 6)
     
     

    Blank line before this.

  23. rbtools/commands/post.py (Diff revision 6)
     
     

    Terminology is "review requests", specifically. "requests" usually means something else, like an HTTP request.

    Can you also update the variable names to be review_request, and not request?

  24. rbtools/commands/post.py (Diff revision 6)
     
     

    Comments should be in sentence casing, ending with a period.

  25. rbtools/commands/post.py (Diff revision 6)
     
     

    Can you use "ID" instead of "id" in the docstring?

  26. rbtools/commands/post.py (Diff revision 6)
     
     

    Blank line before this.

  27. rbtools/commands/post.py (Diff revision 6)
     
     

    You can just do:

    if not requests:
    
  28. rbtools/commands/post.py (Diff revision 6)
     
     
     

    This sounds like an error. We should fail without a successful return code.

    1. Is there a specific Exception I should throw here?

    2. I believe you want CommandError.

  29. rbtools/commands/post.py (Diff revision 6)
     
     

    No need for the parens.

  30. rbtools/commands/post.py (Diff revision 6)
     
     
     
     
     
     

    The comments shouldn't go between the conditionals. Try rewriting to describe what's happening when it hits that conditional, and put them inside the body.

  31. rbtools/commands/post.py (Diff revision 6)
     
     

    Same comment about review_request vs. request.

  32. rbtools/commands/post.py (Diff revision 6)
     
     
     
     
     

    This is also an error, so we need to fail with an error.

  33. 
      
ED
ED
ED
ED
chipx86
  1. Just about there. A few things left, plus some other stuff that didn't actually get changed from my previous review (mentioned as a reply to that review).

  2. rbtools/clients/bazaar.py (Diff revision 9)
     
     
     

    Can be one statement.

  3. rbtools/clients/mercurial.py (Diff revision 9)
     
     
     
     

    Should be one line.

  4. rbtools/commands/post.py (Diff revision 9)
     
     

    Should be review_request, rather than request.

  5. rbtools/commands/post.py (Diff revision 9)
     
     

    review_requests

  6. rbtools/commands/post.py (Diff revision 9)
     
     
     
     

    Shouldn't be any blank lines before an except: line.

  7. rbtools/commands/post.py (Diff revision 9)
     
     

    I know this was old code, but can you change it to "please reopen the review request" ?

  8. rbtools/commands/post.py (Diff revision 9)
     
     

    "No review_request_id"

  9. 
      
ED
ED
ED
ED
david
  1. 
      
    1. Oh, one other thing: you added '4662' as a bug, when I think you meant 'Depends on'

    2. Thanks for catching that. I'm still getting used to the new layout :P

  2. rbtools/commands/post.py (Diff revision 13)
     
     
     

    It's a pretty unlikely situation, but we might want to verify that there's only one exact match before automatically choosing it.

  3. 
      
ED
ED
chipx86
  1. Ship It!
  2. 
      
ED
Review request changed

Status: Closed (submitted)

Change Summary:

Pushed to master (6bee872). Thanks!
Loading...