Enhance rbt post to automatically determine the existing review request

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

Information

RBTools
master

Reviewers

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)
     
     
    Show all issues

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

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

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

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

    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)
     
     
    Show all issues

    Blank line before.

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

    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)
     
     
     
     
    Show all issues

    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)
     
     
    Show all issues

    Blank line before.

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

    Blank line before an if.

    These two can be one statement, though.

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

    Blank line before.

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

    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)
     
     
    Show all issues

    Blank line before.

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

    Should be:

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

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

    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)
     
     
    Show all issues

    Blank line before.

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

    Blank line before.

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

    Wrapping is best done as:

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

    Also, space after ":"

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

    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)
     
     
    Show all issues

    Blank line before.

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

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

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

    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)
     
     
    Show all issues

    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)
     
     
     
     
     
    Show all issues

    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)
     
     
    Show all issues

    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)
     
     
    Show all issues

    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)
     
     
    Show all issues

    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)
     
     
    Show all issues

    "Determine the existing review request to update."

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

    "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)
     
     
    Show all issues

    Same here.

  6. rbtools/clients/bazaar.py (Diff revision 6)
     
     
    Show all issues

    "Return the revision ..."

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

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

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

    Blank line between these.

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

    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)
     
     
     
     
     
     
     
     
     
     
     
     
     
    Show all issues

    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)
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
    Show all issues

    Same here.

    1. Here too.

    2. I made changes to this as well.

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

    "Returns the ..."

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

    "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)
     
     
    Show all issues

    Blank line before this.

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

    Should follow the standard format of:

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

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

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

    Same here.

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

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

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

    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)
     
     
    Show all issues

    "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)
     
     
    Show all issues

    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)
     
     
    Show all issues

    Blank line before this.

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

    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)
     
     
    Show all issues

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

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

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

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

    Blank line before this.

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

    You can just do:

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

    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)
     
     
    Show all issues

    No need for the parens.

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

    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)
     
     
    Show all issues

    Same comment about review_request vs. request.

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

    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)
     
     
     
    Show all issues

    Can be one statement.

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

    Should be one line.

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

    Should be review_request, rather than request.

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

    review_requests

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

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

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

    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)
     
     
    Show all issues

    "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)
     
     
     
    Show all issues

    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:
Completed
Change Summary:
Pushed to master (6bee872). Thanks!