Excluding author from peer reviewer list in gerrit - gerrit

I'm setting up the access control for my company in gerrit and in our current internal process has cross-over between peer reviewers and coders (they tend to be the same group of people). We also want to only require 1 reviewer to peer review the code and submit it if it looks good.
With the default setup any user with the +2: Looks good to me, approved option can peer review their own code.
Is there any way to prevent the author from reviewing their own code, but still allow them to fully review other's code? I haven't been able to find any kind of exclude author in the access control group setup or permissions setups.

The Gerrit Cookbook Example 8 does not strictly prevent the Author to review his/her own change, but will require someone else to +2 it before being able to submit.

This is working for me, but it's a quick hack:
allows a configurable number of +1s to count as a +2 for manual submission
optionally automatically submit with enough +1 votes
optionally counts -1 votes as countering +1 votes for the purposes of the tally
optionally ignores the uploader's own +1 (you may prefer a check against the author, which I've not done)
I've tweaked my earlier answer so it doesn't assume you're using a mysql server.
You might want to move the logfile somewhere it'll be subject to any normal log rotation - perhaps in ../logs/comment-added.log.
I've tried to pull the configurable bits to the fore. Call this file comment-hook and
put it in $gerrit_root/hooks, chmod it 755 or similar. Set up a robot user in the admin
group so the hook can use the sql interface (and comment +2 on things with enough +1s).
#!/usr/bin/perl
#
# comment-hook for a +2 approval from a simple quorum of +1 votes.
#
# Licence: Public domain. All risk is yours; if it breaks, you get to keep both pieces.
$QUORUM = 2; # Total number of +1 votes causing a +2
$PLEBIANS = 'abs(value) < 2'; # or 'value = 1' to ignore -1 unvotes
$AUTO_SUBMIT_ON_QUORACY = '--submit'; # or '' for none
$AND_IGNORE_UPLOADER = 'and uploader_account_id != account_id'; # or '' to let uploaders votes count
$GERRIT_SSH_PORT = 29418;
$SSH_PRIVATE_KEY = '/home/gerrit2/.ssh/id_rsa';
$SSH_USER_IN_ADMIN_GROUP = 'devuser';
# Hopefully you shouldn't need to venture past here.
$SSH = "ssh -i $SSH_PRIVATE_KEY -p $GERRIT_SSH_PORT $SSH_USER_IN_ADMIN_GROUP\#localhost";
$LOG = "/home/gerrit2/hooks/log.comment-added";
open LOG, ">>$LOG" or die;
sub count_of_relevant_votes {
# Total selected code review votes for this commit
my $relevance = shift;
$query = "
select sum(value) from patch_sets, patch_set_approvals
where patch_sets.change_id = patch_set_approvals.change_id
and patch_sets.patch_set_id = patch_set_approvals.patch_set_id
and revision = '$V{commit}'
and category_id = 'CRVW'
and $relevance
$AND_IGNORE_UPLOADER
;";
$command = "$SSH \"gerrit gsql -c \\\"$query\\\"\"";
#print LOG "FOR... $command\n";
#lines = qx($command);
chomp #lines;
#print LOG "GOT... ", join("//", #lines), "\n";
# 0=headers 1=separators 2=data 3=count and timing.
return $lines[2];
}
sub response {
my $review = shift;
return "$SSH 'gerrit review --project=\"$V{project}\" $review $V{commit}'";
}
# ######################
# Parse options
$key='';
while ( $_ = shift #ARGV ) {
if (/^--(.*)/) {
$key = $1;
}
else {
$V{$key} .= " " if exists $V{$key};
$V{$key} .= $_;
}
}
#print LOG join("\n", map { "$_ = '$V{$_}'" } keys %V), "\n";
# ######################
# Ignore my own comments
$GATEKEEPER="::GATEKEEPER::";
if ($V{comment} =~ /$GATEKEEPER/) {
# print LOG localtime() . "$V{commit}: Ignore $GATEKEEPER comments\n";
exit 0;
}
# ######################
# Forbear to analyse anything already +2'd
$submittable = count_of_relevant_votes('value = 2');
if ($submittable > 0) {
# print LOG "$V{commit} Already +2'd by someone or something.\n";
exit 0;
}
# ######################
# Look for a consensus amongst qualified voters.
$plebicite = count_of_relevant_votes($PLEBIANS);
#if ($V{comment} =~ /TEST:(\d)/) {
# $plebicite=$1;
#}
# ######################
# If there's a quorum, approve and submit.
if ( $plebicite >= $QUORUM ) {
$and_submitting = ($AUTO_SUBMIT_ON_QUORACY ? " and submitting" : "");
$review = " --code-review=+2 --message=\"$GATEKEEPER approving$and_submitting due to $plebicite total eligible votes\" $AUTO_SUBMIT_ON_QUORACY";
}
else {
$review = " --code-review=0 --message=\"$GATEKEEPER ignoring $plebicite total eligible votes\"";
# print LOG "$V{commit}: $review\n";
exit 0;
}
$response = response($review);
print LOG "RUNNING: $response\n";
$output = qx( $response 2>&1 );
if ($output =~ /\S/) {
print LOG "$V{commit}: output from commenting: $output";
$response = response(" --message=\"During \Q$review\E: \Q$output\E\"");
print LOG "WARNING: $response\n";
$output = qx( $response 2>&1 );
print LOG "ERROR: $output\n";
}
exit 0;

Gerrit allows you to set up prolog "submit rules" that define when a change is submittable.
The documentation includes several examples, including one that prevents the author from approving his own change.

You can do it from the GUI in the access tab.
Go to the /refs/heads/ section -> add group 'change owner' in Label Code-Review section -> choose -1..+1
This will make the change owner to privilege for giving -1 to +1

I have just written this prolog filter for our Gerrit installation. I did it as a submit_filter in the parent project because I wanted it to apply to all projects in our system.
%filter to require all projects to have a code-reviewer other than the owner
submit_filter(In, Out) :-
%unpack the submit rule into a list of code reviews
In =.. [submit | Ls],
%add the non-owner code review requiremet
reject_self_review(Ls, R),
%pack the list back up and return it (kinda)
Out =.. [submit | R].
reject_self_review(S1, S2) :-
%set O to be the change owner
gerrit:change_owner(O),
%find a +2 code review, if it exists, and set R to be the reviewer
gerrit:commit_label(label('Code-Review', 2), R),
%if there is a +2 review from someone other than the owner, then the filter has no work to do, assign S2 to S1
R \= O, !,
%the cut (!) predicate prevents further rules from being consulted
S2 = S1.
reject_self_review(S1, S2) :-
%set O to be the change owner
gerrit:change_owner(O),
% find a +2 code review, if it exists, and set R to be the reviewer - comment sign was missing
gerrit:commit_label(label('Code-Review', 2), R),
R = O, !,
%if there isn't a +2 from someone else (above rule), and there is a +2 from the owner, reject with a self-reviewed label
S2 = [label('Self-Reviewed', reject(O))|S1].
%if the above two rules didn't make it to the ! predicate, there aren't any +2s so let the default rules through unfiltered
reject_self_review(S1, S1).
The benefits (IMO) of this rule over rule #8 from the cookbook are:
The Self-Reviewed label is only shown when the the change is being blocked, rather than adding a Non-Author-Code-Review label to every change
By using reject(O) the rule causes the Self-Reviewed label to literally be a red flag
As a submit_filter instead of a submit_rule, this rule is installed in a parent project and applies to all sub-projects
Please Note: This rule is authored to prevent the Owner from self-reviewing a change, while the example from the cookbook compares against the Author. Depending on your workflow, you may want to replace the 2 gerrit:change_owner(O) predicates with gerrit:commit_author(O) or gerrit:commit_committer(O)

Related

Bookmarks parsing issue

I have a LARGE number of bookmarks and wanted to export them and share them with a group I work with. The issue is that when I export them, there are ADD_DATE and LAST_MODIFIED fields added by the browser (Firefox). I was hoping to just use cut or awk to pull the fields I want but the lack of a space before the >(website_name) is making that difficult. And my regex skills are weak.
How do I add a single space before the second to last > at the end of the line so that I can use cut or awk to pull out the fields I want into a new file?
Ex: 123456">SecurityTrails would become 123456 >SecurityTrails
Please see below for examples of what I'm working with. Any help is greatly appreciated!
<DT>SecurityTrails
i use firefox myself. it frequently also embeds favicon into the exported bookmarks.html file via base64 encoding. so to account for the different scenarios (than just the one mentioned by OP), maybe something like
{mawk/mawk2/gawk} 'BEGIN { FS = "\042" } $1 = $1'
then do whatever cutting that you want. That's just assuming OP wanted to keep every bit of it, and simply remove the quotations.
Now, if the objective is just to take out URL+Name of it,
{mawk/mawk2/gawk} 'BEGIN { DBLQT="\042"; FS = "(<A HREF=" DBLQT "|>)" } /<A HREF=/ {
url = substr($2, 1, index($2, DBLQT) - 1);
sitename = $(NF-1);
sub(/<\/A$/, "", sitename) ;
print url " > " sitename ; }' # or whatever way you want the output to be
I just typed it in extra verbosity to show what \042 meant - the ascii octal for double quote.

Jenkins Groovy Active Choice Reactive Parameter referenced parameter creates error

Okay, so right now I have an Active Choice Parameter, let's call it 'SKU' that is a list of SKUs a user may select from.
I have a Active Choices Reactive Parameter that is referencing the value of SKU and using that value to query files through the GitHub API to generate a list of titles.
When I run my script with a real, but hardcoded, value for the SKU it works.
When I run my script trying to call in the SKU parameter so that it is reactive to what the user originally picks -- using ${SKU} -- it goes to the fallback which I have set to just generate "error":
Groovy script (with hardcoding):
token = "value of token"
def command = """
curl -H 'Authorization: token ${token}' -H "Accept: application/vnd.github.VERSION.raw" https://api.github.com/repos/org/SKU/path/SKU-File.xml
"""
process = [ 'bash', '-c', command].execute()
parser = new XmlSlurper()
def rootNode = parser.parseText(process.text)
def count = rootNode.children().size() - 1
titles = []
for (i in 1..count) {
title = rootNode.children()[i].title
titles += title
}
return titles
Note: This works -- with the exception that instead of switching the list of titles depending on the SKU you select, it is always the same list from the hardcoded SKU.
Groovy script (without hardcoding):
token = "value of token"
def command = """
curl -H 'Authorization: token ${token}' -H "Accept: application/vnd.github.VERSION.raw" https://api.github.com/repos/org/${SKU}/path/${SKU}-File.xml
"""
process = [ 'bash', '-c', command].execute()
parser = new XmlSlurper()
def rootNode = parser.parseText(process.text)
def count = rootNode.children().size() - 1
titles = []
for (i in 1..count) {
title = rootNode.children()[i].title.toString()
titles += title
}
return titles
Note: This fails and defaults to the Fallback script.
Another important note: This is working when I try the script console, however, since the script console is not able to reference the other parameters from my project, I have to harcode in a SKU = "SKU" variable, so not sure it's as equivalent as I'd like it to be.
Fallback script:
return ["error"]
Note also, I have another Active Choice Reactive Parameter also pointing to the SKU and it is working just fine. But in that I'm not calling the SKU in a curl command. I can't quite figure out what's going wrong here, or if I'm referencing something inappropriately.
I've tried converting the ${SKU} parameter to a string as a variable before passing it to the command variable and that didn't change the outcome.
So here's the answer:
Not all of my SKUs were accurately pulling titles based on the same criteria as the SKU I was hardcoding.
It's a bit strange that an error on one SKU would pull apart the entire ability to get an accurate list -- e.g. if SKU1 had an error, toggling to SKU2 gave an error even if SKU2 was working while hardcoded. I've adjusted my script to work for both SKU1 and SKU2 and now it is working as intended.
This is a 'gotcha' to look out for!

How to make a Bot listen to a conversation in the background, but respond only if his name is quoted in a sentence

I am developing a ChatBot for Telegram named "Joker". It works perfectly in a private conversation. Has a whole training set up to answer several questions. But when placed in a group, it responds to all messages sent to the group, creating a chat disorder. It would be nice if my Bot could hear the group talk in the background and intervene only if his name "Joker" is quoted in one sentence. He would normally answer the questions as long as the word "Joker" was contained in the sentence. For this reason, I am trying to implement this feature, but it does not work as it should.
Current Reaction
Gillan: Good morning guys!
Mitzi Dupree: Good morning, Gillan!
... Joker is typing
Gillan: Good morning, Joker!
... Joker is typing
Gillan: Joker?
... Joker is typing
Communication.py
def respond(self, message):
"""
Receive message from user and returns corresponding answer.
"""
if re.search("joker", message, re.IGNORECASE):
joker_in_message = True
else:
joker_in_message = False
message_without_joker = re.sub(r'\bjoker\b', '', message, flags=re.IGNORECASE)
#remove duplicate spaces
message_without_joker = re.sub(r'\s{2,}', ' ', message_without_joker)
if joker_in_message and len(message) > 50 and self.watson_usage:
top_answer = get_analysis(message_without_joker)
return f"Hmm, you're talking about {top_answer}"
if joker_in_message and len(message.strip()) == len("joker"):
return "Something wrong is not right, text me what "\
"you told my creators!" \
"Type /info to learn more."
elif joker_in_message:
return self.comm.get_response(self.clean(message_without_joker))
Application.py
def text_message(self, bot, update):
self.send_type_action(bot, update)
if not self.check_for_emotion(update):
message = update.effective_message.text
answer = self.comm.respond(message)
if answer:
update.effective_message.reply_text(answer)
return 0

Jira: Producing a report containing the stories & blocking issues associated with a release

The Company I work for uses Jira to support the requirement capture & test phases of a project. We assign stories (i.e. requirements) to a release. As a Test Engineer, I raise bugs which I then reference in the story as "Is Blocked By".
I need to create a report which lists each of the releases and the stories associated with that release. As I raise bugs, I need the report to also be populated by the bugs (or actually any other issue raised).
I cannot see a way of doing this within Jira directly but I have found a Jira module for Python... I've got the following working but now I'm stuck;
from jira import JIRA
server = {"server" : "https://jira.pxr5.hw"}
login = ('bob', 'dave')
jira = JIRA (options = server, basic_auth = login)
myProject = jira.project ("QSC")
for eachVersion in myProject.versions:
print eachVersion.name + " - " + eachVersion.id
This produces the expected output;
Release 0 - 10518
Release 0.1 - 10602
Release 0.2 - 10603
Release 1.0 - 10519
Release 2.0 - 10520
Release 3.0 - 10521
closed - 10616
work complete - 10617
From the documentation I've found, I cannot see how to return anything further by which I mean the stories under each Version and (where they exist) the bugs I've raised.
Please can you help? Thanks for your attention.
I got there in the end... Kind of... Here's a solution I found by unpicking the "raw" property...
from jira import JIRA
import sys
userName = "Dave"
userPassword = "Bob"
server = {"server" : "https://jira.pxr5.hw"}
login = (userName, userPassword)
# Open a link to Jira
jira = JIRA (options = server, basic_auth = login)
# Return an object comprising the project we're working on
myProject = jira.project ("quark")
# Get a list of the releases in the project. Notice that the version
# has to be surrounded by single quotes as it may have spaces in it
for eachVersion in myProject.versions:
jqlStatement = "project=quark AND fixVersion='" + eachVersion.name + "'"
print eachVersion.name
# Get a list of stories accociated with each release of the project
theStories = jira.search_issues (jqlStatement)
# Go through each story in the current release
for eachStory in theStories:
# The story is indented one tab with it's ID and summary name
print "\t" + eachStory.raw["key"] + " " + eachStory.raw["fields"]["summary"]
# Now get a list of issue links and go through each one
issueLinks = eachStory.raw["fields"]["issuelinks"]
for eachLink in issueLinks:
print "\t\t" + eachLink["inwardIssue"]["key"] + " " + \
eachLink["inwardIssue"]["fields"]["summary"]

parse multilines from a file and replace

I need to read a file where the content is like below :
Computer Location = afp.local/EANG
Description = RED_TXT
Device Name = EANG04W
Domain Name = afp.local
Full Name = Admintech
Hardware Monitoring Type = ASIC2
Last Blocked Application Scan Date = 1420558125
Last Custom Definition Scan Date = 1348087114
Last Hardware Scan Date = 1420533869
Last Policy Sync Date = 1420533623
Last Software Scan Date = 1420533924
Last Update Scan Date = 1420558125
Last Vulnerability Scan Date = 1420558125
LDAP Location = **CN=EANG04W**,OU=EANG,DC=afp,DC=local
Login Name = ADMINTECH
Main Board OEM Name = Dell Inc.
Number of Files = 384091
Primary Owner = **CN= LOUHICHI anoir**,OU=EANG,DC=afp,DC=localenter code here
I need to replace CN=$value by CN=Compagny where $value is what is retrived after CN= and before ,.
Ok, so you really should have updated your question an not posted the code in a comment, because it's really hard to read. Here's what I think you intended:
$file = 'D:\sources\scripts\2.txt'
$content = Get-Content $file | foreach ($line in $content) {
if ($line.Contains('CN=')) {
$variable = $line.Split(',').Split('=')[2]
$variable1 = $variable -replace $variable, "Compagny"
} Set-Content -path $file
}
That deffinately has some syntax errors. The first line is great, you define the path. Then things go wrong... Your call to Get-Content is fine, that will get the contents of the file, and send them down the pipe.
You pipe that directly into a ForEach loop, but it's the wrong kind. What you really want there is a ForEach-Object loop (which can be confusing, because it can be shortened to just ForEach when used in a pipeline like this). The ForEach-Object loop does not declare an internal variable (such as ($line in $content)) and instead the scriptblock uses the automatic variable $_. So your loop needs to become something like:
Get-Content $file | ForEach { <do stuff> } | Set-Content
Next let's look inside that loop. You use an If statement to see if the line contains "CN=", understandable, and functional. If it does you then split the line on commas, and then again on equals, selecting the second record. Hm, you create an array of strings anytime you split one, and you have split a string twice, but only specify which record of the array you want to work with for the second split. That could be a problem. Anyway, you assign that substring to $variable, and proceed to replace that whole thing with "company" and store that output to $variable1. So there's a couple issues here. Once you split the string on the commas you have the following array of strings:
"LDAP Location = **CN=EANG04W**"
"OU=EANG"
"DC=afp"
"DC=local"
That's an array with 4 string objects. So then you try to split at least one of those (because you don't specify which one) on the equals sign. You now have an array with 4 array objects, where each of those has 2 string objects:
("LDAP Location", "**CN", "EANG04W**")
("OU", "EANG")
("DC","afp")
("DC","local")
You do specify the third record at this point (arrays in PowerShell start at record 0, so [2] specifies the third record). But you didn't specify which record in the first array so it's just going to throw errors. Let's say that you actually selected what you really wanted though, and I'm guessing that would be "EANG04W". (by the way, that would be $_.Split(",")[0].Split("=")[1]). You then assign that to $Variable, and proceed to replace all of it with "Company", so after PowerShell expands the variable it would look like this:
$variable1 = "EANG04W" -replace "EANG04W", "company"
Ok, you just successfully assigned "company" to a variable. And your If statement ends there. You never output anything from inside your If statement, so Set-Content has nothing to set. Also, it would set that nothing for each and every line that is piped to the ForEach statement, re-writing the file each time, but fortunately for you the script didn't work so it didn't erase your file. Plus, since you were trying to pipe to Set-Content, there was no output at the end of the pipeline, you have assigned absolutely nothing to $content.
So let's try and fix it, shall we? First line? Works great! No change. Now, we aren't saving anything in a variable, we just want to update a file's content, so there's no need to have $Content = there. We'll just move on then, shall we? We pipe the Get-Content into a ForEach loop, just like you tried to do. Once inside the ForEach loop, we're going to do things a bit differently though. The -replace method performs a RegEx match. We can use that to our advantage here. We will replace the text you are interested in for each line, and if it's not found, no replacement will be made, and pass each line on down the pipeline. That will look something like this for the inside of the ForEach:
$_ -replace "(<=CN\=).*?(?=,)", "Company"
The breakdown of that RegEx match can be seen here: https://regex101.com/r/gH6hP2/1
But, let's just say that it looks for text that has 'CN=' immediately before it, and goes up to the first comma following it. In your example, that includes the two trailing asterisks, but it doesn't touch the leading ones. Is that what you intended? That would make the last line of your example file:
Primary Owner = **CN=Company,OU=EANG,DC=afp,DC=localenter code here
Well, if that is as intended, then we have a winner. Now we close out the ForEach loop, and pipe the output to Set-Content and we're all set! Personally, I would highly suggest outputting to a new file, in case you need to reference the original file for some reason later, so that's what I'm going to do.
$file = 'D:\sources\scripts\2.txt'
$newfile = Join-Path (split-path $file) -ChildPath ('Updated-'+(split-path $file -Leaf))
Get-Content $file | ForEach{$_ -replace "(?<=CN\=).*?(?=,)", "Company"} | Set-Content $newfile
Ok, that's it. That code will produce D:\sources\scripts\Updated-2.txt with the following content:
Computer Location = afp.local/EANG
Description = RED_TXT
Device Name = EANG04W
Domain Name = afp.local
Full Name = Admintech
Hardware Monitoring Type = ASIC2
Last Blocked Application Scan Date = 1420558125
Last Custom Definition Scan Date = 1348087114
Last Hardware Scan Date = 1420533869
Last Policy Sync Date = 1420533623
Last Software Scan Date = 1420533924
Last Update Scan Date = 1420558125
Last Vulnerability Scan Date = 1420558125
LDAP Location = **CN=Company,OU=EANG,DC=afp,DC=local
Login Name = ADMINTECH
Main Board OEM Name = Dell Inc.
Number of Files = 384091
Primary Owner = **CN=Company,OU=EANG,DC=afp,DC=localenter code here

Resources