ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

ืื—ืช ื”ืคื•ื ืงืฆื™ื•ืช ื”ื ื—ื•ืฆื•ืช ื‘ื™ื•ืชืจ, ืฉืื™ื ื” ื‘ื’ืจืกื” ื”ื—ื™ื ืžื™ืช ืฉืœ GitLab, ื”ื™ื ื”ื™ื›ื•ืœืช ืœื”ืฆื‘ื™ืข ื ื’ื“ ื‘ื™ื˜ื•ืœ ืžืื’ืจ ื•ืฉืœื™ื˜ื” ื‘ื‘ืงืฉืช ืžื™ื–ื•ื’ (MR), ื‘ืืžืฆืขื•ืช ืกืงื™ืจืช ื”ืงื•ื“ ื”ืžื—ื™ื™ื‘ืช.

ื‘ื•ืื• ื ืขืฉื” ืืช ื”ืคื•ื ืงืฆื™ื•ื ืœื™ื•ืช ื”ืžื™ื ื™ืžืœื™ืช ื‘ืขืฆืžื ื• - ื ืืกื•ืจ ืขืœ ืžื™ื–ื•ื’ ืขื“ ืฉื›ืžื” ืžืคืชื—ื™ื ื™ืชื ื• ืื’ื•ื“ืœ ืœ-MR.

ืœืžื” ื–ื” ื‘ื›ืœืœ ื ื—ื•ืฅ?

ื”ืืจื’ื•ืŸ ืฉืœื ื• ื™ื›ื•ืœ ืœื”ืจืฉื•ืช ืœืขืฆืžื• ื‘ืงืœื•ืช ืœืงื ื•ืช ืจื™ืฉื™ื•ืŸ GitLab. ืื‘ืœ, ืžื›ื™ื•ื•ืŸ ืฉื”ืคื™ืชื•ื— ืžืชื‘ืฆืข ื‘ืœื•ืœืื” ืกื’ื•ืจื” ืœืœื ื’ื™ืฉื” ืœืื™ื ื˜ืจื ื˜, ื•ื™ืฉ ืชื›ื ื•ืŸ ืชืงืฆื™ื‘ื™ ืงืคื“ื ื™, ืจื›ื™ืฉืช ืจื™ืฉื™ื•ื ื•ืช ื‘ื ื™ื”ื•ืœ ืขืฆืžื™ ืขื ื”ืคื•ื ืงืฆื™ื•ื ืœื™ื•ืช ื”ื“ืจื•ืฉื” ื™ื›ื•ืœื” ืœื”ื™ืžืฉืš ื—ื•ื“ืฉื™ื ืจื‘ื™ื, ืื‘ืœ ืฆืจื™ืš ืœืขืฉื•ืช ืขื‘ื•ื“ื” ืขื›ืฉื™ื•.

ื›ืชื•ืฆืื” ืžื›ืš ืขืœื™ืš:

  • ืื• ืœืืกื•ืจ ืœื—ืœื•ื˜ื™ืŸ ืžื™ื–ื•ื’ ื‘ืกื ื™ืคื™ื ืžื•ื’ื ื™ื ืขื‘ื•ืจ ืžืคืชื—ื™ื ืžืกื•ื™ืžื™ื, ืื‘ืœ ืื– ืžืคืชื—ื™ื ืฉื™ืฉ ืœื”ื ืืช ื”ื–ื›ื•ืช ืœืžื™ื–ื•ื’ ืžืงื‘ืœื™ื ื”ืชื ื’ืฉื•ื™ื•ืช ื‘ืขืช ืžื™ื–ื•ื’ MRs ืฉืœ ืื ืฉื™ื ืื—ืจื™ื ื›ื‘ื•ื ื•ืก;
  • ืื• ืชืŸ ืืช ื”ื”ื–ื“ืžื ื•ืช ืœื‘ืฆืข ืžื™ื–ื•ื’ื™ื ื‘ืœืชื™ ืžื‘ื•ืงืจื™ื ืขื ืกื ื™ืฃ ื”ืžืืกื˜ืจ ืฉืœืš ืœืœื ืกืงื™ืจืช ืงื•ื“, ื’ื ืื ื–ื” ื’'ื•ื ื™ื•ืจ, ืฉื”ืชืงื‘ืœ ืœืขื‘ื•ื“ื” ืจืง ืืชืžื•ืœ.

ื”ื“ื‘ืจ ื”ืจืืฉื•ืŸ ืฉืขืฉื™ืชื™ ื”ื™ื” ื’ื•ื’ืœ, ืžืชื•ืš ืืžื•ื ื” ืฉืžื™ืฉื”ื• ื‘ื”ื—ืœื˜ ื›ื‘ืจ ืขืฉื” ืžืฉื”ื• ื“ื•ืžื” (ื‘ืœื™ ืœืฉื ื•ืช ืืช ื”ืงื•ื“), ืื‘ืœ ื”ืชื‘ืจืจ ืฉืขื“ื™ื™ืŸ ืœื ื”ื™ื” ื™ื™ืฉื•ื ื›ื–ื” ื‘ื’ืจืกืช ื”ืงื”ื™ืœื”.

ืชื›ื ื™ืช ืขื‘ื•ื“ื” ื›ืœืœื™ืช

ื›ื“ื•ื’ืžื”, ื‘ื•ืื• ื ื’ื“ื™ืจ ืื™ืฉื•ืจื™ ื‘ืงืฉื•ืช ืžื™ื–ื•ื’ ื‘ืžืื’ืจ ื‘ื“ื™ืงื” ืžื™ื™ืืค:

  1. ื‘ื•ืื• ื ื™ืฆื•ืจ ืืกื™ืžื•ืŸ ืœื’ื™ืฉื” ืœ-GitLab API (ื“ืจื›ื• ื ืงื‘ืœ ืžื™ื“ืข ืขืœ ืžืกืคืจ ื”ื”ืฆื‘ืขื•ืช "ื‘ืขื“" ื•"ื ื’ื“")
  2. ื‘ื•ืื• ื ื•ืกื™ืฃ ืืช ื”ืืกื™ืžื•ืŸ ืœืžืฉืชื ื™ GitLab
  3. ื‘ื•ืื• ื ืฉื‘ื™ืช ืืช ื”ืžื™ื–ื•ื’ ื‘ืžืงืจื” ืฉืœ ืฉื’ื™ืื•ืช ื‘ืฆื ืจืช (ืื ืื™ืŸ ืžืกืคื™ืง ื”ืฆื‘ืขื•ืช ื‘ืขื“)
  4. ื‘ื•ืื• ื ื’ื“ื™ืจ ืืช ืื™ืžื•ืช ื”ื”ืฆื‘ืขื” ื›ื—ืœืง ืžืฆื™ื ื•ืจ ื”-CI/CD
  5. ืื ื• ืื•ืกืจื™ื ืขืœ ื”ืชื—ื™ื™ื‘ื•ื™ื•ืช ืœืกื ื™ืคื™ื ืžื•ื’ื ื™ื; ื›ืœ ื”ืฉื™ื ื•ื™ื™ื ืžืชื‘ืฆืขื™ื ืจืง ื‘ืืžืฆืขื•ืช MR
  6. ื‘ื•ื ื ื‘ื“ื•ืง ืžื” ืงืจื” ื‘ืกื•ืฃ

1. ืฆื•ืจ ืืกื™ืžื•ืŸ ืœื’ื™ืฉื” ืœ-API

ืขื‘ื•ืจ ืืœ ื”ื’ื“ืจื•ืช ืžืฉืชืžืฉ โ† Access Tokens ื•ืจืฉื•ื ืืช ื”ืืกื™ืžื•ืŸ:

ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

ื—ืฉื‘ื•ืŸ ืœืงื‘ืœืช ืืกื™ืžื•ืŸ
ื’ื™ืฉืช API ืžืืคืฉืจืช ืœืš ืœืขืฉื•ืช ื›ืžืขื˜ ื›ืœ ื“ื‘ืจ ืขื ื”ืžืื’ืจื™ื ืฉืœืš, ืื– ืื ื™ ืžืžืœื™ืฅ ืœื™ืฆื•ืจ ื—ืฉื‘ื•ืŸ Gitlab ื ืคืจื“, ืœืชืช ืœื• ื–ื›ื•ื™ื•ืช ืžื™ื ื™ืžืœื™ื•ืช ืœืžืื’ืจื™ื ืฉืœืš (ืœืžืฉืœ Reporter) ื•ืœืงื‘ืœ ืืกื™ืžื•ืŸ ืœื—ืฉื‘ื•ืŸ ื–ื”.

2. ื”ื•ืกืฃ ืืช ื”ืืกื™ืžื•ืŸ ืœืžืฉืชื ื™ Gitlab

ืœืžืฉืœ, ื‘ืฉืœื‘ ื”ืงื•ื“ื ืงื™ื‘ืœื ื• ืืกื™ืžื•ืŸ QmN2Y0NOUFlfeXhvd21ZS01aQzgK

ืคืชื— ืืช ื”ื’ื“ืจื•ืช โ† CI/CD โ† ืžืฉืชื ื™ื โ† ื”ื•ืกืฃ ืžืฉืชื ื” โ† GITLAB_TOKEN_FOR_CI

ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

ื›ืชื•ืฆืื” ืžื›ืš ืื ื• ืžืงื‘ืœื™ื:

ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

ื ื™ืชืŸ ืœืขืฉื•ืช ื–ืืช ื‘ืžืื’ืจ ืื—ื“ ืื• ืขืœ ืงื‘ื•ืฆืช ืžืื’ืจื™ื.

3. ืื ื• ืฉืžื™ื ืื™ืกื•ืจ ืขืœ ืžื™ื–ื•ื’ ืื ืœื ื™ืชืงื‘ืœ ืื™ืฉื•ืจ ืฉืœ ืขืžื™ืชื™ื ืœืื—ืจ ืกืงื™ืจืช ื”ืงื•ื“.

ื‘ืžืงืจื” ืฉืœื ื•, ื”ืื™ืกื•ืจ ืขืœ ืžื™ื–ื•ื’ ื™ื”ื™ื” ืฉืฆื™ื ื•ืจ ื”ื”ืจื›ื‘ื” ื™ื—ื–ื™ืจ ืฉื’ื™ืื” ืื โ€‹โ€‹ืœื ื™ื”ื™ื• ืžืกืคื™ืง ื”ืฆื‘ืขื•ืช.

ืขื‘ื•ืจ ืืœ ื”ื’ื“ืจื•ืช โ† ื›ืœืœื™ โ† ื‘ืงืฉื•ืช ืžื™ื–ื•ื’ โ† ืžื™ื–ื•ื’ ืฆ'ืงื™ื ื•ื”ืคืขืœ ืืช ื”ืืคืฉืจื•ืช ื›ื™ืฉื•ืจื™ ื”ืจื›ื‘ื” ื—ื™ื™ื‘ื™ื ืœื”ืกืชื™ื™ื ื‘ื”ืฆืœื—ื”.

ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

4. ื”ืงืžืช ื”ืฆื™ื ื•ืจ

ืื ืขื“ื™ื™ืŸ ืœื ื™ืฆืจืช ืฆื™ื ื•ืจ CI/CD ืขื‘ื•ืจ ื”ื™ื™ืฉื•ื ืฉืœืš
ืฆื•ืจ ืงื•ื‘ืฅ ื‘ืฉื•ืจืฉ ื”ืžืื’ืจ .gitlab-ci.yml ืขื ื”ืชื•ื›ืŸ ื”ืคืฉื•ื˜ ื‘ื™ื•ืชืจ:

stages:
  - build
  - test

variables:
  NEED_VOTES: 1

include:
  - remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"

run-myapp:
  stage: build
  script: echo "Hello world"

ืžืื’ืจ ื ืคืจื“ ืขื‘ื•ืจ ืชืฆื•ืจืช CI/CD
ืื ื™ ืžืžืœื™ืฅ ืœื™ืฆื•ืจ ืžืื’ืจ ื ืคืจื“ ืฉื‘ื• ืืชื” ืฆืจื™ืš ืœื™ืฆื•ืจ ืงื•ื‘ืฅ myapp.gitlab-ci.yml ื›ื“ื™ ืœื”ื’ื“ื™ืจ ืืช ื”ืฆื™ื ื•ืจ. ื›ืš ืชื•ื›ืœ ืœืฉืœื•ื˜ ื˜ื•ื‘ ื™ื•ืชืจ ื‘ื’ื™ืฉื” ืฉืœ ืžืฉืชืชืคื™ื ืฉื™ื›ื•ืœื™ื ืœืฉื ื•ืช ืืช ืฆื™ื ื•ืจ ื”ื‘ื ื™ื™ื” ื•ืœืงื‘ืœ ืืกื™ืžื•ืŸ ื’ื™ืฉื”.

ื™ื”ื™ื” ืฆื•ืจืš ืœืฆื™ื™ืŸ ืืช ื”ืžื™ืงื•ื ืฉืœ ืงื•ื‘ืฅ ื”ืฆื™ื ื•ืจ ื”ื—ื“ืฉ ืขืœ ื™ื“ื™ ืžืขื‘ืจ ืœืžืื’ืจ myapp - ื”ื’ื“ืจื•ืช - CI/CD - ืงื•ื•ื™ ื”ืจื›ื‘ื” - ื ืชื™ื‘ ืชืฆื•ืจืช CI ืžื•ืชืื ืื™ืฉื™ืช - ืฆื™ื™ืŸ ืืช ื”ืงื•ื‘ืฅ ื”ื—ื“ืฉ, ืœืžืฉืœ. myapp.gitlab-ci.yml@gitlab-ce-mr-approvals/Ci

ื˜ื™ืค: ื”ืฉืชืžืฉ ื‘-linter ื›ื“ื™ ืœื‘ืฆืข ืฉื™ื ื•ื™ื™ื ื‘ืงื‘ืฆื™ GitLab CI
ื’ื ืื ืืชื” ืขื•ื‘ื“ ืœื‘ื“, ืขื‘ื•ื“ื” ื“ืจืš MR ืชื”ื™ื” ืขื–ืจื” ื˜ื•ื‘ื”, ืœื”ืจื™ืฅ ืืช ื›ืœ ื”ืฉื™ื ื•ื™ื™ื ืฉืœืš ื‘ืงื‘ืฆื™ ืฆื™ื ื•ืจ ื“ืจืš linter. ืื ืชืขืฉื” ื˜ืขื•ืช ื‘ืชื—ื‘ื™ืจ ืฉืœ ืงื•ื‘ืฅ ื”-YAML, ื”ื•ื ืœื ื™ืฉื‘ื•ืจ ืืช ืฆื™ื ื•ืจ ื”ื™ื™ืฆื•ืจ ืฉืœืš, ืืœื ืคืฉื•ื˜ ื™ื—ืกื•ื ืืช ื”ืžื™ื–ื•ื’.

ื“ื•ื’ืžื” ืœืžื™ื›ืœื™ื ืขื ืจืฆื•ืขื•ืช ืฉืชื•ื›ืœื• ืœื‘ื ื•ืช ื‘ืฆื™ื ื•ืจ ืฉืœื›ื:

hub.docker.com/r/gableroux/gitlab-ci-lint
hub.docer.com/r/sebiwi/gitlab-ci-validate

ื•ื“ื•ื’ืžื” ืœืฉืœื‘ ื”ืื™ืžื•ืช:

stages:
  - lint

lint:
  stage: lint
  image: sebiwi/gitlab-ci-validate:1.3.0
  variables:
    GITLAB_HOST: https://gitlab.com
  script:
    - CI_FILES=(./*.yml)
    - for f in "${CI_FILES[@]}"; do
        gitlab-ci-validate $f;
      done;

ื ื•ืชืจ ืœื”ื•ืกื™ืฃ ื›ืžื” ืคืจืžื˜ืจื™ื ืœืฆื™ื ื•ืจ ืฉืœืš ื›ื“ื™ ืฉื–ื” ื™ืขื‘ื•ื“:

stages:
- test

variables:
NEED_VOTES: 1

include:
- remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"

ื”ืžืฉืชื ื” NEED_VOTES ืงื•ื‘ืข ื›ืžื” "ืื’ื•ื“ืœ ืœืžืขืœื”" ืฆืจื™ืš ืœื”ื™ื•ืช ืœ-MR ื›ื“ื™ ืฉื”ืžื™ื–ื•ื’ ื™ื”ื™ื” ื–ืžื™ืŸ. ืขืจืš ืฉื•ื•ื” ืœืื—ื“ ืื•ืžืจ ืฉืืชื” ื‘ืขืฆืžืš ื™ื›ื•ืœ ืœืืฉืจ ืืช ื”-MR ืฉืœืš ืขืœ ื™ื“ื™ "ืื”ื‘ืชื™" ืื•ืชื•.

include ื›ื•ืœืœ ืืช ืฉืœื‘ ื”ื‘ื“ื™ืงื”, ืฉื‘ื•ื“ืง ืืช ืžืกืคืจ ื”"ืœื™ื™ืงื™ื".

ื”ืฆื™ื ื•ืจ ื”ืคืฉื•ื˜ ื‘ื™ื•ืชืจ ื‘ืืžืฆืขื•ืช ื”ื“ื•ื’ืžื” ืฉืœ myapp.gitlab-ci.yml
stages:
- build
- test

variables:
NEED_VOTES: 0

include:
- remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"

run-myapp:
stage: build
image: openjdk
script:
- echo CI_MERGE_REQUEST_TARGET_BRANCH_NAME $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
- java HelloWorld.java

ืชื•ื›ืŸ check-approve.gitlab-ci.yml
ci-mr:
stage: test
script:
- echo ${CI_API_V4_URL}
- echo "CI_PROJECT_ID ${CI_PROJECT_ID}"
- echo "CI_COMMIT_SHA ${CI_COMMIT_SHA}"
- "export MR_ID=$(curl --silent --request GET --header "PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq ".[] | if .sha == \"${CI_COMMIT_SHA}\" then .id else {} end" | grep --invert-match {})"
- "export MR_TITLE=$(curl --silent --request GET --header "PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq ".[] | if .sha == \"${CI_COMMIT_SHA}\" then .title else {} end" | grep --invert-match {})"
- "export MR_WIP=$(curl --silent --request GET --header "PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq ".[] | if .sha == \"${CI_COMMIT_SHA}\" then .work_in_progress else {} end" | grep --invert-match {})"
- "export MR_UPVOTES=$(curl --silent --request GET --header "PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq ".[] | if .sha == \"${CI_COMMIT_SHA}\" then .upvotes else {} end" | grep --invert-match {})"
- "export MR_DOWNVOTES=$(curl --silent --request GET --header "PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq ".[] | if .sha == \"${CI_COMMIT_SHA}\" then .downvotes else {} end" | grep --invert-match {})"
- MR_VOTES=$(expr ${MR_UPVOTES} - ${MR_DOWNVOTES})
- NEED_VOTES_REAL=${NEED_VOTES:-1}
- echo "MR_ID ${MR_ID} MR_TITLE ${MR_TITLE} MR_WIP ${MR_WIP} MR_UPVOTES ${MR_UPVOTES} MR_DOWNVOTES ${MR_DOWNVOTES}"
- echo "MR_VOTES ${MR_VOTES} Up vote = 1, down vote = -1, MR OK if votes >=${NEED_VOTES_REAL}"
- if [ "${MR_VOTES}" -ge "$(expr ${NEED_VOTES_REAL})" ];
then
echo "MR OK";
else
echo "MR ERROR Need more votes";
exit 1;
fi
image: laptevss/gitlab-api-util
rules:
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^release/.*$/'

ืžื™ื“ืข ื ื•ืกืฃ ืขืœ ืžื” ืฉืงื•ืจื” ื‘ืžื”ืœืš ื”ืื™ืžื•ืช:

  • ื™ืฉื ื” ื”ื’ื‘ืœื” ืฉื”ื‘ื“ื™ืงื” ืชืชื‘ืฆืข ืจืง ื‘ืขืช ื™ืฆื™ืจืช MR ื‘ืกื ื™ืคื™ื ืžืืกื˜ืจ ืื• ืฉื—ืจื•ืจ/*
  • ื‘ืืžืฆืขื•ืช ื”-API ืฉืœ GitLab, ืื ื• ืžืงื‘ืœื™ื ืืช ืžืกืคืจ "ืื”ื‘ืชื™" ื•-"ื“ื™ืกืœื™ื™ืงื™ื"
  • ืœื—ืฉื‘ ืืช ื”ื”ื‘ื“ืœ ื‘ื™ืŸ ืชื’ื•ื‘ื•ืช ื—ื™ื•ื‘ื™ื•ืช ื•ืฉืœื™ืœื™ื•ืช
  • ืื ื”ื”ืคืจืฉ ืงื˜ืŸ ืžื”ืขืจืš ืฉื”ื’ื“ืจื ื• ื‘-NEED_VOTES, ืื ื• ื—ื•ืกืžื™ื ืืช ื™ื›ื•ืœืช ื”ืžื™ื–ื•ื’

5. ืœืืกื•ืจ ืžืชื—ื™ื™ื‘ ืœืขื ืคื™ื ืžื•ื’ื ื™ื

ืื ื• ืžื’ื“ื™ืจื™ื ืืช ื”ืกื ื™ืคื™ื ืฉืœื’ื‘ื™ื”ื ืขืœื™ื ื• ืœืขืจื•ืš ื‘ื™ืงื•ืจื•ืช ืงื•ื“ ื•ืžืฆื™ื™ื ื™ื ืฉื ื™ืชืŸ ืœืขื‘ื•ื“ ืื™ืชื ืจืง ื“ืจืš MR.

ื›ื“ื™ ืœืขืฉื•ืช ื–ืืช, ืขื‘ื•ืจ ืืœ ื”ื’ื“ืจื•ืช โ† ืžืื’ืจ โ† ืกื ื™ืคื™ื ืžื•ื’ื ื™ื:

ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

6. ื‘ื“ื•ืง

ื”ื’ื“ืจ NEED_VOTES: 0

ืื ื—ื ื• ืขื•ืฉื™ื MR ื•ืฉืžื™ื "ื“ื™ืกืœื™ื™ืง".

ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

ื‘ื™ื•ืžื ื™ ื”ื‘ื ื™ื™ื”:

ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

ื›ืขืช ืฉื™ื "ืื”ื‘ืชื™" ื•ื”ืชื—ืœ ืœื‘ื“ื•ืง ืฉื•ื‘:

ืกืงื™ืจืช ืงื•ื“ ื‘-Gitlab CE: ืื ืื™ืŸ ืื™ืฉื•ืจื™ ื‘ืงืฉืช ืžื™ื–ื•ื’, ืื‘ืœ ืื ื™ ืžืื•ื“ ืจื•ืฆื”

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”