Compare commits

..

8 Commits

Author SHA1 Message Date
dependabot[bot] 571877c327 Bump github.com/spf13/cobra from 1.8.0 to 1.8.1
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-17 20:19:22 +00:00
Shawn Hartsell 9141ea72f2 Merge pull request #126 from actions/dependabot/github_actions/ruby/setup-ruby-1.174.0
Bump ruby/setup-ruby from 1.173.0 to 1.174.0
2024-04-25 10:33:50 -05:00
Shawn Hartsell 6df9de5468 Merge pull request #125 from actions/dependabot/github_actions/actions/stale-9
Bump actions/stale from 5 to 9
2024-04-25 10:33:24 -05:00
Shawn Hartsell 206dd07aca Merge pull request #127 from actions/sh/cgo_0
goreleaser / goreleaser (push) Has been cancelled
Explicitly set CGO_ENABLED=0
2024-04-23 14:19:39 -05:00
Shawn Hartsell dc02275ad7 fixing typo 2024-04-23 11:21:44 -05:00
Shawn Hartsell e6d6bca5cc simplifying goreleaser config. explicitly set CGO_ENABLED=0 2024-04-23 11:05:23 -05:00
dependabot[bot] 19a0a6e383 Bump ruby/setup-ruby from 1.173.0 to 1.174.0
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.173.0 to 1.174.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](https://github.com/ruby/setup-ruby/compare/5f19ec79cedfadb78ab837f95b87734d0003c899...6bd3d993c602f6b675728ebaecb2b569ff86e99b)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 20:22:05 +00:00
dependabot[bot] fe7ea535a8 Bump actions/stale from 5 to 9
Bumps [actions/stale](https://github.com/actions/stale) from 5 to 9.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v5...v9)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 20:22:03 +00:00
26 changed files with 210 additions and 659 deletions
@@ -8,13 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
runson: [
ubuntu-latest,
ubuntu-20.04,
macos-latest,
windows-latest,
windows-2019
]
runson: [ubuntu-latest, macos-latest, windows-latest]
uses: ./.github/workflows/actions-sync-e2e-test-called.yml
with:
runson: ${{ matrix.runson }}
+9 -59
View File
@@ -3,64 +3,14 @@ on:
pull_request:
workflow_dispatch:
jobs:
lint:
CI:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.21
- name: Lint
uses: golangci/golangci-lint-action@v4
with:
version: v1.54.2
unit-test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.21
- name: Unit Tests
run: ./script/test
e2e-test:
needs: unit-test
runs-on: ubuntu-20.04
strategy:
matrix:
platform: [linux]
arch: [amd64, arm64]
exclude:
# Exclude Windows AMD64 because it's not supported
- platform: windows
arch: amd64
steps:
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.21
- name: Checkout
uses: actions/checkout@v4
- name: Build Binary for ${{ matrix.platform }}-${{ matrix.arch }}
run: |
if [[ "${{ matrix.platform }}" == "linux" ]]; then
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
fi
GOOS=${{ matrix.platform }} GOARCH=${{ matrix.arch }} go build -o bin/actions-sync-${{ matrix.platform }}-${{ matrix.arch }} main.go
- name: Run End-to-End Tests for ${{ matrix.platform }}-${{ matrix.arch }}
run: ./script/test-ci bin/actions-sync-${{ matrix.platform }}-${{ matrix.arch }}
- name: Checkout
uses: actions/checkout@v4
- name: Lint
run: docker-compose run --rm lint
- name: Test
run: docker-compose run --rm test
- name: E2E
run: docker-compose run --rm test-build
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
- uses: actions/stale@v9
with:
days-before-issue-stale: 30
days-before-issue-close: 14
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
- run: go mod vendor
# Ruby is required for licensed
- uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899
- uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b
with:
ruby-version: "3.2"
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Set CURRENT_TAG
run: echo "GORELEASER_CURRENT_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
uses: goreleaser/goreleaser-action@f09f1a7bee7ae6b5153daf4e8c63cc8ce8c7e7d5
with:
version: latest
args: release --clean
+4 -2
View File
@@ -15,8 +15,10 @@ linters:
- govet
- unparam
run:
tests: true
modules-download-mode: vendor
tests: true
skip-files:
- ".*_test\\.go$"
modules-download-mode: vendor
issues:
exclude-rules:
- path: test/github.go
+3 -1
View File
@@ -1,4 +1,5 @@
project_name: actions/actions-sync
builds:
- id: build
goos:
@@ -19,6 +20,7 @@ release:
owner: actions
name: actions-sync
# Create the release as a draft so it can be tested before being published
# To test, go to the Actions tab and run the "Actions Sync E2E Sanity Test" workflow
# To test, go to the Actions tab and run the "Actions Sync E2E Sanity Test" workflow
draft: true
+23 -23
View File
@@ -1,23 +1,23 @@
# version: "3.5"
# services:
# lint:
# image: lint
# build:
# context: .
# dockerfile: Dockerfile.cibuild
# entrypoint:
# - script/lint
# test:
# image: test
# build:
# context: .
# dockerfile: Dockerfile.cibuild
# entrypoint:
# - script/test
# test-build:
# image: test-build
# build:
# context: .
# dockerfile: Dockerfile.cibuild
# entrypoint:
# - script/test-build
version: "3.5"
services:
lint:
image: lint
build:
context: .
dockerfile: Dockerfile.cibuild
entrypoint:
- script/lint
test:
image: test
build:
context: .
dockerfile: Dockerfile.cibuild
entrypoint:
- script/test
test-build:
image: test-build
build:
context: .
dockerfile: Dockerfile.cibuild
entrypoint:
- script/test-build
+1 -1
View File
@@ -7,7 +7,7 @@ require (
github.com/google/go-github/v43 v43.0.0
github.com/gorilla/mux v1.8.1
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.8.0
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
golang.org/x/oauth2 v0.19.0
)
+3 -3
View File
@@ -13,7 +13,7 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -73,8 +73,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+1 -1
View File
@@ -4,7 +4,7 @@ if($IsLinux) {
} elseif ($IsWindows) {
$file_postfix = "windows_amd64"
} elseif ($IsMacOS) {
$file_postfix = "darwin_arm64"
$file_postfix = "darwin_amd64"
}
# Download release to test
+1 -1
View File
@@ -7,4 +7,4 @@ test -z "${DEBUG:-}" || {
set -x
}
go build -o bin/actions-sync main.go
CGO_ENABLED=0 go build -o bin/actions-sync main.go
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#script/build
script/build
OUTPUT=$(mktemp)
RESULT=-1
-426
View File
@@ -1,426 +0,0 @@
#!/usr/bin/env bash
#script/build
# if the first argument is missing then print usage and exit
if [ -z "$1" ]; then
echo "Usage: $1 <path to actions-sync binary>"
exit 1
fi
ACTIONS_SYNC_BINARY=$1
OUTPUT=$(mktemp)
RESULT=-1
SRC_GIT_DAEMON_PORT=9419
SRC_GIT_DAEMON_OUTPUT=$(mktemp)
SRC_GIT_DAEMON_PID=-1
DEST_GIT_DAEMON_PORT=9420
DEST_GIT_DAEMON_OUTPUT=$(mktemp)
DEST_GIT_DAEMON_PID=-1
DEST_API_PORT=8081
DEST_API_OUTPUT=$(mktemp)
DEST_API_PID=-1
trap "after_suite" EXIT
trap "after_suite" SIGINT
function test_version() {
version "version shouldn't require any flags"
echo "all version tests passed"
}
function test_pull() {
# Pull new repo
setup_src "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_cache
pull --repo-name "org/repo" "pulling new repo"
assert_cache_sha "org/repo" "heads/main" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "pulling new repo org/repo"
echo "all pull tests passed successfully"
}
function test_push() {
# Push with a new change to main
setup_cache "org/repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push "pushing new commit to main"
assert_dest_sha "org/repo" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org/repo:heads/main to new commit"
# Push a non-linear change
setup_cache "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_dest "org/repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
push "pushing to an old commit to main"
assert_dest_sha "org/repo" "heads/main" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "updating heads/main to an old commit"
# Push with no changes to main
setup_cache "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_dest "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push "pushing no changes to main"
assert_dest_sha "org/repo" "heads/main" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "leaving org/repo:heads/main at existing commit"
# Push multiple branches
setup_cache "org/repo:heads/change:e9009d51dd6da2c363d1d14779c53dd27fcb0c52" \
"org/repo:heads/nochange:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_dest "org/repo:heads/change:a5984bb887dd2fcdc2892cd906d6f004844d1142" \
"org/repo:heads/nochange:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push "pushing multiple branches"
assert_dest_sha "org/repo" "heads/change" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org/repo:heads/change to new commit"
assert_dest_sha "org/repo" "heads/nochange" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "leaving org/repo:heads/nochange at existing commit"
# Pushing multiple branches and tags
setup_cache "org/repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52" \
"org/repo:tags/v1:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142" \
"org/repo:tags/v1:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push "pushing multiple branches and tags"
assert_dest_sha "org/repo" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org/repo:heads/main to new commit"
assert_dest_sha "org/repo" "tags/v1" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org/repo:tags/v1 to new commit"
# Pushing multiple repositories
setup_cache "org1/repo1:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52" \
"org1/repo2:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52" \
"org1/repo2:tags/v1:e9009d51dd6da2c363d1d14779c53dd27fcb0c52" \
"org2/repo1:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org1/repo1:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142" \
"org1/repo2:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142" \
"org1/repo2:tags/v1:a5984bb887dd2fcdc2892cd906d6f004844d1142" \
"org2/repo1:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push "pushing multiple repositories"
assert_dest_sha "org1/repo1" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org1/repo1:heads/main to new commit"
assert_dest_sha "org1/repo2" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org1/repo2:heads/main to new commit"
assert_dest_sha "org1/repo2" "tags/v1" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org1/repo2:tags/v1 to new commit"
assert_dest_sha "org2/repo1" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org2/repo1:tags/v1 to new commit"
# Honor --repo-name flag, ignore other cache entries
setup_cache "org/repo1:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52" \
"org/repo2:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org/repo1:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142" \
"org/repo2:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push2args --repo-name "org/repo1" "pushing only one of the repos in the cache"
assert_dest_sha "org/repo1" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org/repo1 passed in repo flag"
assert_dest_sha "org/repo2" "heads/main" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "org/repo2 not updated despite cache"
# Push to pre-existing org
setup_cache "org-already-exists/new-repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org-already-exists/new-repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push "pushing to existing org"
assert_dest_sha "org-already-exists/new-repo" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org-already-exists/new-repo"
# Push to pre-existing repo
setup_cache "org-already-exists/repo-already-exists:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org-already-exists/repo-already-exists:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push "pushing to existing repo"
assert_dest_sha "org-already-exists/repo-already-exists" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org-already-exists/repo-already-exists"
# Push to repo in user's account
setup_cache "monalisa/new-repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "monalisa/new-repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push "pushing to authenticated user's account"
assert_dest_sha "monalisa/new-repo" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating monalisa/new-repo"
# Push to GHAE with impersonation
setup_cache "org-already-exists/ghae-repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org-already-exists/ghae-repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push_impersonation "ghae-admin" "pushing to GHAE repo"
# Push to GHES with impersonation
setup_cache "org-already-exists/ghes-repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org-already-exists/ghes-repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
push_impersonation "ghes-admin" "pushing to GHES repo"
echo "all push tests passed successfully"
}
function test_sync() {
# Sync no change without cached
setup_src "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_cache
setup_dest "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
sync --repo-name "org/repo" "syncing a new commit in an uncached repo"
assert_dest_sha "org/repo" "heads/main" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "syncing no changes without cache"
# Sync a new commit without cache
setup_src "org/repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_cache
setup_dest "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
sync --repo-name "org/repo" "syncing a new commit in an uncached repo"
assert_dest_sha "org/repo" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "syncing a new commit without cache"
# Sync no change with uncached
setup_src "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_cache "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_dest "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
sync --repo-name "org/repo" "syncing a new commit in an uncached repo"
assert_dest_sha "org/repo" "heads/main" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "syncing no changes with cache"
# Sync a new commit with cache
setup_src "org/repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_cache "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_dest "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
sync --repo-name "org/repo" "syncing a new commit in an uncached repo"
assert_dest_sha "org/repo" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "syncing a new commit with cache"
# Sync a non-linear change without cache
setup_src "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_cache
setup_dest "org/repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
sync --repo-name "org/repo" "syncing an old commit in an uncached repo"
assert_dest_sha "org/repo" "heads/main" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "syncing heads/main to an old commit without cache"
# Sync a non-linear change with cache
setup_src "org/repo:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
setup_cache "org/repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org/repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
sync --repo-name "org/repo" "syncing an old commit in an cached repo"
assert_dest_sha "org/repo" "heads/main" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "syncing heads/main to an old commit with cache"
# Sync to a different repo
setup_src "org/repo:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_cache
setup_dest "org2/repo2:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
sync --repo-name "org/repo:org2/repo2" "syncing org/repo aliased to org2/repo2"
assert_dest_sha "org2/repo2" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "syncing org/repo aliased to org2/repo2"
# Honor --repo-name flag, ignore other cache entries
setup_cache "org/repo1:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52" \
"org/repo2:heads/main:e9009d51dd6da2c363d1d14779c53dd27fcb0c52"
setup_dest "org/repo1:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142" \
"org/repo2:heads/main:a5984bb887dd2fcdc2892cd906d6f004844d1142"
sync --repo-name "org/repo1" "syncing only one of the repos in the cache"
assert_dest_sha "org/repo1" "heads/main" "e9009d51dd6da2c363d1d14779c53dd27fcb0c52" "updating org/repo1 passed in repo flag"
assert_dest_sha "org/repo2" "heads/main" "a5984bb887dd2fcdc2892cd906d6f004844d1142" "org/repo2 not updated despite cache"
echo "all sync tests passed successfully"
}
function before_suite() {
set -e
$(
rm -rf test/tmp
mkdir -p test/tmp/dest
mkdir -p test/tmp/src
) >/dev/null
git daemon \
--reuseaddr \
--verbose \
--export-all \
--enable=receive-pack \
--port="$SRC_GIT_DAEMON_PORT" \
--base-path=test/tmp/src &>$SRC_GIT_DAEMON_OUTPUT &
SRC_GIT_DAEMON_PID=$!
git daemon \
--reuseaddr \
--verbose \
--export-all \
--enable=receive-pack \
--port="$DEST_GIT_DAEMON_PORT" \
--base-path=test/tmp/dest &>$DEST_GIT_DAEMON_OUTPUT &
DEST_GIT_DAEMON_PID=$!
# wait for the git daemons to start before continuing
if ! check_git_daemon $SRC_GIT_DAEMON_PORT; then
echo "timeout waiting for src git daemon to start"
exit 1
fi
if ! check_git_daemon $DEST_GIT_DAEMON_PORT; then
echo "timeout waiting for dest git daemon to start"
exit 1
fi
go run test/github.go \
-p "$DEST_API_PORT" \
-git-daemon-url "git://localhost:$DEST_GIT_DAEMON_PORT/" &>$DEST_API_OUTPUT &
DEST_API_PID=$!
script/build
until $(curl --output /dev/null --silent --head --fail "http://localhost:$DEST_API_PORT/ping"); do
sleep 0.1
done
set +e
}
function after_suite() {
kill "$SRC_GIT_DAEMON_PID" "$DEST_GIT_DAEMON_PID" "$DEST_API_PID"
}
function setup_src() {
rm -rf test/tmp/src
mkdir -p test/tmp/src
for r in "$@"; do
local nwo=$(echo $r | cut -d':' -f1)
local refname=$(echo $r | cut -d':' -f2)
local sha=$(echo $r | cut -d':' -f3)
local org=$(echo $nwo | cut -d'/' -f1)
mkdir -p test/tmp/src/$org && cp -R test/fixtures/repo.git test/tmp/src/$nwo
echo "$sha" >"test/tmp/src/$nwo/refs/$refname"
done
}
function setup_cache() {
rm -rf test/tmp/cache
mkdir -p test/tmp/cache
for r in "$@"; do
local nwo=$(echo $r | cut -d':' -f1)
local refname=$(echo $r | cut -d':' -f2)
local sha=$(echo $r | cut -d':' -f3)
mkdir -p test/tmp/cache/$nwo && cp -R test/fixtures/repo.git test/tmp/cache/$nwo/.git
echo "$sha" >"test/tmp/cache/$nwo/.git/refs/$refname"
done
}
function setup_dest() {
rm -rf test/tmp/dest
mkdir -p test/tmp/dest
for r in "$@"; do
local nwo=$(echo $r | cut -d':' -f1)
local refname=$(echo $r | cut -d':' -f2)
local sha=$(echo $r | cut -d':' -f3)
mkdir -p test/tmp/dest/$nwo && cp -R test/fixtures/repo.git test/tmp/dest/$nwo/.git
echo "$sha" >"test/tmp/dest/$nwo/.git/refs/$refname"
done
}
function version() {
$ACTIONS_SYNC_BINARY version \
&>$OUTPUT ||
fail $1
}
function pull() {
$ACTIONS_SYNC_BINARY pull \
--cache-dir "test/tmp/cache" \
--source-url "git://localhost:$SRC_GIT_DAEMON_PORT" \
"$1" "$2" \
&>$OUTPUT ||
fail $3
}
function push() {
$ACTIONS_SYNC_BINARY push \
--cache-dir "test/tmp/cache" \
--disable-push-git-auth \
--destination-token "token" \
--destination-url "http://localhost:$DEST_API_PORT" \
&>$OUTPUT ||
fail "$1"
}
function push2args() {
$ACTIONS_SYNC_BINARY push \
--cache-dir "test/tmp/cache" \
--disable-push-git-auth \
--destination-token "token" \
--destination-url "http://localhost:$DEST_API_PORT" \
"$1" "$2" \
&>$OUTPUT ||
fail $3
}
function push_impersonation() {
$ACTIONS_SYNC_BINARY push \
--cache-dir "test/tmp/cache" \
--disable-push-git-auth \
--destination-token "token" \
--destination-url "http://localhost:$DEST_API_PORT" \
--actions-admin-user $1 \
&>$OUTPUT ||
fail "$2"
}
function sync() {
$ACTIONS_SYNC_BINARY sync \
--cache-dir "test/tmp/cache" \
--source-url "git://localhost:$SRC_GIT_DAEMON_PORT" \
--disable-push-git-auth \
--destination-token "token" \
--destination-url "http://localhost:$DEST_API_PORT" \
"$1" "$2" \
&>$OUTPUT ||
fail $3
}
function assert_cache_sha() {
nwo=$1
ref=$2
expected=$3
actual=$(cat "test/tmp/cache/$nwo/.git/refs/$ref")
[ "$actual" == "$expected" ] || fail "unexpected cache sha \`$expected != $actual\` - \`$nwo\` \`$ref\` - \`$4\`"
}
function assert_dest_sha() {
nwo=$1
ref=$2
expected=$3
actual=$(cat "test/tmp/dest/$nwo/.git/refs/$ref")
[ "$actual" == "$expected" ] || fail "unexpected dest sha \`$expected != $actual\` - \`$nwo\` \`$ref\` - \`$4\`"
}
function fail() {
MSG=$1
echo "FAIL: Failed $MSG"
echo -----output-----
cat $OUTPUT
echo -----/output-----
echo -----github git daemon output-----
cat $SRC_GIT_DAEMON_OUTPUT
echo -----/github git daemon output-----
echo -----ghes git daemon output-----
cat $DEST_GIT_DAEMON_OUTPUT
echo -----/ghes git daemon output-----
echo -----ghes api output-----
cat $DEST_API_OUTPUT
echo -----/ghes api output-----
exit 1
}
function check_git_daemon {
port=$1
for i in {1..10}; do
process=$(ps aux | grep "git daemon" | grep $port)
if [ -z "$process" ]; then
echo "Waiting for git daemon to start on port $port..."
sleep 1
else
echo "Git daemon is running on port $port"
return 0
fi
done
echo "Git daemon did not start within the expected time."
return 1
}
test_version
before_suite
test_pull
test_push
test_sync
+29 -32
View File
@@ -71,11 +71,11 @@ func GetImpersonationToken(ctx context.Context, flags *PushFlags) (string, error
rootRequest, err := ghClient.NewRequest("GET", enterpriseAPIPath, nil)
if err != nil {
return "", errors.Wrap(err, "error constructing request for GitHub Enterprise client")
return "", errors.Wrap(err, "error constructing request for GitHub Enterprise client.")
}
rootResponse, err := ghClient.Do(ctx, rootRequest, nil)
if err != nil {
return "", errors.Wrap(err, "error checking connectivity for GitHub Enterprise client")
return "", errors.Wrap(err, "error checking connectivity for GitHub Enterprise client.")
}
scopesHeader := rootResponse.Header.Get(xOAuthScopesHeader)
@@ -96,7 +96,7 @@ func GetImpersonationToken(ctx context.Context, flags *PushFlags) (string, error
impersonationToken, _, err := ghClient.Admin.CreateUserImpersonation(ctx, flags.ActionsAdminUser, &github.ImpersonateUserOptions{Scopes: []string{minimumRepositoryScope, "workflow"}})
if err != nil {
return "", errors.Wrap(err, "failed to impersonate Actions admin user")
return "", errors.Wrap(err, "failed to impersonate Actions admin user.")
}
fmt.Printf("got the impersonation token for `%s` ...\n", flags.ActionsAdminUser)
@@ -205,41 +205,38 @@ func getOrCreateGitHubRepo(ctx context.Context, client *github.Client, repoName,
}
// check if repository already exists
var ghRepo *github.Repository
var resp *github.Response
ghRepo, resp, err = client.Repositories.Get(ctx, ownerName, repoName)
if err != nil {
ghRepo, resp, err := client.Repositories.Get(ctx, ownerName, repoName)
if err == nil {
fmt.Printf("Existing repo `%s/%s`\n", ownerName, repoName)
} else if resp != nil && resp.StatusCode == 404 {
// repo not existing yet - try to create
if resp != nil && resp.StatusCode == 404 {
fmt.Printf("repo `%s/%s` doesn't exist, attempting to create\n", ownerName, repoName)
visibility := github.String("public")
if isAE {
visibility = github.String("internal")
}
repo := &github.Repository{
Name: github.String(repoName),
HasIssues: github.Bool(false),
HasWiki: github.Bool(false),
HasPages: github.Bool(false),
HasProjects: github.Bool(false),
Visibility: visibility,
}
ghRepo, _, err = client.Repositories.Create(ctx, createRepoOrgName, repo)
if err != nil {
return nil, errors.Wrapf(err, "error creating repository %s/%s", ownerName, repoName)
}
fmt.Printf("created repo `%s/%s`\n", ownerName, repoName)
return ghRepo, nil
visibility := github.String("public")
if isAE {
visibility = github.String("internal")
}
repo := &github.Repository{
Name: github.String(repoName),
HasIssues: github.Bool(false),
HasWiki: github.Bool(false),
HasPages: github.Bool(false),
HasProjects: github.Bool(false),
Visibility: visibility,
}
ghRepo, _, err = client.Repositories.Create(ctx, createRepoOrgName, repo)
if err == nil {
fmt.Printf("Created repo `%s/%s`\n", ownerName, repoName)
} else {
return nil, errors.Wrapf(err, "error creating repository %s/%s", ownerName, repoName)
}
} else if err != nil {
return nil, errors.Wrapf(err, "error creating repository %s/%s", ownerName, repoName)
}
fmt.Printf("found existing repo `%s/%s`\n", ownerName, repoName)
if ghRepo == nil {
return nil, errors.New("error repository is nil")
}
return ghRepo, nil
}
+7 -7
View File
@@ -18,7 +18,7 @@ func Test_extractSourceDest(t *testing.T) {
assert.Equal(t, "src_owner/src_repo", src)
assert.Equal(t, "dst_owner/dst_repo", dst)
_, _, err = extractSourceDest("src_owner/src_repo:dst_owner/dst_repo:bogus/bogus")
src, dst, err = extractSourceDest("src_owner/src_repo:dst_owner/dst_repo:bogus/bogus")
require.Error(t, err)
}
@@ -37,24 +37,24 @@ func Test_validateNwo(t *testing.T) {
assert.Equal(t, "a/b", nwo)
// no slash separator
_, err = validateNwo("bogus")
nwo, err = validateNwo("bogus")
require.Error(t, err)
// no owner
_, err = validateNwo("/bogus")
nwo, err = validateNwo("/bogus")
require.Error(t, err)
// no repo name
_, err = validateNwo("bogus/")
nwo, err = validateNwo("bogus/")
require.Error(t, err)
_, err = validateNwo("bogus whitespace/bogus")
nwo, err = validateNwo("bogus whitespace/bogus")
require.Error(t, err)
_, err = validateNwo("bogus/bogus/bogus")
nwo, err = validateNwo("bogus/bogus/bogus")
require.Error(t, err)
// A separate destination is only permitted for "repo names", not NWOs.
_, err = validateNwo("owner/repo:bogus/bogus")
nwo, err = validateNwo("owner/repo:bogus/bogus")
require.Error(t, err)
}
+8 -13
View File
@@ -26,33 +26,28 @@ linters:
- errcheck
#- exhaustive
#- funlen
- gas
#- gochecknoinits
- goconst
#- gocritic
- gocritic
#- gocyclo
#- gofmt
- gofmt
- goimports
- golint
#- gomnd
#- goprintffuncname
#- gosec
#- gosimple
- gosec
- gosimple
- govet
- ineffassign
- interfacer
#- lll
- maligned
- megacheck
#- misspell
- misspell
#- nakedret
#- noctx
#- nolintlint
- nolintlint
#- rowserrcheck
#- scopelint
#- staticcheck
- staticcheck
#- structcheck ! deprecated since v1.49.0; replaced by 'unused'
#- stylecheck
- stylecheck
#- typecheck
- unconvert
#- unparam
+3 -10
View File
@@ -17,21 +17,17 @@ package cobra
import (
"fmt"
"os"
"regexp"
"strings"
)
const (
activeHelpMarker = "_activeHelp_ "
// The below values should not be changed: programs will be using them explicitly
// in their user documentation, and users will be using them explicitly.
activeHelpEnvVarSuffix = "_ACTIVE_HELP"
activeHelpGlobalEnvVar = "COBRA_ACTIVE_HELP"
activeHelpEnvVarSuffix = "ACTIVE_HELP"
activeHelpGlobalEnvVar = configEnvVarGlobalPrefix + "_" + activeHelpEnvVarSuffix
activeHelpGlobalDisable = "0"
)
var activeHelpEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`)
// AppendActiveHelp adds the specified string to the specified array to be used as ActiveHelp.
// Such strings will be processed by the completion script and will be shown as ActiveHelp
// to the user.
@@ -60,8 +56,5 @@ func GetActiveHelpConfig(cmd *Command) string {
// variable. It has the format <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
func activeHelpEnvVar(name string) string {
// This format should not be changed: users will be using it explicitly.
activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix))
activeHelpEnvVar = activeHelpEnvVarPrefixSubstRegexp.ReplaceAllString(activeHelpEnvVar, "_")
return activeHelpEnvVar
return configEnvVar(name, activeHelpEnvVarSuffix)
}
+2 -2
View File
@@ -52,9 +52,9 @@ func OnlyValidArgs(cmd *Command, args []string) error {
if len(cmd.ValidArgs) > 0 {
// Remove any description that may be included in ValidArgs.
// A description is following a tab character.
var validArgs []string
validArgs := make([]string, 0, len(cmd.ValidArgs))
for _, v := range cmd.ValidArgs {
validArgs = append(validArgs, strings.Split(v, "\t")[0])
validArgs = append(validArgs, strings.SplitN(v, "\t", 2)[0])
}
for _, v := range args {
if !stringInSlice(v, validArgs) {
+10 -13
View File
@@ -597,19 +597,16 @@ func writeRequiredFlag(buf io.StringWriter, cmd *Command) {
if nonCompletableFlag(flag) {
return
}
for key := range flag.Annotations {
switch key {
case BashCompOneRequiredFlag:
format := " must_have_one_flag+=(\"--%s"
if flag.Value.Type() != "bool" {
format += "="
}
format += cbn
WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name))
if _, ok := flag.Annotations[BashCompOneRequiredFlag]; ok {
format := " must_have_one_flag+=(\"--%s"
if flag.Value.Type() != "bool" {
format += "="
}
format += cbn
WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name))
if len(flag.Shorthand) > 0 {
WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand))
}
if len(flag.Shorthand) > 0 {
WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand))
}
}
})
@@ -621,7 +618,7 @@ func writeRequiredNouns(buf io.StringWriter, cmd *Command) {
for _, value := range cmd.ValidArgs {
// Remove any description that may be included following a tab character.
// Descriptions are not supported by bash completion.
value = strings.Split(value, "\t")[0]
value = strings.SplitN(value, "\t", 2)[0]
WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
}
if cmd.ValidArgsFunction != nil {
-2
View File
@@ -193,8 +193,6 @@ func ld(s, t string, ignoreCase bool) int {
d := make([][]int, len(s)+1)
for i := range d {
d[i] = make([]int, len(t)+1)
}
for i := range d {
d[i][0] = i
}
for j := range d[0] {
+31 -20
View File
@@ -154,8 +154,10 @@ type Command struct {
// pflags contains persistent flags.
pflags *flag.FlagSet
// lflags contains local flags.
// This field does not represent internal state, it's used as a cache to optimise LocalFlags function call
lflags *flag.FlagSet
// iflags contains inherited flags.
// This field does not represent internal state, it's used as a cache to optimise InheritedFlags function call
iflags *flag.FlagSet
// parentsPflags is all persistent flags of cmd's parents.
parentsPflags *flag.FlagSet
@@ -706,7 +708,7 @@ Loop:
// This is not a flag or a flag value. Check to see if it matches what we're looking for, and if so,
// return the args, excluding the one at this position.
if s == x {
ret := []string{}
ret := make([]string, 0, len(args)-1)
ret = append(ret, args[:pos]...)
ret = append(ret, args[pos+1:]...)
return ret
@@ -754,14 +756,14 @@ func (c *Command) findSuggestions(arg string) string {
if c.SuggestionsMinimumDistance <= 0 {
c.SuggestionsMinimumDistance = 2
}
suggestionsString := ""
var sb strings.Builder
if suggestions := c.SuggestionsFor(arg); len(suggestions) > 0 {
suggestionsString += "\n\nDid you mean this?\n"
sb.WriteString("\n\nDid you mean this?\n")
for _, s := range suggestions {
suggestionsString += fmt.Sprintf("\t%v\n", s)
_, _ = fmt.Fprintf(&sb, "\t%v\n", s)
}
}
return suggestionsString
return sb.String()
}
func (c *Command) findNext(next string) *Command {
@@ -873,7 +875,7 @@ func (c *Command) ArgsLenAtDash() int {
func (c *Command) execute(a []string) (err error) {
if c == nil {
return fmt.Errorf("Called Execute() on a nil Command")
return fmt.Errorf("called Execute() on a nil Command")
}
if len(c.Deprecated) > 0 {
@@ -1187,10 +1189,11 @@ func (c *Command) InitDefaultHelpFlag() {
c.mergePersistentFlags()
if c.Flags().Lookup("help") == nil {
usage := "help for "
if c.Name() == "" {
name := c.displayName()
if name == "" {
usage += "this command"
} else {
usage += c.Name()
usage += name
}
c.Flags().BoolP("help", "h", false, usage)
_ = c.Flags().SetAnnotation("help", FlagSetByCobraAnnotation, []string{"true"})
@@ -1236,7 +1239,7 @@ func (c *Command) InitDefaultHelpCmd() {
Use: "help [command]",
Short: "Help about any command",
Long: `Help provides help for any command in the application.
Simply type ` + c.Name() + ` help [path to command] for full details.`,
Simply type ` + c.displayName() + ` help [path to command] for full details.`,
ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
var completions []string
cmd, _, e := c.Root().Find(args)
@@ -1427,6 +1430,10 @@ func (c *Command) CommandPath() string {
if c.HasParent() {
return c.Parent().CommandPath() + " " + c.Name()
}
return c.displayName()
}
func (c *Command) displayName() string {
if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok {
return displayName
}
@@ -1436,10 +1443,11 @@ func (c *Command) CommandPath() string {
// UseLine puts out the full usage for a given command (including parents).
func (c *Command) UseLine() string {
var useline string
use := strings.Replace(c.Use, c.Name(), c.displayName(), 1)
if c.HasParent() {
useline = c.parent.CommandPath() + " " + c.Use
useline = c.parent.CommandPath() + " " + use
} else {
useline = c.Use
useline = use
}
if c.DisableFlagsInUseLine {
return useline
@@ -1452,7 +1460,6 @@ func (c *Command) UseLine() string {
// DebugFlags used to determine which flags have been assigned to which commands
// and which persist.
// nolint:goconst
func (c *Command) DebugFlags() {
c.Println("DebugFlags called on", c.Name())
var debugflags func(*Command)
@@ -1642,7 +1649,7 @@ func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) f
// to this command (local and persistent declared here and by all parents).
func (c *Command) Flags() *flag.FlagSet {
if c.flags == nil {
c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
@@ -1653,10 +1660,11 @@ func (c *Command) Flags() *flag.FlagSet {
}
// LocalNonPersistentFlags are flags specific to this command which will NOT persist to subcommands.
// This function does not modify the flags of the current command, it's purpose is to return the current state.
func (c *Command) LocalNonPersistentFlags() *flag.FlagSet {
persistentFlags := c.PersistentFlags()
out := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
out := flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
c.LocalFlags().VisitAll(func(f *flag.Flag) {
if persistentFlags.Lookup(f.Name) == nil {
out.AddFlag(f)
@@ -1666,11 +1674,12 @@ func (c *Command) LocalNonPersistentFlags() *flag.FlagSet {
}
// LocalFlags returns the local FlagSet specifically set in the current command.
// This function does not modify the flags of the current command, it's purpose is to return the current state.
func (c *Command) LocalFlags() *flag.FlagSet {
c.mergePersistentFlags()
if c.lflags == nil {
c.lflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.lflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
@@ -1693,11 +1702,12 @@ func (c *Command) LocalFlags() *flag.FlagSet {
}
// InheritedFlags returns all flags which were inherited from parent commands.
// This function does not modify the flags of the current command, it's purpose is to return the current state.
func (c *Command) InheritedFlags() *flag.FlagSet {
c.mergePersistentFlags()
if c.iflags == nil {
c.iflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.iflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
@@ -1718,6 +1728,7 @@ func (c *Command) InheritedFlags() *flag.FlagSet {
}
// NonInheritedFlags returns all flags which were not inherited from parent commands.
// This function does not modify the flags of the current command, it's purpose is to return the current state.
func (c *Command) NonInheritedFlags() *flag.FlagSet {
return c.LocalFlags()
}
@@ -1725,7 +1736,7 @@ func (c *Command) NonInheritedFlags() *flag.FlagSet {
// PersistentFlags returns the persistent FlagSet specifically set in the current command.
func (c *Command) PersistentFlags() *flag.FlagSet {
if c.pflags == nil {
c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
@@ -1738,9 +1749,9 @@ func (c *Command) PersistentFlags() *flag.FlagSet {
func (c *Command) ResetFlags() {
c.flagErrorBuf = new(bytes.Buffer)
c.flagErrorBuf.Reset()
c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
c.flags.SetOutput(c.flagErrorBuf)
c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
c.pflags.SetOutput(c.flagErrorBuf)
c.lflags = nil
@@ -1857,7 +1868,7 @@ func (c *Command) mergePersistentFlags() {
// If c.parentsPflags == nil, it makes new.
func (c *Command) updateParentsPflags() {
if c.parentsPflags == nil {
c.parentsPflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.parentsPflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError)
c.parentsPflags.SetOutput(c.flagErrorBuf)
c.parentsPflags.SortFlags = false
}
+50 -12
View File
@@ -17,6 +17,8 @@ package cobra
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"sync"
@@ -211,24 +213,29 @@ func (c *Command) initCompleteCmd(args []string) {
// 2- Even without completions, we need to print the directive
}
noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
noDescriptions := cmd.CalledAs() == ShellCompNoDescRequestCmd
if !noDescriptions {
if doDescriptions, err := strconv.ParseBool(getEnvConfig(cmd, configEnvVarSuffixDescriptions)); err == nil {
noDescriptions = !doDescriptions
}
}
noActiveHelp := GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable
out := finalCmd.OutOrStdout()
for _, comp := range completions {
if GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable {
// Remove all activeHelp entries in this case
if strings.HasPrefix(comp, activeHelpMarker) {
continue
}
if noActiveHelp && strings.HasPrefix(comp, activeHelpMarker) {
// Remove all activeHelp entries if it's disabled.
continue
}
if noDescriptions {
// Remove any description that may be included following a tab character.
comp = strings.Split(comp, "\t")[0]
comp = strings.SplitN(comp, "\t", 2)[0]
}
// Make sure we only write the first line to the output.
// This is needed if a description contains a linebreak.
// Otherwise the shell scripts will interpret the other lines as new flags
// and could therefore provide a wrong completion.
comp = strings.Split(comp, "\n")[0]
comp = strings.SplitN(comp, "\n", 2)[0]
// Finally trim the completion. This is especially important to get rid
// of a trailing tab when there are no description following it.
@@ -237,14 +244,14 @@ func (c *Command) initCompleteCmd(args []string) {
// although there is no description).
comp = strings.TrimSpace(comp)
// Print each possible completion to stdout for the completion script to consume.
fmt.Fprintln(finalCmd.OutOrStdout(), comp)
// Print each possible completion to the output for the completion script to consume.
fmt.Fprintln(out, comp)
}
// As the last printout, print the completion directive for the completion script to parse.
// The directive integer must be that last character following a single colon (:).
// The completion script expects :<directive>
fmt.Fprintf(finalCmd.OutOrStdout(), ":%d\n", directive)
fmt.Fprintf(out, ":%d\n", directive)
// Print some helpful info to stderr for the user to understand.
// Output from stderr must be ignored by the completion script.
@@ -291,7 +298,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
}
if err != nil {
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("unable to find a command for arguments: %v", trimmedArgs)
}
finalCmd.ctx = c.ctx
@@ -899,3 +906,34 @@ func CompError(msg string) {
func CompErrorln(msg string) {
CompError(fmt.Sprintf("%s\n", msg))
}
// These values should not be changed: users will be using them explicitly.
const (
configEnvVarGlobalPrefix = "COBRA"
configEnvVarSuffixDescriptions = "COMPLETION_DESCRIPTIONS"
)
var configEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`)
// configEnvVar returns the name of the program-specific configuration environment
// variable. It has the format <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
func configEnvVar(name, suffix string) string {
// This format should not be changed: users will be using it explicitly.
v := strings.ToUpper(fmt.Sprintf("%s_%s", name, suffix))
v = configEnvVarPrefixSubstRegexp.ReplaceAllString(v, "_")
return v
}
// getEnvConfig returns the value of the configuration environment variable
// <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the root command in upper
// case, with all non-ASCII-alphanumeric characters replaced by `_`.
// If the value is empty or not set, the value of the environment variable
// COBRA_<SUFFIX> is returned instead.
func getEnvConfig(cmd *Command, suffix string) string {
v := os.Getenv(configEnvVar(cmd.Root().Name(), suffix))
if v == "" {
v = os.Getenv(configEnvVar(configEnvVarGlobalPrefix, suffix))
}
return v
}
+17 -17
View File
@@ -23,9 +23,9 @@ import (
)
const (
requiredAsGroup = "cobra_annotation_required_if_others_set"
oneRequired = "cobra_annotation_one_required"
mutuallyExclusive = "cobra_annotation_mutually_exclusive"
requiredAsGroupAnnotation = "cobra_annotation_required_if_others_set"
oneRequiredAnnotation = "cobra_annotation_one_required"
mutuallyExclusiveAnnotation = "cobra_annotation_mutually_exclusive"
)
// MarkFlagsRequiredTogether marks the given flags with annotations so that Cobra errors
@@ -37,7 +37,7 @@ func (c *Command) MarkFlagsRequiredTogether(flagNames ...string) {
if f == nil {
panic(fmt.Sprintf("Failed to find flag %q and mark it as being required in a flag group", v))
}
if err := c.Flags().SetAnnotation(v, requiredAsGroup, append(f.Annotations[requiredAsGroup], strings.Join(flagNames, " "))); err != nil {
if err := c.Flags().SetAnnotation(v, requiredAsGroupAnnotation, append(f.Annotations[requiredAsGroupAnnotation], strings.Join(flagNames, " "))); err != nil {
// Only errs if the flag isn't found.
panic(err)
}
@@ -53,7 +53,7 @@ func (c *Command) MarkFlagsOneRequired(flagNames ...string) {
if f == nil {
panic(fmt.Sprintf("Failed to find flag %q and mark it as being in a one-required flag group", v))
}
if err := c.Flags().SetAnnotation(v, oneRequired, append(f.Annotations[oneRequired], strings.Join(flagNames, " "))); err != nil {
if err := c.Flags().SetAnnotation(v, oneRequiredAnnotation, append(f.Annotations[oneRequiredAnnotation], strings.Join(flagNames, " "))); err != nil {
// Only errs if the flag isn't found.
panic(err)
}
@@ -70,7 +70,7 @@ func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) {
panic(fmt.Sprintf("Failed to find flag %q and mark it as being in a mutually exclusive flag group", v))
}
// Each time this is called is a single new entry; this allows it to be a member of multiple groups if needed.
if err := c.Flags().SetAnnotation(v, mutuallyExclusive, append(f.Annotations[mutuallyExclusive], strings.Join(flagNames, " "))); err != nil {
if err := c.Flags().SetAnnotation(v, mutuallyExclusiveAnnotation, append(f.Annotations[mutuallyExclusiveAnnotation], strings.Join(flagNames, " "))); err != nil {
panic(err)
}
}
@@ -91,9 +91,9 @@ func (c *Command) ValidateFlagGroups() error {
oneRequiredGroupStatus := map[string]map[string]bool{}
mutuallyExclusiveGroupStatus := map[string]map[string]bool{}
flags.VisitAll(func(pflag *flag.Flag) {
processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus)
processFlagForGroupAnnotation(flags, pflag, oneRequired, oneRequiredGroupStatus)
processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus)
processFlagForGroupAnnotation(flags, pflag, requiredAsGroupAnnotation, groupStatus)
processFlagForGroupAnnotation(flags, pflag, oneRequiredAnnotation, oneRequiredGroupStatus)
processFlagForGroupAnnotation(flags, pflag, mutuallyExclusiveAnnotation, mutuallyExclusiveGroupStatus)
})
if err := validateRequiredFlagGroups(groupStatus); err != nil {
@@ -130,7 +130,7 @@ func processFlagForGroupAnnotation(flags *flag.FlagSet, pflag *flag.Flag, annota
continue
}
groupStatus[group] = map[string]bool{}
groupStatus[group] = make(map[string]bool, len(flagnames))
for _, name := range flagnames {
groupStatus[group][name] = false
}
@@ -232,9 +232,9 @@ func (c *Command) enforceFlagGroupsForCompletion() {
oneRequiredGroupStatus := map[string]map[string]bool{}
mutuallyExclusiveGroupStatus := map[string]map[string]bool{}
c.Flags().VisitAll(func(pflag *flag.Flag) {
processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus)
processFlagForGroupAnnotation(flags, pflag, oneRequired, oneRequiredGroupStatus)
processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus)
processFlagForGroupAnnotation(flags, pflag, requiredAsGroupAnnotation, groupStatus)
processFlagForGroupAnnotation(flags, pflag, oneRequiredAnnotation, oneRequiredGroupStatus)
processFlagForGroupAnnotation(flags, pflag, mutuallyExclusiveAnnotation, mutuallyExclusiveGroupStatus)
})
// If a flag that is part of a group is present, we make all the other flags
@@ -253,17 +253,17 @@ func (c *Command) enforceFlagGroupsForCompletion() {
// If none of the flags of a one-required group are present, we make all the flags
// of that group required so that the shell completion suggests them automatically
for flagList, flagnameAndStatus := range oneRequiredGroupStatus {
set := 0
isSet := false
for _, isSet := range flagnameAndStatus {
for _, isSet = range flagnameAndStatus {
if isSet {
set++
break
}
}
// None of the flags of the group are set, mark all flags in the group
// as required
if set == 0 {
if !isSet {
for _, fName := range strings.Split(flagList, " ") {
_ = c.MarkFlagRequired(fName)
}
+2 -2
View File
@@ -28,8 +28,8 @@ import (
func genPowerShellComp(buf io.StringWriter, name string, includeDesc bool) {
// Variables should not contain a '-' or ':' character
nameForVar := name
nameForVar = strings.Replace(nameForVar, "-", "_", -1)
nameForVar = strings.Replace(nameForVar, ":", "_", -1)
nameForVar = strings.ReplaceAll(nameForVar, "-", "_")
nameForVar = strings.ReplaceAll(nameForVar, ":", "_")
compCmd := ShellCompRequestCmd
if !includeDesc {
Generated Vendored
+1 -1
View File
@@ -156,7 +156,7 @@ github.com/sergi/go-diff/diffmatchpatch
# github.com/skeema/knownhosts v1.2.2
## explicit; go 1.17
github.com/skeema/knownhosts
# github.com/spf13/cobra v1.8.0
# github.com/spf13/cobra v1.8.1
## explicit; go 1.15
github.com/spf13/cobra
# github.com/spf13/pflag v1.0.5