From 067c85c51be119e887d98e3f7329fb738275fbfe Mon Sep 17 00:00:00 2001 From: joncrall Date: Sat, 19 Mar 2022 14:56:18 -0400 Subject: [PATCH 1/4] Update deploy scripts --- .gitlab-ci.yml | 65 +++++++++++-------- dev/secrets_configuration.sh | 1 + dev/setup_secrets.sh | 122 ++--------------------------------- 3 files changed, 46 insertions(+), 142 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c492f22..b0fdbb5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,15 +1,15 @@ -# Note: expand yaml -# yaml merge-expand .gitlab-ci.yml _expandyml && cat _expandyml -# -# GITLAB LINTER -# https://gitlab.kitware.com/computer-vision/netharn/-/ci/lint +# Abuse YAML notation to make a heredoc. This will be ignored by the CI. +.__heredoc__: &__heredoc__ + - | + NOTE: INSTRUCTION HAVE BEEN MOVED TO ./dev/setup_secrets.sh + This file should need minimal modification. -# This CI file has 4 types of jobs: -# (1) in the build stage we we build the wheels on a manylinux docker image -# (2) then in the test stage we install the wheels, run unit tests, and measure coverage -# (3) after testing we sign the wheels with the CI's GPG key -# (4) finally if we are on the release branch we will push the signed wheels to pypi + Template for this files is from + ~/misc/templates/PYPKG/.gitlab-ci.yml + + Enable the opencv-hack if needed, and turn on/off the desired versions + of Python. stages: - build @@ -105,25 +105,24 @@ stages: script: - export GPG_EXECUTABLE=gpg - export GPG_KEYID=$(cat dev/public_gpg_key) - - source dev/secrets_configuration.sh - # note the variable pointed to by VARNAME_CI_SECRET is a protected variables only available on master and release branch - - CI_SECRET=${!VARNAME_CI_SECRET} - echo "GPG_KEYID = $GPG_KEYID" - $GPG_EXECUTABLE --version - openssl version - $GPG_EXECUTABLE --list-keys - $GPG_EXECUTABLE --list-keys # Decrypt and import GPG Keys / trust + # note the variable pointed to by VARNAME_CI_SECRET is a protected variables only available on master and release branch + - source dev/secrets_configuration.sh + - CI_SECRET=${!VARNAME_CI_SECRET} - GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | $GPG_EXECUTABLE --import - GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc | $GPG_EXECUTABLE --import-ownertrust - GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | $GPG_EXECUTABLE --import - $GPG_EXECUTABLE --list-keys || echo "first one fails for some reason" - $GPG_EXECUTABLE --list-keys - # The publish script only builds wheels and does gpg signing if DO_UPLOAD is no + # The publish script only builds wheels and does gpg signing if TAG_AND_UPLOAD is False - pip install requests[security] twine - MB_PYTHON_TAG=$MB_PYTHON_TAG DO_GPG=True GPG_KEYID=$GPG_KEYID TWINE_PASSWORD=$TWINE_PASSWORD TWINE_USERNAME=$TWINE_USERNAME GPG_EXECUTABLE=$GPG_EXECUTABLE DEPLOY_BRANCH=release DO_TAG=False DO_UPLOAD=False ./publish.sh - artifacts: paths: - dist/*.asc @@ -133,9 +132,10 @@ stages: only: refs: # Gitlab will only expose protected variables on protected branches - # (which I've set to be master and release), so only run this stage + # (which I've set to be main, master, and release), so only run this stage # there. - master + - main - release @@ -150,28 +150,32 @@ stages: - export GPG_EXECUTABLE=gpg - export GPG_KEYID=$(cat dev/public_gpg_key) - echo "GPG_KEYID = $GPG_KEYID" - - source dev/secrets_configuration.sh - # note the variable pointed to by VARNAME_CI_SECRET is a protected variables only available on master and release branch - - CI_SECRET=${!VARNAME_CI_SECRET} - $GPG_EXECUTABLE --version - openssl version - $GPG_EXECUTABLE --list-keys - $GPG_EXECUTABLE --list-keys - # Decrypt and import GPG Keys / trust + # note the variable pointed to by VARNAME_CI_SECRET is a protected variables only available on master and release branch + - source dev/secrets_configuration.sh + - CI_SECRET=${!VARNAME_CI_SECRET} + - PUSH_TOKEN=${!VARNAME_PUSH_TOKEN} + - TWINE_PASSWORD=${!VARNAME_TWINE_PASSWORD} + - TWINE_USERNAME=${!VARNAME_TWINE_USERNAME} - GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | $GPG_EXECUTABLE --import - GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc | $GPG_EXECUTABLE --import-ownertrust - GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | $GPG_EXECUTABLE --import - $GPG_EXECUTABLE --list-keys || echo "first one fails for some reason" - $GPG_EXECUTABLE --list-keys # Install twine - - pip install six pyopenssl ndg-httpsclient pyasn1 -U + - pip install pyopenssl ndg-httpsclient pyasn1 -U - pip install requests[security] twine # Execute the publish script for real this time - - MB_PYTHON_TAG=$MB_PYTHON_TAG DO_GPG=True GPG_KEYID=$GPG_KEYID TWINE_PASSWORD=$TWINE_PASSWORD TWINE_USERNAME=$TWINE_USERNAME GPG_EXECUTABLE=$GPG_EXECUTABLE CURRENT_BRANCH=release DEPLOY_BRANCH=release DO_TAG=True DO_UPLOAD=True ./publish.sh - # Have the server git-tag the release and push the tags - - VERSION=$(python -c "import setup; print(setup.VERSION)") + - TWINE_REPOSITORY_URL="https://upload.pypi.org/legacy/" + - TWINE_REPOSITORY_URL=$TWINE_REPOSITORY_URL MB_PYTHON_TAG=$MB_PYTHON_TAG DO_GPG=True GPG_KEYID=$GPG_KEYID TWINE_PASSWORD=$TWINE_PASSWORD TWINE_USERNAME=$TWINE_USERNAME GPG_EXECUTABLE=$GPG_EXECUTABLE CURRENT_BRANCH=release DEPLOY_BRANCH=release DO_TAG=True DO_UPLOAD=True ./publish.sh || echo "upload already exists" # do sed twice to handle the case of https clone with and without a read token - | + # Have the server git-tag the release and push the tags + export VERSION=$(python -c "import setup; print(setup.VERSION)") + # do sed twice to handle the case of https clone with and without a read token URL_HOST=$(git remote get-url origin | sed -e 's|https\?://.*@||g' | sed -e 's|https\?://||g' | sed -e 's|git@||g' | sed -e 's|:|/|g') echo "URL_HOST = $URL_HOST" # A git config user name and email is required. Set if needed. @@ -179,17 +183,24 @@ stages: git config user.email "ci@gitlab.org.com" git config user.name "Gitlab-CI" fi - if [ $(git tag -l "$VERSION") ]; then + TAG_NAME="v${VERSION}" + echo "TAG_NAME = $TAG_NAME" + if [ $(git tag -l "$TAG_NAME") ]; then echo "Tag already exists" else - git tag $VERSION -m "tarball tag $VERSION" - git push --tags "https://${GITLAB_ORG_PUSH_TOKEN}@${URL_HOST}" + # if we messed up we can delete the tag + # git push origin :refs/tags/$TAG_NAME + # and then tag with -f + git tag $TAG_NAME -m "tarball tag $VERSION" + git push --tags "https://git-push-token:${PUSH_TOKEN}@${URL_HOST}" fi + only: refs: - release + .build_install_test: &build_install_test - pip install -r requirements.txt -U diff --git a/dev/secrets_configuration.sh b/dev/secrets_configuration.sh index 5947366..9110a51 100644 --- a/dev/secrets_configuration.sh +++ b/dev/secrets_configuration.sh @@ -3,4 +3,5 @@ export VARNAME_TWINE_USERNAME="TWINE_USERNAME" export VARNAME_TWINE_PASSWORD="TWINE_PASSWORD" export VARNAME_TEST_TWINE_USERNAME="TEST_TWINE_USERNAME" export VARNAME_TEST_TWINE_PASSWORD="TEST_TWINE_PASSWORD" +export VARNAME_PUSH_TOKEN="GITLAB_KITWARE_TOKEN" export GPG_IDENTIFIER="=Erotemic-CI " diff --git a/dev/setup_secrets.sh b/dev/setup_secrets.sh index b45c506..56c3472 100644 --- a/dev/setup_secrets.sh +++ b/dev/setup_secrets.sh @@ -3,23 +3,12 @@ __doc__=' SETUP CI SECRET INSTRUCTIONS ============================ -TODO: These instructions are currently pieced together from old disparate -instances, and are not yet fully organized. - The original template file should be: ~/misc/templates/PYPKG/dev/setup_secrets.sh Development script for updating secrets when they rotate -The intent of this script is to help setup secrets for whichever of the -following CI platforms is used: - -../.github/workflows/tests.yml -../.gitlab-ci.yml -../.circleci/config.yml - - ========================= GITHUB ACTION INSTRUCTIONS ========================= @@ -96,15 +85,6 @@ GITLAB ACTION INSTRUCTIONS # and masked option. Also make sure you have master and release # branches protected. # https://gitlab.kitware.com/computer-vision/kwcoco/-/settings/repository#js-protected-branches-settings - - -============================ -Relevant CI Secret Locations -============================ - -https://github.com/pyutils/line_profiler/settings/secrets/actions - -https://app.circleci.com/settings/project/github/pyutils/line_profiler/environment-variables?return-to=https%3A%2F%2Fapp.circleci.com%2Fpipelines%2Fgithub%2Fpyutils%2Fline_profiler ' setup_package_environs(){ @@ -121,6 +101,7 @@ setup_package_environs(){ export VARNAME_TWINE_PASSWORD="TWINE_PASSWORD" export VARNAME_TEST_TWINE_USERNAME="TEST_TWINE_USERNAME" export VARNAME_TEST_TWINE_PASSWORD="TEST_TWINE_PASSWORD" + export VARNAME_PUSH_TOKEN="GITLAB_KITWARE_TOKEN" export GPG_IDENTIFIER="=Erotemic-CI " ' | python -c "import sys; from textwrap import dedent; print(dedent(sys.stdin.read()).strip(chr(10)))" > dev/secrets_configuration.sh git add dev/secrets_configuration.sh @@ -149,104 +130,20 @@ setup_package_environs(){ #' | python -c "import sys; from textwrap import dedent; print(dedent(sys.stdin.read()).strip(chr(10)))" > dev/secrets_configuration.sh } -upload_github_secrets(){ - load_secrets - unset GITHUB_TOKEN - gh auth login - source dev/secrets_configuration.sh - gh secret set $VARNAME_CI_SECRET -b"${!VARNAME_CI_SECRET}" - gh secret set $VARNAME_TWINE_USERNAME -b"${!VARNAME_TWINE_USERNAME}" - gh secret set $VARNAME_TWINE_PASSWORD -b"${!VARNAME_TWINE_PASSWORD}" - gh secret set $VARNAME_TEST_TWINE_PASSWORD -b"${!VARNAME_TEST_TWINE_PASSWORD}" - gh secret set $VARNAME_TEST_TWINE_USERNAME -b"${!VARNAME_TEST_TWINE_USERNAME}" - -} - - -upload_gitlab_secrets(){ - __doc__=" - Use the gitlab API to modify group-level secrets - " - # In Repo Directory - load_secrets - REMOTE=origin - MERGE_BRANCH=$(git branch --show-current) - echo "MERGE_BRANCH = $MERGE_BRANCH" - GROUP_NAME=$(git remote get-url $REMOTE | cut -d ":" -f 2 | cut -d "/" -f 1) - echo "GROUP_NAME = $GROUP_NAME" - HOST=https://$(git remote get-url $REMOTE | cut -d "/" -f 1 | cut -d "@" -f 2 | cut -d ":" -f 1) - echo "HOST = $HOST" - PRIVATE_GITLAB_TOKEN=$(git_token_for $HOST) - if [[ "$PRIVATE_GITLAB_TOKEN" == "ERROR" ]]; then - echo "Failed to load authentication key" - return 1 - fi - - TMP_DIR=$(mktemp -d -t ci-XXXXXXXXXX) - curl --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups" > $TMP_DIR/all_group_info - GROUP_ID=$(cat $TMP_DIR/all_group_info | jq ". | map(select(.name==\"$GROUP_NAME\")) | .[0].id") - echo "GROUP_ID = $GROUP_ID" - - curl --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID" > $TMP_DIR/group_info - cat $TMP_DIR/group_info | jq - - # Get group-level secret variables - curl --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID/variables" > $TMP_DIR/group_vars - cat $TMP_DIR/group_vars | jq '.[] | .key' - - source dev/secrets_configuration.sh - SECRET_VARNAME_ARR=(VARNAME_CI_SECRET VARNAME_TWINE_USERNAME VARNAME_TWINE_PASSWORD VARNAME_TEST_TWINE_PASSWORD VARNAME_TEST_TWINE_USERNAME) - for SECRET_VARNAME_PTR in "${SECRET_VARNAME_ARR[@]}"; do - SECRET_VARNAME=${!SECRET_VARNAME_PTR} - echo "" - echo " ---- " - LOCAL_VALUE=${!SECRET_VARNAME} - REMOTE_VALUE=$(cat $TMP_DIR/group_vars | jq -r ".[] | select(.key==\"$SECRET_VARNAME\") | .value") - - # Print current local and remote value of a variable - echo "SECRET_VARNAME_PTR = $SECRET_VARNAME_PTR" - echo "SECRET_VARNAME = $SECRET_VARNAME" - echo "(local) $SECRET_VARNAME = $LOCAL_VALUE" - echo "(remote) $SECRET_VARNAME = $REMOTE_VALUE" - - #curl --request GET --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID/variables/SECRET_VARNAME" | jq -r .message - if [[ "$REMOTE_VALUE" == "" ]]; then - # New variable - echo "Remove variable does not exist, posting" - curl --request POST --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID/variables" \ - --form "key=${SECRET_VARNAME}" \ - --form "value=${LOCAL_VALUE}" \ - --form "protected=true" \ - --form "masked=true" \ - --form "environment_scope=*" \ - --form "variable_type=env_var" - elif [[ "$REMOTE_VALUE" != "$LOCAL_VALUE" ]]; then - echo "Remove variable does not agree, putting" - # Update variable value - curl --request PUT --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID/variables/$SECRET_VARNAME" \ - --form "value=${LOCAL_VALUE}" - else - echo "Remote value agrees with local" - fi - done - rm $TMP_DIR/group_vars -} export_encrypted_code_signing_keys(){ - # You will need to rerun this whenever the signkeys expire and are renewed + setup_package_environs + cd $REPO_DPATH # Load or generate secrets load_secrets - source dev/secrets_configuration.sh - CI_SECRET="${!VARNAME_CI_SECRET}" - echo "VARNAME_CI_SECRET = $VARNAME_CI_SECRET" - echo "CI_SECRET=$CI_SECRET" - echo "GPG_IDENTIFIER=$GPG_IDENTIFIER" + echo "CI_SECRET = $CI_SECRET" # ADD RELEVANT VARIABLES TO THE CI SECRET VARIABLES + # HOW TO ENCRYPT YOUR SECRET GPG KEY # You need to have a known public gpg key for this to make any sense @@ -271,7 +168,7 @@ export_encrypted_code_signing_keys(){ # Test decrpyt GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | gpg --list-packets --verbose GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | gpg --list-packets --verbose - GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc | gpg --list-packets --verbose cat dev/public_gpg_key unload_secrets @@ -292,8 +189,6 @@ _test_gnu(){ ls -al $GNUPGHOME chmod 700 -R $GNUPGHOME - source dev/secrets_configuration.sh - gpg -k load_secrets @@ -302,14 +197,11 @@ _test_gnu(){ cat dev/public_gpg_key GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc - GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | gpg --import - GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc | gpg --import-ownertrust GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | gpg --import gpg -k - # | gpg --import - # | gpg --list-packets --verbose } + -- GitLab From 65c83105406bcdbe83c5fb3cfb5bfb383fa07038 Mon Sep 17 00:00:00 2001 From: joncrall Date: Sat, 19 Mar 2022 10:46:45 -0400 Subject: [PATCH 2/4] Start branch for 0.5.19 --- CHANGELOG.md | 5 ++++- netharn/__init__.py | 2 +- super_setup.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24d55eb..b89eed3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,10 @@ This changelog follows the specifications detailed in: [Keep a Changelog](https: This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), although we have not yet reached a `1.0.0` release. -## Version 0.5.18 - Unreleased +## Version 0.5.19 - Unreleased + + +## Version 0.5.18 - Released 2022-03-19 ## Version 0.5.17 - Released 2021-10-05 diff --git a/netharn/__init__.py b/netharn/__init__.py index cc2d4c1..492ddee 100644 --- a/netharn/__init__.py +++ b/netharn/__init__.py @@ -4,7 +4,7 @@ mkinit netharn --noattrs --dry mkinit netharn --noattrs """ -__version__ = '0.5.18' +__version__ = '0.5.19' try: # PIL 7.0.0 removed PIL_VERSION, which breaks torchvision, monkey patch it diff --git a/super_setup.py b/super_setup.py index 51b6b8b..43a5a22 100755 --- a/super_setup.py +++ b/super_setup.py @@ -889,7 +889,7 @@ DEVEL_REPOS = [ # netharn - training harness { - 'name': 'netharn', 'branch': 'dev/0.5.18', 'remote': 'origin', + 'name': 'netharn', 'branch': 'dev/0.5.19', 'remote': 'origin', 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/netharn.git'}, }, ] -- GitLab From 97ff51ff78401d525c51d49a66855f939244c0a1 Mon Sep 17 00:00:00 2001 From: joncrall Date: Fri, 1 Apr 2022 17:17:46 -0400 Subject: [PATCH 3/4] wip --- super_setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/super_setup.py b/super_setup.py index 43a5a22..472f9a9 100755 --- a/super_setup.py +++ b/super_setup.py @@ -850,7 +850,7 @@ DEVEL_REPOS = [ 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/kwarray.git'}, }, { - 'name': 'kwimage', 'branch': 'dev/0.8.3', 'remote': 'origin', + 'name': 'kwimage', 'branch': 'dev/0.8.4', 'remote': 'origin', 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/kwimage.git'}, }, # TODO: @@ -859,7 +859,7 @@ DEVEL_REPOS = [ # 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/kwannot.git'}, # }, { - 'name': 'kwcoco', 'branch': 'dev/0.2.26', 'remote': 'origin', + 'name': 'kwcoco', 'branch': 'dev/0.2.28', 'remote': 'origin', 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/kwcoco.git'}, }, { @@ -883,7 +883,7 @@ DEVEL_REPOS = [ 'remotes': {'origin': 'git@gitlab.kitware.com:utils/scriptconfig.git'}, }, { - 'name': 'ndsampler', 'branch': 'dev/0.6.7', 'remote': 'origin', + 'name': 'ndsampler', 'branch': 'dev/0.6.8', 'remote': 'origin', 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/ndsampler.git'}, }, -- GitLab From a2ec8382d48ff7d2a3d303a9f76c869c1eab33f2 Mon Sep 17 00:00:00 2001 From: joncrall Date: Tue, 12 Apr 2022 21:04:07 -0400 Subject: [PATCH 4/4] wip --- super_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/super_setup.py b/super_setup.py index 472f9a9..43a702c 100755 --- a/super_setup.py +++ b/super_setup.py @@ -850,7 +850,7 @@ DEVEL_REPOS = [ 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/kwarray.git'}, }, { - 'name': 'kwimage', 'branch': 'dev/0.8.4', 'remote': 'origin', + 'name': 'kwimage', 'branch': 'dev/0.8.5', 'remote': 'origin', 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/kwimage.git'}, }, # TODO: @@ -859,7 +859,7 @@ DEVEL_REPOS = [ # 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/kwannot.git'}, # }, { - 'name': 'kwcoco', 'branch': 'dev/0.2.28', 'remote': 'origin', + 'name': 'kwcoco', 'branch': 'dev/0.2.30', 'remote': 'origin', 'remotes': {'origin': 'git@gitlab.kitware.com:computer-vision/kwcoco.git'}, }, { -- GitLab