gerrit

[!NOTE|label:references:]

.gitconfig

$ git config --global gitreview.username <UserName>
$ git config --global gitreview.remote origin

[!TIP]

Special references

  • refs/changes/*

  • refs/meta/config

  • refs/meta/dashboards/*

  • refs/notes/review

Magic references

  • refs/for/<branch ref>

get project.config

  • clone the repo

    $ git clone <repo url>
    # or update the local repo to HEAD
    $ git pull [--rebase]
  • checkout meta/config

    $ git fetch origin refs/meta/config:refs/remotes/origin/meta/config
    $ git checkout meta/config

    or

    $ git fetch ssh://localhost:29418/project refs/meta/config
    $ git checkout FETCH_HEAD

publish to remote

$ git add --all .
$ git commit -m "<add your comments here>"
  • submit directly

    $ git push origin meta/config:meta/config

    or

    $ git push origin HEAD:refs/meta/config
  • submit review

    [!NOTE|label:references:]

    $ git push origin HEAD:refs/for/refs/meta/config
    • or

      $ git push origin meta/config:refs/for/refs/meta/config

update meta/config if remotes update

$ git fetch origin --force refs/meta/config:refs/remotes/origin/meta/config

$ git pull origin refs/meta/config
# or
$ git merge meta/config

reset to remotes

$ git fetch origin --force refs/meta/config:refs/remotes/origin/meta/config
$ git reset --hard remotes/origin/meta/config

useful refs

sandbox:

refs/heads/sandbox/${username}/*

its-jira:

  • for project specific

    [commentlink "its-jira"]
      match = ^[ \\t]*PROJECT-([0-9]{1,5}):
      link = https://<jira-domain>:<jira-port>/browse/PROJECT-$1
  • for common setup

    [plugin "its-jira"]
      association = OPTIONAL
      branch = ^refs/heads/.*
      branch = ^refs/heads/stable-.*
      commentOnChangeAbandoned = false
      commentOnChangeCreated = true
      commentOnChangeMerged = true
      commentOnChangeRestored = false
      commentOnCommentAdded = false
      commentOnFirstLinkedPatchSetCreated = true
      commentOnPatchSetCreated = false
      commentOnRefUpdatedGitWeb = true
      enabled = enforced
    [commentlink "its-jira"]
      match = ^[ \\t]*([A-Za-z]*-[0-9]{1,5}):
      link = https://<jira-domain>:<jira-port>/browse/$1
    [commentlink "changeid"]
      match = (I[0-9a-f]{8,40})
      link = "#/q/$1"

verified label

[label "Verified"]
    function = MaxWithBlock
    defaultValue = 0
    copyAllScoresIfNoCodeChange = true
    value = -1 Fails
    value =  0 No score
    value = +1 Verified

change-id

[receive]
  requireChangeId = true
  createNewChangeForAllNotInTarget = false
  maxObjectSizeLimit = 6m
  maxBatchChanges = 1
[commentlink "changeid"]
  match = (I[0-9a-f]{8,40})
  link = "#/q/$1"

freeze master branch

[!TIP] One quirk is that the shortest possible pattern expansion must be a valid ref name thus ^refs/heads/.*/name will fail because refs/heads//name is not a valid reference but ^refs/heads/.+/name will work.

About the refs/for namespace

[!TIP] references:

refs/for/* syntax is just a short name for refs/for/refs/*:

  • project.config

    [access "refs/for/refs/heads/master"]
      push = block group user/Marslo Jiao (marslo)
      push = block group Registered Users
      submit = block group Registered Users
      submit = block group group user/Marslo Jiao (marslo)
      addPatchSet = block group user/Marslo Jiao (marslo)
      addPatchSet = block group Registered Users
      pushMerge = block group user/Marslo Jiao (marslo)
      pushMerge = block group Registered Users
  • groups

    ...
    global:Project-Owners      Project Owners
    global:Registered-Users    Registered Users
    ...
    user:marslo                user/Marslo Jiao(marslo)
    ...

freeze multiple branches (stable & release) for the specific account

  • project.config

    [access "^refs/for/refs/heads/(stable|release)$"]
      push = block group Registered Users
      submit = block group Registered Users
      addPatchSet = block group Registered Users
      pushMerge = block group Registered Users
    [access "^refs/heads/(stable|release)$"]
      read = group user/Marslo Jiao (marslo)
      push = +force group user/Marslo Jiao (marslo)
      pushMerge = group user/Marslo Jiao (marslo)
    • or using exclusiveGroupPermissions

      [access "^refs/heads/backup/(master|dev|staging|stable)/.+$"]
             exclusiveGroupPermissions = create delete push pushMerge
             create = group Project Owners
             create = block group Registered Users
             delete = block group Registered Users
             push = block group Registered Users
             pushMerge = block group Registered Users
      [access "^refs/for/refs/heads/backup/(master|dev|staging|stable)/.+$"]
             exclusiveGroupPermissions = addPatchSet create push pushMerge submit
             addPatchSet = block group Registered Users
             create = block group Registered Users
             push = block group Registered Users
             pushMerge = block group Registered Users
             submit = block group Registered Users
  • groups

    ...
    global:Project-Owners      Project Owners
    global:Registered-Users    Registered Users
    ...
    user:marslo                user/Marslo Jiao(marslo)
    ...

restriction for branches (feature1, feature2 and master) for only allow code review merge, forbidden code push

  • project.config

    [access "refs/*"]
      read = group Project Owners
      read = group user/Marslo Jiao (marslo)
    [access "refs/for/*"]
      addPatchSet = group Project Owners
      addPatchSet = group user/Marslo Jiao (marslo)
      push = group Project Owners
      push = group user/Marslo Jiao (marslo)
      pushMerge = group Project Owners
      pushMerge = group user/Marslo Jiao (marslo)
    [access "^refs/heads/(feature1|feature2|master)$"]
      push = block group Registered Users
      pushMerge = block group Registered Users
      submit = group Change Owner
  • groups

    ...
    global:Project-Owners      Project Owners
    global:Registered-Users    Registered Users
    ...
    user:marslo                user/Marslo Jiao(marslo)
    ...

example of project.config

  • project.config

    [project]
      description = Gerrit Code Review
    [access "refs/*"]
      owner = group google/gerritcodereview-maintainers@googlegroups.com
    [access "refs/heads/*"]
      label-Code-Review = -2..+2 group google/gerritcodereview-maintainers@googlegroups.com
      label-Code-Review = -2..+2 group polygerrit-maintainers
      label-Verified = -1..+1 group Change Owner
      label-Verified = -1..+1 group gerrit-verifiers
      label-Code-Style = -1..+1 group gerrit-verifiers
      label-Verified-Notedb = -1..+1 group gerrit-verifiers
      label-Library-Compliance = -1..+1 group gerrit-lib
      label-Library-Compliance = -1..+0 group google/gerritcodereview-maintainers@googlegroups.com
      submit = group Change Owner
      submit = group google/gerritcodereview-maintainers@googlegroups.com
      create = group google/gerritcodereview-maintainers@googlegroups.com
      abandon = group gerrit-verifiers
      editTopicName = +force group google/gerritcodereview-maintainers@googlegroups.com
      removeReviewer = group google/gerritcodereview-maintainers@googlegroups.com
      publishDrafts = group google/gerritcodereview-maintainers@googlegroups.com
    [access "refs/tags/*"]
      create = group gerrit-release-creators
      create = group google/gerritcodereview-maintainers@googlegroups.com
      createTag = group gerrit-release-creators
      createTag = group google/gerritcodereview-maintainers@googlegroups.com
      createSignedTag = group gerrit-release-creators
      createSignedTag = group google/gerritcodereview-maintainers@googlegroups.com
    [access]
      inheritFrom = Public-Projects
    [receive]
      rejectImplicitMerges = true
    [reviewer]
      enableByEmail = true
    [label "Verified"]
      function = MaxNoBlock
      copyAllScoresIfNoCodeChange = true
      value = -1 Fails
      value = 0 No score
      value = +1 Verified
      defaultValue = 0
    [label "Code-Style"]
      function = MaxWithBlock
      copyAllScoresIfNoCodeChange = true
      value = -1 Wrong Style or Formatting
      value = 0 No score
      value = +1 Style Verified
      defaultValue = 0
    [label "Library-Compliance"]
      function = MaxWithBlock
      copyAllScoresIfNoCodeChange = true
      copyAllScoresOnTrivialRebase = true
      value = -1 Do not submit
      value = 0 No score
      value = +1 Approved
      defaultValue = 0
    [access "refs/for/refs/meta/dashboards/*"]
      push = group google/gerritcodereview-maintainers@googlegroups.com
    [access "refs/meta/dashboards/*"]
      label-Code-Review = -2..+2 group google/gerritcodereview-maintainers@googlegroups.com
      label-Code-Review = -1..+1 group Registered Users
      label-Verified = -1..+1 group gerrit-verifiers
      label-Verified = -1..+1 group google/gerritcodereview-maintainers@googlegroups.com
      submit = group google/gerritcodereview-maintainers@googlegroups.com
      forgeAuthor = group google/gerritcodereview-maintainers@googlegroups.com
      label-Code-Style = -1..+1 group google/gerritcodereview-maintainers@googlegroups.com
    [access "refs/for/refs/meta/config"]
      push = group gerrit-verifiers
    [notify "polygerrit-reviews"]
      email = polygerrit-reviews@google.com
      type = all_comments
      type = submitted_changes
      header = cc
      filter = file:polygerrit-ui
    [access "refs/heads/infra/config"]
      push = group gerrit-tricium-admins

[!TIP] check also:

submit_rule(S) :-
    gerrit:default_submit(X),
    X =.. [submit | Ls],
    add_non_author_approval(Ls, R),
    S =.. [submit | R].

add_non_author_approval(S1, S2) :-
    gerrit:commit_author(A),
    gerrit:commit_label(label('Code-Review', 2), R),
    R \= A, !,
    S2 = [label('Non-Author-Code-Review', ok(R)) | S1].
add_non_author_approval(S1, [label('Non-Author-Code-Review', need(_)) | S1]).
  • by project.config

    [access "refs/*"]
      label-Code-Review = block -2..+2 group Change Owner
      exclusiveGroupPermissions = label-Code-Review

[!TIP] check also:

  • optional validation

    submit_rule(S) :-
        gerrit:default_submit(X),
        X =.. [submit | Ls],
        require_ticket_check_for_ticket(Ls, Nls),
        S =.. [submit | Nls].
    
    require_ticket_check_for_ticket(S1, S2) :-
        gerrit:commit_message_matches('^issue-[\\d]+\\s?:\\s?[\\w\\W]+'),
        !,
        S2 = [label('Ticket-Checked', need(_)) | S1].
    
    require_ticket_check_for_ticket(S1, S2) :-
        !, S2 = S1.
  • optional validation with auto vote

    submit_rule(S) :-
        gerrit:default_submit(X),
        X =.. [submit | Ls],
        require_ticket_check_for_ticket(Ls, Nls),
        S =.. [submit | Nls].
    
    require_ticket_check_for_ticket(S1, S2) :-
        gerrit:commit_message_matches('\\[issue-[\\d]{2}\\]\\s?:\\s?[\\w\\W]+'),
        !,
        S2 = [label('Ticket-Checked', ok(user(824))) | S1].
    
    require_ticket_check_for_ticket(S1, S2) :-
        !, S2 = S1.
  • mandatory validation

    submit_rule(S) :-
      gerrit:default_submit(X), % get the current submit structure
      X=.. [submit | Ls],
      require_ticket_check_for_ticket(Ls, Nls),
      S=.. [submit | Nls].
    
    require_ticket_check_for_ticket(S1, S2) :-
       gerrit:commit_message_matches('\\[issue-[\\d]{2}\\][\\s\\S]+'),
       !,
       S2 = [label('Ticket-Checked', ok(user(790))) | S1]. % Add the label and automatically approval by user-id: 790
    
    require_ticket_check_for_ticket(S1, [label('Ticket-Checked', need(_)) | S1]).

api

regular options

                                  a might means [a]pi
                                    
$ curl -X PUT    http://domain.name/a/path/to/api/
$ curl -X POST   http://domain.name/a/path/to/api/
$ curl -X DELETE http://domain.name/a/path/to/api/

sending data

  • json with file

    $ curl -X PUT \
           -d@testdata.json \
           --header "Content-Type: application/json" \
           http://domain.name/a/path/to/api/
  • json with string

    $ curl -X POST \
           -H "Content-Type: application/json" https://domain.name/a/changes/<number>/move \
           -d '{ "destination_branch" : "target/branch/name" }'
    )]}'
    {
      "id": "marslo-project~target%2Fbranch%2Fname~Id90057ab632eb93be2fa9128a9d624664008cb4a",
      "project": "marslo-project",
      "branch": "target/branch/name",
      "hashtags": [],
      "change_id": "Id90057ab632eb93be2fa9128a9d624664008cb4a",
      "subject": "marslo: testing api move",
      "status": "NEW",
      "created": "2022-01-21 05:21:25.000000000",
      "updated": "2022-05-17 06:56:37.000000000",
      "submit_type": "FAST_FORWARD_ONLY",
      "mergeable": false,
      "insertions": 8,
      "deletions": 8,
      "unresolved_comment_count": 0,
      "has_review_started": true,
      "_number": 94490,
      "owner": {
        "_account_id": 790
      },
      "requirements": []
    }
    
    # or
    $ curl -X POST \
           -H "Content-Type: application/json" https://domain.name/a/changes/<number>/move \
           -d '{
                "destination_branch" : "target/branch/name"
           }' |
      tail -n +2 |
      jq -r .branch
  • txt

    $ curl -X PUT \
           --data-binary @testdata.txt \
           --header "Content-Type: text/plain" \
           http://domain.name/a/path/to/api/

verifying header content

$ curl -v -n -X DELETE http://domain.name/a/path/to/api/

  • get change via change-id

    $ curl -X GET 'https://domina.name/a/changes/<change-id>'
  • get change via commit-id

    $ changeid=$(git show <commit-id> --no-patch --format="%s%n%n%b" | sed -nre 's!Change-Id: (.*$)!\1!p')
    $ curl -X GET "https://domina.name/a/changes/${changeid}"

    or

    $ project=$(echo 'path/to/project' | sed 's:/:%2F:g')
    $ branch='dev'
    $ changeid=$(git show <commit-id> --no-patch --format="%s%n%n%b" | sed -nre 's!Change-Id: (.*$)!\1!p')
    $ curl -X GET "https://domina.name/a/changes/${project}~${branch}~${changeid}"

who approval the CR+2

$ curl -s -X GET https://domain.name/a/changes/${changeid}/detail |
       tail -n +2 |
       jq -r '.labels."Code-Review".approved.name'

get all vote CR-2

  • example output for .labels.<tag>.all[]

{
  "value": -2,
  "date": "2021-05-31 07:57:14.000000000",
  "permitted_voting_range": {
    "min": -2,
    "max": 2
  },
  "_account_id": 790,
  "name": "Marslo Jiao",
  "email": "marslo.jiao@gmail.com",
  "username": "marslo"
}
{
  "value": 0,
  "permitted_voting_range": {
    "min": -2,
    "max": 2
  },
  "_account_id": 124,
  "name": "John Doe",
  "email": "john@gmail.com",
  "username": "john"
}

reference:

$ curl -s -X GET https://domain.name/a/changes/${changeid}/detail |
       tail -n +2 |
       jq -r '.labels."Code-Review".all[] | select ( .value == -2 ) | .username'
#                                         : |⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂| :
#                                         :           ⇣             :
#                                         :  select ".value"== -2   :
#                                         :                         :
#                                         ⇣                         ⇣
#                                        pipe                     pipe

# or
$ curl -s -X GET https://domain.name/a/changes/${changeid}/detail |
       tail -n +2 |
      jq -r '( .labels."Code-Review".all[] | select ( .value == -2 ) ).username'
             :                                                       :
                                                                    
         expression                                              expression

# or
$ curl -s -X GET https://domain.name/a/changes/${changeid}/detail |
       tail -n +2 |
      jq -r '[ .labels."Code-Review".all[] | select ( .value == -2 ) ][].username'
             :                                                       :
                                                                    
         expression                                              expression

# or
$ curl -s -X GET https://domain.name/a/changes/${changeid}/detail |
       tail -n +2 |
      jq -r '.labels."Code-Review".all[] | select ( .value == -2 )' |
      jq -r .username                                               :
                                                                    
                                                                   pipe

who approval the V+1

$ curl -s -X GET https://domain.name/a/changes/${changeid}/detail |
       tail -n +2 |
       jq -r .labels.Verified.approved.username

access list contains account

[!NOTE]

# i.e. : check all repos who contains account marslo@sample.com
$ while read -r _proj; do
    output=$( curl -fsSL https://gerrit.sample.com/a/projects/"${_proj}"/access |
              tail -n+2 |
              jq -r '.. | .rules? | select(. != null) | keys[] | ascii_downcase | select(contains("marslo@sample.com"))';
            )
    [[ -z "${output}" ]] || echo ">> https://gerrit.sample.com/admin/repos/$(sed 's:%2F:/:g' <<< "${_proj}")"
  done < <( curl -fsSL https://gerrit.sample.com/a/projects/?d |
                  tail -n+2 |
                  jq -r '.[].id' |
                  grep --color=never -E 'keyword|keyword'
          )

all reviews at a certain time

[!NOTE|label:references:]

project='PROJECT'
branch='BRANCH'
start='2023-01-01'
end='2024-01-01'
curlOpt='--silent --insecure --globoff --netrc-file ~/.netrc'
query="project:${project}+branch:${branch}+after:${start}+before:${end}"
filter by status if necessary
query="${query}+is:closed+-is:abandoned"

echo ">> ${project} ~ ${branch}"
while IFS='|' read -r _change_id _id; do
  echo -e "\t- [${_id}] [_change_id]"
$( eval "curl ${curlOpt} 'https://gerrit.sample.com/a/changes/?q=${query}'" |
         tail -n +2 |
         jq -r '.[] | .change_id + "|" + .id'
 )

get review rate in certain time

sum=0
rnum=0
onum=0
echo ">> ${project} ~ ${branch}"
while IFS='|' read -r _change_id _id; do
  sum=$(( sum+1 ))
  output=$( eval "curl ${curlOpt} 'https://gerrit.sample.com/a/changes/${_id}/detail' | tail -n+2" )
  reviewed=$( jq -r '.labels."Code-Review".all[] | select(.value != null) | select( .value | contains(2) ) | .username' <<< "${output}" )
  owned=$( jq -r '.owner.username' <<< "${output}" )
  if grep 'marslo' <<< "${reviewed}" >/dev/null; then rnum=$(( rnum+1 )); fi
  if grep 'marslo' <<< "${owned}"    >/dev/null; then onum=$(( onum+1 )); fi
$( eval "curl ${curlOpt} 'https://gerrit.sample.com/a/changes/?q=${query}'" |
         tail -n +2 |
         jq -r '.[] | .change_id + "|" + .id'
 )
echo "${sum} ${rnum} ${onum} $(( sum-onum ))" |
      awk '{ sum=$1; reviewed=$2; owned=$3; rsum=$4; rate=$2*100/$4 } END { printf("\t- gerrit review: %s/(%s-%s) ( %s% )\n", reviewed, sum, owned, rate) }'

list gerrit projects with certain account

$ account='marslo'
$ id=1
$ gerritUrl='https://gerrit.sample.com'

$ while read -r _proj; do
    output=$( curl -fsSL "${gerritUrl}"/a/projects/"${_proj}"/access |
              tail -n+2 |
              jq -r --arg ACCOUNT "${account}" '.. | ."rules"? | select(. != null) | keys[] | ascii_downcase | select(contains($ACCOUNT))';
            )
    [[ -n "${output}" ]] && echo "[${id}] >> "${gerritUrl}"/admin/repos/$(sed 's:%2F:/:g' <<< "${_proj}")" && ((id++));
  done < <( curl -fsSL "${gerritUrl}"/a/projects/?d | tail -n+2 | jq -r '.[].id' )

reference

integrate in Jenkins

[!NOTE|label:references:]

  • stream-events

    # permission requires
    $ ssh -i id_rsa jenkins@gerrit.domain.com -p 29418 gerrit stream-events
    stream events not permitted
    
    # verify
    $ ssh -i id_rsa jenkins@gerrit.domain.com -p 29418 gerrit stream-events | jq -r .type
    ref-updated
    comment-added
  • build current patches only

    [!NOTE] Warning: The current implementation takes into account that 'Build Current Patches Only' with 'Abort new patch sets' and 'Abort patch sets with same topic' are enabled (see help for more).

  • generate ssh-key

    $ keyname='devops@jenkins'
    $ ssh-keygen -m PEM -t rsa -f ~/.ssh/${keyname} -C "${keyname}" -P '' -q

css for code block

.gr-formatted-text-0 gr-linked-text.pre.gr-formatted-text,
gr-linked-text[class*="pre"], gr-linked-text[class*="pre"] #output {
  font-family: "Comic Mono", "Monaco", "Menlo", "Andale Mono", "Ubuntu Mono", "monofur" !important;
  font-size: 16px !important;
}
.gr-formatted-text-0 gr-linked-text.pre.gr-formatted-text,
gr-linked-text[class*="pre"] {
  color: #c8c8c8 !important;
  background: #272727 !important;
  border-radius: .75em !important;
  box-shadow: 0 4px 8px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
  overflow: auto;
  display: block;
  padding: 12px 12px 1px 12px;
  margin: 0px;
}
  • gruvbox

    • background : #272727

    • front-color: #e8dbb6

  • ubunut

    • background : #3a122e

    • front-color: #eee

  • solarized

    • background : #0d2a34

    • front-color: #869395

Last updated