Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Last active December 3, 2024 17:58
Show Gist options
  • Save x-yuri/14b0a21a616cf3eee90cf00323bd4537 to your computer and use it in GitHub Desktop.
Save x-yuri/14b0a21a616cf3eee90cf00323bd4537 to your computer and use it in GitHub Desktop.

gcloud functions deploy: .gcloudignore

(the beginning)

The algorithm is basically as follows:

for each dir:
  if allowed:
    for each file (of this dir):
      if allowed: add
  • The iteration starts with ..
  • Every subdirectory of . (at any level) is iterated (whether its parent directories are allowed or not).
  • A file or a directory is allowed if all its prefixes are allowed.
  • Prefixes of a/b/c are a, a/b and a/b/c. Prefixes of ./a are . and ./a.

The relevant parts of the source code are in:

  • /google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py (MakeZipFromDir())
  • /google-cloud-sdk/lib/googlecloudsdk/command_lib/util/gcloudignore.py (FileChooser.IsIncluded())
  • /google-cloud-sdk/lib/googlecloudsdk/command_lib/util/glob.py (Glob._MatchesHelper())

The tests below were run in a google/cloud-sdk:502.0.0-alpine container:

$ docker run --rm -itv "$PWD:/app" -w /app google/cloud-sdk:502.0.0-alpine

For *.bats tests you will need:

/app # git clone https://github.com/bats-core/bats-core
/app # git clone https://github.com/bats-core/bats-support
/app # git clone https://github.com/bats-core/bats-assert
/app # apk add patch ncurses

ncurses provides tput which makes the output more human-readable.

For tests that invoke gcloud you will need to authenticate:

/app # gcloud auth login

replace PROJECT and REGION and the following patch:

archive.py.patch:

--- /google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py
+++ /google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py
@@ -84,6 +84,8 @@
         AddToArchive(zip_file, src_dir, file_path, True)
   finally:
     zip_file.close()
+  shutil.copy(dest_zip_file, "/tmp/out.zip")
+  import sys; sys.exit()
 
 
 def AddToArchive(zip_file, src_dir, rel_path, is_file):

Pattern anchoring

gcloud under the hood uses the Glob class (see /google-cloud-sdk/lib/googlecloudsdk/command_lib/util/glob.py).

a.py:

"""

Patterns are anchored to the end of a path:

>>> Glob('b').Matches('a/b')
True
>>> not Glob('a').Matches('a/b')
True

Patterns the first segment of which is * are additionally anchored to a beginning:

>>> Glob('*').Matches('aa')
True
>>> not Glob('*').Matches('aa/bb')
True
>>> Glob('*/bb').Matches('aa/bb')
True
>>> not Glob('*/cc').Matches('aa/bb/cc')
True
>>> Glob('aa/*').Matches('aa/bb')
True
>>> Glob('bb/*').Matches('aa/bb/cc')
True
>>> Glob('*b/cc').Matches('aa/bb/cc')
True

But when such patterns get applied to a path that starts with `?/` (the first
segment is one character long), `*` can match the second segment of the path:

>>> Glob('*').Matches('a/b')
True
>>> not Glob('*').Matches('aa/b')
True
>>> Glob('*').Matches('a/bb')
True
>>> Glob('*/c').Matches('a/b/c')
True
>>> Glob('*/c/d').Matches('a/b/c/d')
True
>>> not Glob('*/d').Matches('a/b/c/d')
True

"""
import sys
sys.path.insert(0, '/venv/lib/python3.11/site-packages')
sys.path.insert(0, '/google-cloud-sdk/lib/third_party')
sys.path.insert(0, '/google-cloud-sdk/lib')
from googlecloudsdk.command_lib.util.glob import Glob
/app # python -m doctest a.py

a.bats:

ml() { for l; do echo "$l"; done; }

setup() {
    load 'bats-support/load'
    load 'bats-assert/load'
    (cd .. && patch -p0 < app/archive.py.patch)
}

teardown() {
    (cd .. && patch -Rp0 < app/archive.py.patch)
    rm -rf /tmp/gcloudignore
}

_run() {
    local layout=$1 gcloudignore=$2
    mkdir /tmp/gcloudignore
    cd /tmp/gcloudignore
    local l
    while IFS= read -r l; do
        if [[ $l == */* ]]; then
            local p
            p=${l%/*}
            mkdir -p "$p"
            touch "$l"
        else
            touch "$l"
        fi
    done < <(echo "$layout")
    touch go.mod
    echo "$gcloudignore" > .gcloudignore
    gcloud functions deploy a \
        --project PROJECT \
        --gen2 \
        --runtime go121 \
        --region REGION \
        --trigger-http
    unzip -l /tmp/out.zip \
        | tail +4 | head -n -2 \
        | awk '{print $4}' \
        | grep -vE '/$' \
        | grep -Fve .gcloudignore -e go.mod \
        || true
}

# setup() {
#     load 'bats-support/load'
#     load 'bats-assert/load'
# }
# 
# teardown() {
#     rm -rf /tmp/gcloudignore
# }
# 
# _run() {
#     local layout=$1 gcloudignore=$2
#     mkdir /tmp/gcloudignore
#     cd /tmp/gcloudignore
#     local l
#     while IFS= read -r l; do
#         if [[ $l == */* ]]; then
#             local p
#             p=${l%/*}
#             mkdir -p "$p"
#             touch "$l"
#         else
#             touch "$l"
#         fi
#     done < <(echo "$layout")
#     echo "$gcloudignore" > .gcloudignore
#     local code
#     code=`ml \
#         'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
#         'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
#         'fc = FileChooser.FromFile(".gcloudignore")' \
#         'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
#     PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
#         python -c "$code"
#     unzip -l /tmp/out.zip \
#         | tail +4 | head -n -2 \
#         | awk '{print $4}' \
#         | grep -vE '/$' \
#         | grep -Fve .gcloudignore -e go.mod \
#         || true
# }

@test 'b ~ a/b' {
    layout=`ml \
        a/b`
    gcloudignore=`ml \
        'b'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test 'a ~ a/b' {
    layout=`ml \
        a/b`
    gcloudignore=`ml \
        'a' \
        '!/a'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        a/b
}

@test '* ~ aa' {
    # if layout were aa, the path would be ./aa
    layout=`ml \
        aa/b`
    gcloudignore=`ml \
        '*'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test '* ~ aa/bb' {
    layout=`ml \
        aa/bb`
    gcloudignore=`ml \
        '*' \
        '!/aa'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        aa/bb
}

@test '*/bb ~ aa/bb' {
    layout=`ml \
        aa/bb`
    gcloudignore=`ml \
        '*/bb'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test '*/cc ~ aa/bb/cc' {
    layout=`ml \
        aa/bb/cc`
    gcloudignore=`ml \
        '*/cc'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        aa/bb/cc
}

@test 'aa/* ~ aa/bb' {
    layout=`ml \
        aa/bb`
    gcloudignore=`ml \
        'aa/*'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test 'bb/* ~ aa/bb/cc' {
    layout=`ml \
        aa/bb/cc`
    gcloudignore=`ml \
        'bb/*'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test '*b/cc ~ aa/bb/cc' {
    layout=`ml \
        aa/bb/cc`
    gcloudignore=`ml \
        '*b/cc'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test '* ~ a/b' {
    layout=`ml \
        a/b`
    gcloudignore=`ml \
        '*' \
        '!/a'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test '* ~ aa/b' {
    layout=`ml \
        aa/b`
    gcloudignore=`ml \
        '*' \
        '!/aa'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        aa/b
}

@test '* ~ a/bb' {
    layout=`ml \
        a/bb`
    gcloudignore=`ml \
        '*' \
        '!/a'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test '*/c ~ a/b/c' {
    layout=`ml \
        a/b/c`
    gcloudignore=`ml \
        '*/c'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test '*/c/d ~ a/b/c/d' {
    layout=`ml \
        a/b/c/d`
    gcloudignore=`ml \
        '*/c/d'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test '*/d ~ a/b/c/d' {
    layout=`ml \
        a/b/c/d`
    gcloudignore=`ml \
        '*/d'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        a/b/c/d
}
/app # bats-core/bin/bats a.bats
a.bats
 ✓ b ~ a/b
 ✓ a ~ a/b
 ✓ * ~ aa
 ✓ * ~ aa/bb
 ✓ */bb ~ aa/bb
 ✓ */cc ~ aa/bb/cc
 ✓ aa/* ~ aa/bb
 ✓ bb/* ~ aa/bb/cc
 ✓ *b/cc ~ aa/bb/cc
 ✓ * ~ a/b
 ✓ * ~ aa/b
 ✓ * ~ a/bb
 ✓ */c ~ a/b/c
 ✓ */c/d ~ a/b/c/d
 ✓ */d ~ a/b/c/d

15 tests, 0 failures

Unignoring a file

.
├── a
│   └── b
├── c
└── d
*
!/c
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the a dir:
    • a matches *
    • a is disallowed

c was ignored because . was disallowed.

.
├── a
│   └── b
├── c
└── d
*
!.
!/c
  • It considers the . dir:
    • . matches *, !.
    • . is ALLOWED
  • It considers the ./c file:
    • . matches *, !.
    • ./c matches *, !/c
    • ./c is ADDED
  • It considers the ./go.mod file:
    • . matches *, !.
    • ./go.mod matches *
    • ./go.mod is ignored
  • It considers the ./.gcloudignore file:
    • . matches *, !.
    • ./.gcloudignore matches *
    • ./.gcloudignore is ignored
  • It considers the ./d file:
    • . matches *, !.
    • ./d matches *
    • ./d is ignored
  • It considers the a dir:
    • a matches *
    • a is disallowed

It worked, but ./c and ./d matched *.

b.bats:

ml() { for l; do echo "$l"; done; }

setup() {
    load 'bats-support/load'
    load 'bats-assert/load'
    (cd .. && patch -p0 < app/archive.py.patch)
}

teardown() {
    (cd .. && patch -Rp0 < app/archive.py.patch)
    rm -rf /tmp/gcloudignore
}

_run() {
    local layout=$1 gcloudignore=$2
    mkdir /tmp/gcloudignore
    cd /tmp/gcloudignore
    local l
    while IFS= read -r l; do
        if [[ $l == */* ]]; then
            local p
            p=${l%/*}
            mkdir -p "$p"
            touch "$l"
        else
            touch "$l"
        fi
    done < <(echo "$layout")
    touch go.mod
    echo "$gcloudignore" > .gcloudignore
    gcloud functions deploy a \
        --project PROJECT \
        --gen2 \
        --runtime go121 \
        --region REGION \
        --trigger-http
    unzip -l /tmp/out.zip \
        | tail +4 | head -n -2 \
        | awk '{print $4}' \
        | grep -vE '/$' \
        | grep -Fve .gcloudignore -e go.mod \
        || true
}

# setup() {
#     load 'bats-support/load'
#     load 'bats-assert/load'
# }
# 
# teardown() {
#     rm -rf /tmp/gcloudignore
# }
# 
# _run() {
#     local layout=$1 gcloudignore=$2
#     mkdir /tmp/gcloudignore
#     cd /tmp/gcloudignore
#     local l
#     while IFS= read -r l; do
#         if [[ $l == */* ]]; then
#             local p
#             p=${l%/*}
#             mkdir -p "$p"
#             touch "$l"
#         else
#             touch "$l"
#         fi
#     done < <(echo "$layout")
#     echo "$gcloudignore" > .gcloudignore
#     local code
#     code=`ml \
#         'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
#         'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
#         'fc = FileChooser.FromFile(".gcloudignore")' \
#         'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
#     PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
#         python -c "$code"
#     unzip -l /tmp/out.zip \
#         | tail +4 | head -n -2 \
#         | awk '{print $4}' \
#         | grep -vE '/$' \
#         | grep -Fve .gcloudignore -e go.mod \
#         || true
# }

@test 'unignore a file' {
    layout=`ml \
        a/b \
        c \
        d`
    gcloudignore=`ml \
        '*' \
        '!/c'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test 'unignore a file (!.)' {
    layout=`ml \
        a/b \
        c \
        d`
    gcloudignore=`ml \
        '*' \
        '!.' \
        '!/c'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        c
}
/app # bats-core/bin/bats b.bats
b.bats
 ✓ unignore a file
 ✓ unignore a file (!.)

2 tests, 0 failures

Unignoring a directory

.
├── a
│   └── b
├── c
│   └── d
└── e
*
!/a
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the c dir:
    • c matches *
    • c is disallowed
  • It considers the a dir:
    • a matches *, !/a
    • a is ALLOWED
  • It considers the a/b file:
    • a matches *, !/a
    • a/b matches *
    • a/b is ignored

a/b was ignored because it matched *.

.
├── aa
│   └── b
├── c
│   └── d
└── e
*
!/aa
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the c dir:
    • c matches *
    • c is disallowed
  • It considers the aa dir:
    • aa matches *, !/aa
    • aa is ALLOWED
  • It considers the aa/b file:
    • aa matches *, !/aa
    • aa/b matches nothing
    • aa/b is ADDED

It worked.

.
├── a
│   └── b
├── c
│   └── d
└── e
*
!/a/**
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the c dir:
    • c matches *
    • c is disallowed
  • It considers the a dir:
    • a matches *, !/a/**
    • a is ALLOWED
  • It considers the a/b file:
    • a matches *, !/a/**
    • a/b matches *, !/a/**
    • a/b is ADDED

It worked.

c.bats:

ml() { for l; do echo "$l"; done; }

setup() {
    load 'bats-support/load'
    load 'bats-assert/load'
    (cd .. && patch -p0 < app/archive.py.patch)
}

teardown() {
    (cd .. && patch -Rp0 < app/archive.py.patch)
    rm -rf /tmp/gcloudignore
}

_run() {
    local layout=$1 gcloudignore=$2
    mkdir /tmp/gcloudignore
    cd /tmp/gcloudignore
    local l
    while IFS= read -r l; do
        if [[ $l == */* ]]; then
            local p
            p=${l%/*}
            mkdir -p "$p"
            touch "$l"
        else
            touch "$l"
        fi
    done < <(echo "$layout")
    touch go.mod
    echo "$gcloudignore" > .gcloudignore
    gcloud functions deploy a \
        --project PROJECT \
        --gen2 \
        --runtime go121 \
        --region REGION \
        --trigger-http
    unzip -l /tmp/out.zip \
        | tail +4 | head -n -2 \
        | awk '{print $4}' \
        | grep -vE '/$' \
        | grep -Fve .gcloudignore -e go.mod \
        || true
}

# setup() {
#     load 'bats-support/load'
#     load 'bats-assert/load'
# }
# 
# teardown() {
#     rm -rf /tmp/gcloudignore
# }
# 
# _run() {
#     local layout=$1 gcloudignore=$2
#     mkdir /tmp/gcloudignore
#     cd /tmp/gcloudignore
#     local l
#     while IFS= read -r l; do
#         if [[ $l == */* ]]; then
#             local p
#             p=${l%/*}
#             mkdir -p "$p"
#             touch "$l"
#         else
#             touch "$l"
#         fi
#     done < <(echo "$layout")
#     echo "$gcloudignore" > .gcloudignore
#     local code
#     code=`ml \
#         'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
#         'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
#         'fc = FileChooser.FromFile(".gcloudignore")' \
#         'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
#     PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
#         python -c "$code"
#     unzip -l /tmp/out.zip \
#         | tail +4 | head -n -2 \
#         | awk '{print $4}' \
#         | grep -vE '/$' \
#         | grep -Fve .gcloudignore -e go.mod \
#         || true
# }

@test 'unignore a dir' {
    layout=`ml \
        a/b \
        c/d \
        e`
    gcloudignore=`ml \
        '*' \
        '!/a'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test 'unignore a dir (aa)' {
    layout=`ml \
        aa/b \
        c/d \
        e`
    gcloudignore=`ml \
        '*' \
        '!/aa'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        aa/b
}

@test 'unignore a dir (!/a/**)' {
    layout=`ml \
        a/b \
        c/d \
        e`
    gcloudignore=`ml \
        '*' \
        '!/a/**'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        a/b
}
/app # bats-core/bin/bats c.bats
c.bats
 ✓ unignore a dir
 ✓ unignore a dir (aa)
 ✓ unignore a dir (!a/**)

3 tests, 0 failures

Unignoring a file in a directory

.
├── a
│   ├── b
│   │   └── c
│   ├── d
│   └── e
├── f
│   └── g
└── h
*
!/a/d
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the f dir:
    • f matches *
    • f is disallowed
  • It considers the a dir:
    • a matches *
    • a is disallowed
  • It considers the a/b dir:
    • a matches *
    • a/b is disallowed

a/d was ignored because a was disallowed.

.
├── a
│   ├── b
│   │   └── c
│   ├── d
│   └── e
├── f
│   └── g
└── h
*
!/a
!/a/d
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the f dir:
    • f matches *
    • f is disallowed
  • It considers the a dir:
    • a matches *, !/a
    • a is ALLOWED
  • It considers the a/e file:
    • a matches *, !/a
    • a/e matches *
    • a/e is ignored
  • It considers the a/d file:
    • a matches *, !/a
    • a/d matches *, !/a/d
    • a/d is ADDED
  • It considers the a/b dir:
    • a matches *, !/a
    • a/b matches *
    • a/b is disallowed

It worked, but a/b was disallowed and a/e was ignored because they matched *.

.
├── aa
│   ├── b
│   │   └── c
│   ├── d
│   └── e
├── f
│   └── g
└── h
*
!/aa
!/aa/d
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the f dir:
    • f matches *
    • f is disallowed
  • It considers the aa dir:
    • aa matches *, !/aa
    • aa is ALLOWED
  • It considers the aa/e file:
    • aa matches *, !/aa
    • aa/e matches nothing
    • aa/e is ADDED
  • It considers the aa/d file:
    • aa matches *, !/aa
    • aa/d matches !/aa/d
    • aa/d is ADDED
  • It considers the aa/b dir:
    • aa matches *, !/aa
    • aa/b matches nothing
    • aa/b is ALLOWED
  • It considers the aa/b/c file:
    • aa matches *, !/aa
    • aa/b matches nothing
    • aa/b/c matches nothing
    • aa/b/c is ADDED

aa/b/c and aa/e were added because dirs and files were allowed in aa.

.
├── aa
│   ├── b
│   │   └── c
│   ├── d
│   └── e
├── f
│   └── g
└── h
*
!/aa
/aa/*
!/aa/d
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the f dir:
    • f matches *
    • f is disallowed
  • It considers the aa dir:
    • aa matches *, !/aa
    • aa is ALLOWED
  • It considers the aa/e file:
    • aa matches *, !/aa
    • aa/e matches /aa/*
    • aa/e is ignored
  • It considers the aa/d file:
    • aa matches *, !/aa
    • aa/d matches /aa/*, !/aa/d
    • aa/d is ADDED
  • It considers the aa/b dir:
    • aa matches *, !/aa
    • aa/b matches /aa/*
    • aa/b is disallowed

It worked.

d.bats:

ml() { for l; do echo "$l"; done; }

setup() {
    load 'bats-support/load'
    load 'bats-assert/load'
    (cd .. && patch -p0 < app/archive.py.patch)
}

teardown() {
    (cd .. && patch -Rp0 < app/archive.py.patch)
    rm -rf /tmp/gcloudignore
}

_run() {
    local layout=$1 gcloudignore=$2
    mkdir /tmp/gcloudignore
    cd /tmp/gcloudignore
    local l
    while IFS= read -r l; do
        if [[ $l == */* ]]; then
            local p
            p=${l%/*}
            mkdir -p "$p"
            touch "$l"
        else
            touch "$l"
        fi
    done < <(echo "$layout")
    touch go.mod
    echo "$gcloudignore" > .gcloudignore
    gcloud functions deploy a \
        --project PROJECT \
        --gen2 \
        --runtime go121 \
        --region REGION \
        --trigger-http
    unzip -l /tmp/out.zip \
        | tail +4 | head -n -2 \
        | awk '{print $4}' \
        | grep -vE '/$' \
        | grep -Fve .gcloudignore -e go.mod \
        || true
}

# setup() {
#     load 'bats-support/load'
#     load 'bats-assert/load'
# }
# 
# teardown() {
#     rm -rf /tmp/gcloudignore
# }
# 
# _run() {
#     local layout=$1 gcloudignore=$2
#     mkdir /tmp/gcloudignore
#     cd /tmp/gcloudignore
#     local l
#     while IFS= read -r l; do
#         if [[ $l == */* ]]; then
#             local p
#             p=${l%/*}
#             mkdir -p "$p"
#             touch "$l"
#         else
#             touch "$l"
#         fi
#     done < <(echo "$layout")
#     echo "$gcloudignore" > .gcloudignore
#     local code
#     code=`ml \
#         'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
#         'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
#         'fc = FileChooser.FromFile(".gcloudignore")' \
#         'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
#     PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
#         python -c "$code"
#     unzip -l /tmp/out.zip \
#         | tail +4 | head -n -2 \
#         | awk '{print $4}' \
#         | grep -vE '/$' \
#         | grep -Fve .gcloudignore -e go.mod \
#         || true
# }

@test 'unignore a file in a dir' {
    layout=`ml \
        a/b/c \
        a/d \
        a/e \
        f/g \
        h`
    gcloudignore=`ml \
        '*' \
        '!/a/d'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test 'unignore a file in a dir (!/a)' {
    layout=`ml \
        a/b/c \
        a/d \
        a/e \
        f/g \
        h`
    gcloudignore=`ml \
        '*' \
        '!/a' \
        '!/a/d'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        a/d
}

@test 'unignore a file in a dir (aa)' {
    layout=`ml \
        aa/b/c \
        aa/d \
        aa/e \
        f/g \
        h`
    gcloudignore=`ml \
        '*' \
        '!/aa' \
        '!/aa/d'`
    run _run "$layout" "$gcloudignore"
    assert_output "`ml \
        aa/e \
        aa/d \
        aa/b/c`"
}

@test 'unignore a file in a dir (/aa/*)' {
    layout=`ml \
        aa/b/c \
        aa/d \
        aa/e \
        f/g \
        h`
    gcloudignore=`ml \
        '*' \
        '!/aa' \
        '/aa/*' \
        '!/aa/d'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        aa/d
}
/app # bats-core/bin/bats d.bats
d.bats
 ✓ unignore a file in a dir
 ✓ unignore a file in a dir (!a)
 ✓ unignore a file in a dir (aa)
 ✓ unignore a file in a dir (aa/*)

4 tests, 0 failures

Unignoring a directory in a directory

.
├── a
│   ├── b
│   │   └── c
│   ├── d
│   │   └── e
│   └── f
├── g
│   └── h
└── i
*
!/a/b
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the g dir:
    • g matches *
    • g is disallowed
  • It considers the a dir:
    • a matches *
    • a is disallowed
  • It considers the a/b dir:
    • a matches *
    • a/b is disallowed
  • It considers the a/d dir:
    • a matches *
    • a/d is disallowed

a/b/c was ignored because a/b was disallowed because a was disallowed.

.
├── a
│   ├── b
│   │   └── c
│   ├── d
│   │   └── e
│   └── f
├── g
│   └── h
└── i
*
!/a
!/a/b
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the g dir:
    • g matches *
    • g is disallowed
  • It considers the a dir:
    • a matches *, !/a
    • a is ALLOWED
  • It considers the a/f file:
    • a matches *, !/a
    • a/f matches *
    • a/f is ignored
  • It considers the a/b dir:
    • a matches *, !/a
    • a/b matches *, !/a/b
    • a/b is ALLOWED
  • It considers the a/b/c file:
    • a matches *, !/a
    • a/b matches *, !/a/b
    • a/b/c matches nothing
    • a/b/c is ADDED
  • It considers the a/d dir:
    • a matches *, !/a
    • a/d matches *
    • a/d is disallowed

It worked, but a/d was disallowed and a/f was ignored because they matched *.

.
├── aa
│   ├── b
│   │   └── c
│   ├── d
│   │   └── e
│   └── f
├── g
│   └── h
└── i
*
!/aa
!/aa/b
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the g dir:
    • g matches *
    • g is disallowed
  • It considers the aa dir:
    • aa matches *, !/aa
    • aa is ALLOWED
  • It considers the aa/f file:
    • aa matches *, !/aa
    • aa/f matches nothing
    • aa/f is ADDED
  • It considers the aa/b dir:
    • aa matches *, !/aa
    • aa/b matches !/aa/b
    • aa/b is ALLOWED
  • It considers the aa/b/c file:
    • aa matches *, !/aa
    • aa/b matches !/aa/b
    • aa/b/c matches nothing
    • aa/b/c is ADDED
  • It considers the aa/d dir:
    • aa matches *, !/aa
    • aa/d matches nothing
    • aa/d is ALLOWED
  • It considers the aa/d/e file:
    • aa matches *, !/aa
    • aa/d matches nothing
    • aa/d/e matches nothing
    • aa/d/e is ADDED

aa/d/e and aa/f were added because files and dirs were allowed in aa.

.
├── aa
│   ├── b
│   │   └── c
│   ├── d
│   │   └── e
│   └── f
├── g
│   └── h
└── i
*
!aa
aa/*
!aa/b
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the g dir:
    • g matches *
    • g is disallowed
  • It considers the aa dir:
    • aa matches *, !/aa
    • aa is ALLOWED
  • It considers the aa/f file:
    • aa matches *, !/aa
    • aa/f matches /aa/*
    • aa/f is ignored
  • It considers the aa/b dir:
    • aa matches *, !/aa
    • aa/b matches /aa/*, !/aa/b
    • aa/b is ALLOWED
  • It considers the aa/b/c file:
    • aa matches *, !/aa
    • aa/b matches /aa/*, !/aa/b
    • aa/b/c matches nothing
    • aa/b/c is ADDED
  • It considers the aa/d dir:
    • aa matches *, !/aa
    • aa/d matches /aa/*
    • aa/d is disallowed

It worked.

e.bats:

ml() { for l; do echo "$l"; done; }

setup() {
    load 'bats-support/load'
    load 'bats-assert/load'
    (cd .. && patch -p0 < app/archive.py.patch)
}

teardown() {
    (cd .. && patch -Rp0 < app/archive.py.patch)
    rm -rf /tmp/gcloudignore
}

_run() {
    local layout=$1 gcloudignore=$2
    mkdir /tmp/gcloudignore
    cd /tmp/gcloudignore
    local l
    while IFS= read -r l; do
        if [[ $l == */* ]]; then
            local p
            p=${l%/*}
            mkdir -p "$p"
            touch "$l"
        else
            touch "$l"
        fi
    done < <(echo "$layout")
    touch go.mod
    echo "$gcloudignore" > .gcloudignore
    gcloud functions deploy a \
        --project PROJECT \
        --gen2 \
        --runtime go121 \
        --region REGION \
        --trigger-http
    unzip -l /tmp/out.zip \
        | tail +4 | head -n -2 \
        | awk '{print $4}' \
        | grep -vE '/$' \
        | grep -Fve .gcloudignore -e go.mod \
        || true
}

# setup() {
#     load 'bats-support/load'
#     load 'bats-assert/load'
# }
# 
# teardown() {
#     rm -rf /tmp/gcloudignore
# }
# 
# _run() {
#     local layout=$1 gcloudignore=$2
#     mkdir /tmp/gcloudignore
#     cd /tmp/gcloudignore
#     local l
#     while IFS= read -r l; do
#         if [[ $l == */* ]]; then
#             local p
#             p=${l%/*}
#             mkdir -p "$p"
#             touch "$l"
#         else
#             touch "$l"
#         fi
#     done < <(echo "$layout")
#     echo "$gcloudignore" > .gcloudignore
#     local code
#     code=`ml \
#         'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
#         'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
#         'fc = FileChooser.FromFile(".gcloudignore")' \
#         'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
#     PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
#         python -c "$code"
#     unzip -l /tmp/out.zip \
#         | tail +4 | head -n -2 \
#         | awk '{print $4}' \
#         | grep -vE '/$' \
#         | grep -Fve .gcloudignore -e go.mod \
#         || true
# }

@test 'unignore a dir in a dir' {
    layout=`ml \
        a/b/c \
        a/d/e \
        a/f \
        g/h \
        i`
    gcloudignore=`ml \
        '*' \
        '!/a/b'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        ''
}

@test 'unignore a dir in a dir (!/a)' {
    layout=`ml \
        a/b/c \
        a/d/e \
        a/f \
        g/h \
        i`
    gcloudignore=`ml \
        '*' \
        '!/a' \
        '!/a/b'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        a/b/c
}

@test 'unignore a dir in a dir (aa)' {
    layout=`ml \
        aa/b/c \
        aa/d/e \
        aa/f \
        g/h \
        i`
    gcloudignore=`ml \
        '*' \
        '!/aa' \
        '!/aa/b'`
    run _run "$layout" "$gcloudignore"
    assert_output "`ml \
        aa/f \
        aa/b/c \
        aa/d/e`"
}

@test 'unignore a dir in a dir (/aa/*)' {
    layout=`ml \
        aa/b/c \
        aa/d/e \
        aa/f \
        g/h \
        i`
    gcloudignore=`ml \
        '*' \
        '!/aa' \
        '/aa/*' \
        '!/aa/b'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        aa/b/c
}
/app # bats-core/bin/bats e.bats
e.bats
 ✓ unignore a dir in a dir
 ✓ unignore a dir in a dir (!a)
 ✓ unignore a dir in a dir (aa)
 ✓ unignore a dir in a dir (aa/*)

4 tests, 0 failures

!a/ trick

Althought it might be useful in theory, I don't see a use case for now.

.
└── aa
    ├── b
    │   └── c
    └── d
*
!/aa
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the aa dir:
    • aa matches *, !/aa
    • aa is ALLOWED
  • It considers the aa/d file:
    • aa matches *, !/aa
    • aa/d matches nothing
    • aa/d is ADDED
  • It considers the aa/b dir:
    • aa matches *, !/aa
    • aa/b matches nothing
    • aa/b is ALLOWED
  • It considers the aa/b/c file:
    • aa matches *, !/aa
    • aa/b matches nothing
    • aa/b/c matches nothing
    • aa/b/c is ADDED

aa/d was added because files in aa were allowed.

.
└── aa
    ├── b
    │   └── c
    └── d
*
!/aa/
  • It considers the . dir:
    • . matches *
    • . is disallowed
  • It considers the aa dir:
    • aa matches *
    • aa is disallowed
  • It considers the aa/b dir:
    • aa matches *, !/aa/
    • aa/b matches nothing
    • aa/b is ALLOWED
  • It considers the aa/b/c file:
    • aa matches *, !/aa/
    • aa/b matches nothing
    • aa/b/c matches nothing
    • aa/b/c is ADDED

Now files in aa are not allowed and a/d was ignored. Do note how aa doesn't match !/aa/ when it's the last segment of a path, and matches !/aa/ when it isn't. That's because in the former case it's matched as a file.

f.bats:

ml() { for l; do echo "$l"; done; }

setup() {
    load 'bats-support/load'
    load 'bats-assert/load'
    (cd .. && patch -p0 < app/archive.py.patch)
}

teardown() {
    (cd .. && patch -Rp0 < app/archive.py.patch)
    rm -rf /tmp/gcloudignore
}

_run() {
    local layout=$1 gcloudignore=$2
    mkdir /tmp/gcloudignore
    cd /tmp/gcloudignore
    local l
    while IFS= read -r l; do
        if [[ $l == */* ]]; then
            local p
            p=${l%/*}
            mkdir -p "$p"
            touch "$l"
        else
            touch "$l"
        fi
    done < <(echo "$layout")
    touch go.mod
    echo "$gcloudignore" > .gcloudignore
    gcloud functions deploy a \
        --project PROJECT \
        --gen2 \
        --runtime go121 \
        --region REGION \
        --trigger-http
    unzip -l /tmp/out.zip \
        | tail +4 | head -n -2 \
        | awk '{print $4}' \
        | grep -vE '/$' \
        | grep -Fve .gcloudignore -e go.mod \
        || true
}

# setup() {
#     load 'bats-support/load'
#     load 'bats-assert/load'
# }
# 
# teardown() {
#     rm -rf /tmp/gcloudignore
# }
# 
# _run() {
#     local layout=$1 gcloudignore=$2
#     mkdir /tmp/gcloudignore
#     cd /tmp/gcloudignore
#     local l
#     while IFS= read -r l; do
#         if [[ $l == */* ]]; then
#             local p
#             p=${l%/*}
#             mkdir -p "$p"
#             touch "$l"
#         else
#             touch "$l"
#         fi
#     done < <(echo "$layout")
#     echo "$gcloudignore" > .gcloudignore
#     local code
#     code=`ml \
#         'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
#         'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
#         'fc = FileChooser.FromFile(".gcloudignore")' \
#         'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
#     PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
#         python -c "$code"
#     unzip -l /tmp/out.zip \
#         | tail +4 | head -n -2 \
#         | awk '{print $4}' \
#         | grep -vE '/$' \
#         | grep -Fve .gcloudignore -e go.mod \
#         || true
# }

@test '!a/ trick' {
    layout=`ml \
        aa/b/c \
        aa/d`
    gcloudignore=`ml \
        '*' \
        '!/aa'`
    run _run "$layout" "$gcloudignore"
    assert_output "`ml \
        aa/d \
        aa/b/c`"
}

@test '!a/ trick (!/aa/)' {
    layout=`ml \
        aa/b/c \
        aa/d`
    gcloudignore=`ml \
        '*' \
        '!/aa/'`
    run _run "$layout" "$gcloudignore"
    assert_output \
        aa/b/c
}
/app # bats-core/bin/bats f.bats
f.bats
 ✓ !a/ trick
 ✓ !a/ trick (!aa/)

2 tests, 0 failures

Scripts for producing verbose output

Running a script:

/app # bash ga.sh && cd /tmp/gcloudignore && (cd / && patch -p0 < app/display.patch >/dev/null) && gcloud functions deploy a --project PROJECT --gen2 --runtime go121 --region REGION --trigger-http; (cd / && patch -Rp0 < app/display.patch >/dev/null)

display.patch:

--- /google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py
+++ /google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py
@@ -73,17 +73,24 @@
   try:
     for root, _, filelist in os.walk(six.text_type(src_dir)):
       dir_path = os.path.normpath(os.path.relpath(root, src_dir))
+      print('* It considers the `%s` dir:' % dir_path)
       if not predicate(dir_path):
+        print(f'  * `{dir_path}` is disallowed')
         continue
+      print(f'  * `{dir_path}` is ALLOWED')
       if dir_path != os.curdir:
         AddToArchive(zip_file, src_dir, dir_path, False)
       for file_name in filelist:
         file_path = os.path.join(dir_path, file_name)
+        print('* It considers the `%s` file:' % file_path)
         if not predicate(file_path):
+          print(f'  * `{file_path}` is ignored')
           continue
+        print(f'  * `{file_path}` is ADDED')
         AddToArchive(zip_file, src_dir, file_path, True)
   finally:
     zip_file.close()
+  import sys; sys.exit()
 
 
 def AddToArchive(zip_file, src_dir, rel_path, is_file):
--- /google-cloud-sdk/lib/googlecloudsdk/command_lib/util/gcloudignore.py
+++ /google-cloud-sdk/lib/googlecloudsdk/command_lib/util/gcloudignore.py
@@ -183,11 +183,18 @@
     path_prefixes = glob.GetPathPrefixes(path)[1:]  # root dir can't be matched
     for path_prefix in path_prefixes:
       prefix_match = Match.NO_MATCH
+      matches = []
       for pattern in self.patterns:
         is_prefix_dir = path_prefix != path or is_dir
         match = pattern.Matches(path_prefix, is_dir=is_prefix_dir)
         if match is not Match.NO_MATCH:
+          matches.append(pattern)
           prefix_match = match
+      print('  * `%s` matches %s' % (path_prefix,
+        ', '.join(f"`{'!' if m.negated else ''}{m.pattern.pattern}{'/' if m.pattern.must_be_dir else ''}`" for m in matches)
+        if matches
+        else 'nothing'
+      ))
       if prefix_match is Match.IGNORE:
         log.debug('Skipping file [{}]'.format(path))
         return False

ga.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    a/b \
    c \
    d`
gcloudignore=`ml \
    '*' \
    '!/c'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

gb.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    a/b \
    c \
    d`
gcloudignore=`ml \
    '*' \
    '!.' \
    '!/c'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

ha.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    a/b \
    c/d \
    e`
gcloudignore=`ml \
    '*' \
    '!/a'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

hb.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    aa/b \
    c/d \
    e`
gcloudignore=`ml \
    '*' \
    '!/aa'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

hc.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    a/b \
    c/d \
    e`
gcloudignore=`ml \
    '*' \
    '!/a/**'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

ia.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    a/b/c \
    a/d \
    a/e \
    f/g \
    h`
gcloudignore=`ml \
    '*' \
    '!/a/d'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

ib.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    a/b/c \
    a/d \
    a/e \
    f/g \
    h`
gcloudignore=`ml \
    '*' \
    '!/a' \
    '!/a/d'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

ic.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    aa/b/c \
    aa/d \
    aa/e \
    f/g \
    h`
gcloudignore=`ml \
    '*' \
    '!/aa' \
    '!/aa/d'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

id.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    aa/b/c \
    aa/d \
    aa/e \
    f/g \
    h`
gcloudignore=`ml \
    '*' \
    '!/aa' \
    '/aa/*' \
    '!/aa/d'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

ja.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    a/b/c \
    a/d/e \
    a/f \
    g/h \
    i`
gcloudignore=`ml \
    '*' \
    '!/a/b'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

jb.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    a/b/c \
    a/d/e \
    a/f \
    g/h \
    i`
gcloudignore=`ml \
    '*' \
    '!/a' \
    '!/a/b'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

jc.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    aa/b/c \
    aa/d/e \
    aa/f \
    g/h \
    i`
gcloudignore=`ml \
    '*' \
    '!/aa' \
    '!/aa/b'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

jd.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    aa/b/c \
    aa/d/e \
    aa/f \
    g/h \
    i`
gcloudignore=`ml \
    '*' \
    '!/aa' \
    '/aa/*' \
    '!/aa/b'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

ka.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    aa/b/c \
    aa/d`
gcloudignore=`ml \
    '*' \
    '!/aa'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo

kb.sh:

rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
    aa/b/c \
    aa/d`
gcloudignore=`ml \
    '*' \
    '!/aa/'`
while IFS= read -r l; do
    if [[ $l == */* ]]; then
        p=${l%/*}
        mkdir -p "$p"
        touch "$l"
    else
        touch "$l"
    fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment