mirror of https://github.com/docker/docs.git
add support for exclusion rules in dockerignore
Signed-off-by: Dave Goodchild <buddhamagnet@gmail.com>
This commit is contained in:
parent
67da055ceb
commit
6fd8e485c8
|
@ -32,13 +32,14 @@ ephemeral as possible. By “ephemeral,” we mean that it can be stopped and
|
||||||
destroyed and a new one built and put in place with an absolute minimum of
|
destroyed and a new one built and put in place with an absolute minimum of
|
||||||
set-up and configuration.
|
set-up and configuration.
|
||||||
|
|
||||||
### Use [a .dockerignore file](https://docs.docker.com/reference/builder/#the-dockerignore-file)
|
### Use a .dockerignore file
|
||||||
|
|
||||||
For faster uploading and efficiency during `docker build`, you should use
|
In most cases, it's best to put each Dockerfile in an empty directory. Then,
|
||||||
a `.dockerignore` file to exclude files or directories from the build
|
add to that directory only the files needed for building the Dockerfile. To
|
||||||
context and final image. For example, unless`.git` is needed by your build
|
increase the build's performance, you can exclude files and directories by
|
||||||
process or scripts, you should add it to `.dockerignore`, which can save many
|
adding a `.dockerignore` file to that directory as well. This file supports
|
||||||
megabytes worth of upload time.
|
exclusion patterns similar to `.gitignore` files. For information on creating one,
|
||||||
|
see the [.dockerignore file](../../reference/builder/#dockerignore-file).
|
||||||
|
|
||||||
### Avoid installing unnecessary packages
|
### Avoid installing unnecessary packages
|
||||||
|
|
||||||
|
|
|
@ -41,10 +41,11 @@ whole context must be transferred to the daemon. The Docker CLI reports
|
||||||
> repository, the entire contents of your hard drive will get sent to the daemon (and
|
> repository, the entire contents of your hard drive will get sent to the daemon (and
|
||||||
> thus to the machine running the daemon). You probably don't want that.
|
> thus to the machine running the daemon). You probably don't want that.
|
||||||
|
|
||||||
In most cases, it's best to put each Dockerfile in an empty directory, and then add only
|
In most cases, it's best to put each Dockerfile in an empty directory. Then,
|
||||||
the files needed for building that Dockerfile to that directory. To further speed up the
|
only add the files needed for building the Dockerfile to the directory. To
|
||||||
build, you can exclude files and directories by adding a `.dockerignore` file to the same
|
increase the build's performance, you can exclude files and directories by
|
||||||
directory.
|
adding a `.dockerignore` file to the directory. For information about how to
|
||||||
|
[create a `.dockerignore` file](#the-dockerignore-file) on this page.
|
||||||
|
|
||||||
You can specify a repository and tag at which to save the new image if
|
You can specify a repository and tag at which to save the new image if
|
||||||
the build succeeds:
|
the build succeeds:
|
||||||
|
@ -169,43 +170,67 @@ will result in `def` having a value of `hello`, not `bye`. However,
|
||||||
`ghi` will have a value of `bye` because it is not part of the same command
|
`ghi` will have a value of `bye` because it is not part of the same command
|
||||||
that set `abc` to `bye`.
|
that set `abc` to `bye`.
|
||||||
|
|
||||||
## The `.dockerignore` file
|
### .dockerignore file
|
||||||
|
|
||||||
If a file named `.dockerignore` exists in the source repository, then it
|
If a file named `.dockerignore` exists in the root of `PATH`, then Docker
|
||||||
is interpreted as a newline-separated list of exclusion patterns.
|
interprets it as a newline-separated list of exclusion patterns. Docker excludes
|
||||||
Exclusion patterns match files or directories relative to the source repository
|
files or directories relative to `PATH` that match these exclusion patterns. If
|
||||||
that will be excluded from the context. Globbing is done using Go's
|
there are any `.dockerignore` files in `PATH` subdirectories, Docker treats
|
||||||
|
them as normal files.
|
||||||
|
|
||||||
|
Filepaths in `.dockerignore` are absolute with the current directory as the
|
||||||
|
root. Wildcards are allowed but the search is not recursive. Globbing (file name
|
||||||
|
expansion) is done using Go's
|
||||||
[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules.
|
[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules.
|
||||||
|
|
||||||
> **Note**:
|
You can specify exceptions to exclusion rules. To do this, simply prefix a
|
||||||
> The `.dockerignore` file can even be used to ignore the `Dockerfile` and
|
pattern with an `!` (exclamation mark) in the same way you would in a
|
||||||
> `.dockerignore` files. This might be useful if you are copying files from
|
`.gitignore` file. Currently there is no support for regular expressions.
|
||||||
> the root of the build context into your new container but do not want to
|
Formats like `[^temp*]` are ignored.
|
||||||
> include the `Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`).
|
|
||||||
|
|
||||||
The following example shows the use of the `.dockerignore` file to exclude the
|
The following is an example `.dockerignore` file:
|
||||||
`.git` directory from the context. Its effect can be seen in the changed size of
|
|
||||||
the uploaded context.
|
```
|
||||||
|
*/temp*
|
||||||
|
*/*/temp*
|
||||||
|
temp?
|
||||||
|
*.md
|
||||||
|
!LICENCSE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
This file causes the following build behavior:
|
||||||
|
|
||||||
|
| Rule | Behavior |
|
||||||
|
|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `*/temp*` | Exclude all files with names starting with`temp` in any subdirectory below the root directory. For example, a file named`/somedir/temporary.txt` is ignored. |
|
||||||
|
| `*/*/temp*` | Exclude files starting with name `temp` from any subdirectory that is two levels below the root directory. For example, the file `/somedir/subdir/temporary.txt` is ignored. |
|
||||||
|
| `temp?` | Exclude the files that match the pattern in the root directory. For example, the files `tempa`, `tempb` in the root directory are ignored. |
|
||||||
|
| `*.md ` | Exclude all markdown files. |
|
||||||
|
| `!LICENSE.md` | Exception to the exclude all Markdown files is this file, `LICENSE.md`, include this file in the build. |
|
||||||
|
|
||||||
|
The placement of `!` exception rules influences the matching algorithm; the
|
||||||
|
last line of the `.dockerignore` that matches a particular file determines
|
||||||
|
whether it is included or excluded. In the above example, the `LICENSE.md` file
|
||||||
|
matches both the `*.md` and `!LICENSE.md` rule. If you reverse the lines in the
|
||||||
|
example:
|
||||||
|
|
||||||
|
```
|
||||||
|
*/temp*
|
||||||
|
*/*/temp*
|
||||||
|
temp?
|
||||||
|
!LICENCSE.md
|
||||||
|
*.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The build would exclude `LICENSE.md` because the last `*.md` rule adds all
|
||||||
|
Markdown files back onto the ignore list. The `!LICENSE.md` rule has no effect
|
||||||
|
because the subsequent `*.md` rule overrides it.
|
||||||
|
|
||||||
|
You can even use the `.dockerignore` file to ignore the `Dockerfile` and
|
||||||
|
`.dockerignore` files. This is useful if you are copying files from the root of
|
||||||
|
the build context into your new container but do not want to include the
|
||||||
|
`Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`).
|
||||||
|
|
||||||
$ docker build .
|
|
||||||
Uploading context 18.829 MB
|
|
||||||
Uploading context
|
|
||||||
Step 0 : FROM busybox
|
|
||||||
---> 769b9341d937
|
|
||||||
Step 1 : CMD echo Hello World
|
|
||||||
---> Using cache
|
|
||||||
---> 99cc1ad10469
|
|
||||||
Successfully built 99cc1ad10469
|
|
||||||
$ echo ".git" > .dockerignore
|
|
||||||
$ docker build .
|
|
||||||
Uploading context 6.76 MB
|
|
||||||
Uploading context
|
|
||||||
Step 0 : FROM busybox
|
|
||||||
---> 769b9341d937
|
|
||||||
Step 1 : CMD echo Hello World
|
|
||||||
---> Using cache
|
|
||||||
---> 99cc1ad10469
|
|
||||||
Successfully built 99cc1ad10469
|
|
||||||
|
|
||||||
## FROM
|
## FROM
|
||||||
|
|
||||||
|
|
|
@ -653,6 +653,26 @@ If you use STDIN or specify a `URL`, the system places the contents into a
|
||||||
file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this
|
file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this
|
||||||
scenario, there is no context.
|
scenario, there is no context.
|
||||||
|
|
||||||
|
By default the `docker build` command will look for a `Dockerfile` at the
|
||||||
|
root of the build context. The `-f`, `--file`, option lets you specify
|
||||||
|
the path to an alternative file to use instead. This is useful
|
||||||
|
in cases where the same set of files are used for multiple builds. The path
|
||||||
|
must be to a file within the build context. If a relative path is specified
|
||||||
|
then it must to be relative to the current directory.
|
||||||
|
|
||||||
|
In most cases, it's best to put each Dockerfile in an empty directory. Then, add
|
||||||
|
to that directory only the files needed for building the Dockerfile. To increase
|
||||||
|
the build's performance, you can exclude files and directories by adding a
|
||||||
|
`.dockerignore` file to that directory as well. For information on creating one,
|
||||||
|
see the [.dockerignore file](../../reference/builder/#dockerignore-file).
|
||||||
|
|
||||||
|
If the Docker client loses connection to the daemon, the build is canceled.
|
||||||
|
This happens if you interrupt the Docker client with `ctrl-c` or if the Docker
|
||||||
|
client is killed for any reason.
|
||||||
|
|
||||||
|
> **Note:** Currently only the "run" phase of the build can be canceled until
|
||||||
|
> pull cancelation is implemented).
|
||||||
|
|
||||||
### Return code
|
### Return code
|
||||||
|
|
||||||
On a successful build, a return code of success `0` will be returned.
|
On a successful build, a return code of success `0` will be returned.
|
||||||
|
@ -673,55 +693,11 @@ INFO[0000] The command [/bin/sh -c exit 13] returned a non-zero code: 13
|
||||||
$ echo $?
|
$ echo $?
|
||||||
1
|
1
|
||||||
```
|
```
|
||||||
|
|
||||||
### .dockerignore file
|
|
||||||
|
|
||||||
If a file named `.dockerignore` exists in the root of `PATH` then it
|
|
||||||
is interpreted as a newline-separated list of exclusion patterns.
|
|
||||||
Exclusion patterns match files or directories relative to `PATH` that
|
|
||||||
will be excluded from the context. Globbing is done using Go's
|
|
||||||
[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules.
|
|
||||||
|
|
||||||
Please note that `.dockerignore` files in other subdirectories are
|
|
||||||
considered as normal files. Filepaths in `.dockerignore` are absolute with
|
|
||||||
the current directory as the root. Wildcards are allowed but the search
|
|
||||||
is not recursive.
|
|
||||||
|
|
||||||
#### Example .dockerignore file
|
|
||||||
*/temp*
|
|
||||||
*/*/temp*
|
|
||||||
temp?
|
|
||||||
|
|
||||||
The first line above `*/temp*`, would ignore all files with names starting with
|
|
||||||
`temp` from any subdirectory below the root directory. For example, a file named
|
|
||||||
`/somedir/temporary.txt` would be ignored. The second line `*/*/temp*`, will
|
|
||||||
ignore files starting with name `temp` from any subdirectory that is two levels
|
|
||||||
below the root directory. For example, the file `/somedir/subdir/temporary.txt`
|
|
||||||
would get ignored in this case. The last line in the above example `temp?`
|
|
||||||
will ignore the files that match the pattern from the root directory.
|
|
||||||
For example, the files `tempa`, `tempb` are ignored from the root directory.
|
|
||||||
Currently there is no support for regular expressions. Formats
|
|
||||||
like `[^temp*]` are ignored.
|
|
||||||
|
|
||||||
By default the `docker build` command will look for a `Dockerfile` at the
|
|
||||||
root of the build context. The `-f`, `--file`, option lets you specify
|
|
||||||
the path to an alternative file to use instead. This is useful
|
|
||||||
in cases where the same set of files are used for multiple builds. The path
|
|
||||||
must be to a file within the build context. If a relative path is specified
|
|
||||||
then it must to be relative to the current directory.
|
|
||||||
|
|
||||||
If the Docker client loses connection to the daemon, the build is canceled.
|
|
||||||
This happens if you interrupt the Docker client with `ctrl-c` or if the Docker
|
|
||||||
client is killed for any reason.
|
|
||||||
|
|
||||||
> **Note:** Currently only the "run" phase of the build can be canceled until
|
|
||||||
> pull cancelation is implemented).
|
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
[*Dockerfile Reference*](/reference/builder).
|
[*Dockerfile Reference*](/reference/builder).
|
||||||
|
|
||||||
#### Examples
|
### Examples
|
||||||
|
|
||||||
$ docker build .
|
$ docker build .
|
||||||
Uploading context 10240 bytes
|
Uploading context 10240 bytes
|
||||||
|
@ -790,7 +766,8 @@ affect the build cache.
|
||||||
|
|
||||||
This example shows the use of the `.dockerignore` file to exclude the `.git`
|
This example shows the use of the `.dockerignore` file to exclude the `.git`
|
||||||
directory from the context. Its effect can be seen in the changed size of the
|
directory from the context. Its effect can be seen in the changed size of the
|
||||||
uploaded context.
|
uploaded context. The builder reference contains detailed information on
|
||||||
|
[creating a .dockerignore file](../../builder/#dockerignore-file)
|
||||||
|
|
||||||
$ docker build -t vieux/apache:2.0 .
|
$ docker build -t vieux/apache:2.0 .
|
||||||
|
|
||||||
|
|
|
@ -3427,20 +3427,29 @@ func (s *DockerSuite) TestBuildDockerignore(c *check.C) {
|
||||||
RUN [[ ! -e /bla/src/_vendor ]]
|
RUN [[ ! -e /bla/src/_vendor ]]
|
||||||
RUN [[ ! -e /bla/.gitignore ]]
|
RUN [[ ! -e /bla/.gitignore ]]
|
||||||
RUN [[ ! -e /bla/README.md ]]
|
RUN [[ ! -e /bla/README.md ]]
|
||||||
|
RUN [[ ! -e /bla/dir/foo ]]
|
||||||
|
RUN [[ ! -e /bla/foo ]]
|
||||||
RUN [[ ! -e /bla/.git ]]`
|
RUN [[ ! -e /bla/.git ]]`
|
||||||
ctx, err := fakeContext(dockerfile, map[string]string{
|
ctx, err := fakeContext(dockerfile, map[string]string{
|
||||||
"Makefile": "all:",
|
"Makefile": "all:",
|
||||||
".git/HEAD": "ref: foo",
|
".git/HEAD": "ref: foo",
|
||||||
"src/x.go": "package main",
|
"src/x.go": "package main",
|
||||||
"src/_vendor/v.go": "package main",
|
"src/_vendor/v.go": "package main",
|
||||||
|
"dir/foo": "",
|
||||||
".gitignore": "",
|
".gitignore": "",
|
||||||
"README.md": "readme",
|
"README.md": "readme",
|
||||||
".dockerignore": ".git\npkg\n.gitignore\nsrc/_vendor\n*.md",
|
".dockerignore": `
|
||||||
|
.git
|
||||||
|
pkg
|
||||||
|
.gitignore
|
||||||
|
src/_vendor
|
||||||
|
*.md
|
||||||
|
dir`,
|
||||||
})
|
})
|
||||||
defer ctx.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer ctx.Close()
|
||||||
if _, err := buildImageFromContext(name, ctx, true); err != nil {
|
if _, err := buildImageFromContext(name, ctx, true); err != nil {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -3467,6 +3476,55 @@ func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildDockerignoreExceptions(c *check.C) {
|
||||||
|
name := "testbuilddockerignoreexceptions"
|
||||||
|
defer deleteImages(name)
|
||||||
|
dockerfile := `
|
||||||
|
FROM busybox
|
||||||
|
ADD . /bla
|
||||||
|
RUN [[ -f /bla/src/x.go ]]
|
||||||
|
RUN [[ -f /bla/Makefile ]]
|
||||||
|
RUN [[ ! -e /bla/src/_vendor ]]
|
||||||
|
RUN [[ ! -e /bla/.gitignore ]]
|
||||||
|
RUN [[ ! -e /bla/README.md ]]
|
||||||
|
RUN [[ -e /bla/dir/dir/foo ]]
|
||||||
|
RUN [[ ! -e /bla/dir/foo1 ]]
|
||||||
|
RUN [[ -f /bla/dir/e ]]
|
||||||
|
RUN [[ -f /bla/dir/e-dir/foo ]]
|
||||||
|
RUN [[ ! -e /bla/foo ]]
|
||||||
|
RUN [[ ! -e /bla/.git ]]`
|
||||||
|
ctx, err := fakeContext(dockerfile, map[string]string{
|
||||||
|
"Makefile": "all:",
|
||||||
|
".git/HEAD": "ref: foo",
|
||||||
|
"src/x.go": "package main",
|
||||||
|
"src/_vendor/v.go": "package main",
|
||||||
|
"dir/foo": "",
|
||||||
|
"dir/foo1": "",
|
||||||
|
"dir/dir/f1": "",
|
||||||
|
"dir/dir/foo": "",
|
||||||
|
"dir/e": "",
|
||||||
|
"dir/e-dir/foo": "",
|
||||||
|
".gitignore": "",
|
||||||
|
"README.md": "readme",
|
||||||
|
".dockerignore": `
|
||||||
|
.git
|
||||||
|
pkg
|
||||||
|
.gitignore
|
||||||
|
src/_vendor
|
||||||
|
*.md
|
||||||
|
dir
|
||||||
|
!dir/e*
|
||||||
|
!dir/dir/foo`,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
defer ctx.Close()
|
||||||
|
if _, err := buildImageFromContext(name, ctx, true); err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) {
|
func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) {
|
||||||
name := "testbuilddockerignoredockerfile"
|
name := "testbuilddockerignoredockerfile"
|
||||||
dockerfile := `
|
dockerfile := `
|
||||||
|
@ -3607,6 +3665,7 @@ func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) {
|
||||||
ctx, err := fakeContext(dockerfile, map[string]string{
|
ctx, err := fakeContext(dockerfile, map[string]string{
|
||||||
"Dockerfile": "FROM scratch",
|
"Dockerfile": "FROM scratch",
|
||||||
"Makefile": "all:",
|
"Makefile": "all:",
|
||||||
|
".gitignore": "",
|
||||||
".dockerignore": ".*\n",
|
".dockerignore": ".*\n",
|
||||||
})
|
})
|
||||||
defer ctx.Close()
|
defer ctx.Close()
|
||||||
|
|
|
@ -391,6 +391,13 @@ func Tar(path string, compression Compression) (io.ReadCloser, error) {
|
||||||
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
|
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
|
||||||
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
|
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
|
||||||
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
||||||
|
|
||||||
|
patterns, patDirs, exceptions, err := fileutils.CleanPatterns(options.ExcludePatterns)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
pipeReader, pipeWriter := io.Pipe()
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
|
|
||||||
compressWriter, err := CompressStream(pipeWriter, options.Compression)
|
compressWriter, err := CompressStream(pipeWriter, options.Compression)
|
||||||
|
@ -441,7 +448,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
||||||
// is asking for that file no matter what - which is true
|
// is asking for that file no matter what - which is true
|
||||||
// for some files, like .dockerignore and Dockerfile (sometimes)
|
// for some files, like .dockerignore and Dockerfile (sometimes)
|
||||||
if include != relFilePath {
|
if include != relFilePath {
|
||||||
skip, err = fileutils.Matches(relFilePath, options.ExcludePatterns)
|
skip, err = fileutils.OptimizedMatches(relFilePath, patterns, patDirs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Error matching %s", relFilePath, err)
|
logrus.Debugf("Error matching %s", relFilePath, err)
|
||||||
return err
|
return err
|
||||||
|
@ -449,7 +456,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if skip {
|
if skip {
|
||||||
if f.IsDir() {
|
if !exceptions && f.IsDir() {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,33 +1,120 @@
|
||||||
package fileutils
|
package fileutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Matches returns true if relFilePath matches any of the patterns
|
func Exclusion(pattern string) bool {
|
||||||
func Matches(relFilePath string, patterns []string) (bool, error) {
|
return pattern[0] == '!'
|
||||||
for _, exclude := range patterns {
|
}
|
||||||
matched, err := filepath.Match(exclude, relFilePath)
|
|
||||||
if err != nil {
|
func Empty(pattern string) bool {
|
||||||
logrus.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude)
|
return pattern == ""
|
||||||
return false, err
|
}
|
||||||
}
|
|
||||||
if matched {
|
// Cleanpatterns takes a slice of patterns returns a new
|
||||||
if filepath.Clean(relFilePath) == "." {
|
// slice of patterns cleaned with filepath.Clean, stripped
|
||||||
logrus.Errorf("Can't exclude whole path, excluding pattern: %s", exclude)
|
// of any empty patterns and lets the caller know whether the
|
||||||
|
// slice contains any exception patterns (prefixed with !).
|
||||||
|
func CleanPatterns(patterns []string) ([]string, [][]string, bool, error) {
|
||||||
|
// Loop over exclusion patterns and:
|
||||||
|
// 1. Clean them up.
|
||||||
|
// 2. Indicate whether we are dealing with any exception rules.
|
||||||
|
// 3. Error if we see a single exclusion marker on it's own (!).
|
||||||
|
cleanedPatterns := []string{}
|
||||||
|
patternDirs := [][]string{}
|
||||||
|
exceptions := false
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
// Eliminate leading and trailing whitespace.
|
||||||
|
pattern = strings.TrimSpace(pattern)
|
||||||
|
if Empty(pattern) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logrus.Debugf("Skipping excluded path: %s", relFilePath)
|
if Exclusion(pattern) {
|
||||||
return true, nil
|
if len(pattern) == 1 {
|
||||||
|
logrus.Errorf("Illegal exclusion pattern: %s", pattern)
|
||||||
|
return nil, nil, false, errors.New("Illegal exclusion pattern: !")
|
||||||
}
|
}
|
||||||
|
exceptions = true
|
||||||
}
|
}
|
||||||
|
pattern = filepath.Clean(pattern)
|
||||||
|
cleanedPatterns = append(cleanedPatterns, pattern)
|
||||||
|
if Exclusion(pattern) {
|
||||||
|
pattern = pattern[1:]
|
||||||
|
}
|
||||||
|
patternDirs = append(patternDirs, strings.Split(pattern, "/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanedPatterns, patternDirs, exceptions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches returns true if file matches any of the patterns
|
||||||
|
// and isn't excluded by any of the subsequent patterns.
|
||||||
|
func Matches(file string, patterns []string) (bool, error) {
|
||||||
|
file = filepath.Clean(file)
|
||||||
|
|
||||||
|
if file == "." {
|
||||||
|
// Don't let them exclude everything, kind of silly.
|
||||||
return false, nil
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
patterns, patDirs, _, err := CleanPatterns(patterns)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return OptimizedMatches(file, patterns, patDirs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches is basically the same as fileutils.Matches() but optimized for archive.go.
|
||||||
|
// It will assume that the inputs have been preprocessed and therefore the function
|
||||||
|
// doen't need to do as much error checking and clean-up. This was done to avoid
|
||||||
|
// repeating these steps on each file being checked during the archive process.
|
||||||
|
// The more generic fileutils.Matches() can't make these assumptions.
|
||||||
|
func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) {
|
||||||
|
matched := false
|
||||||
|
parentPath := filepath.Dir(file)
|
||||||
|
parentPathDirs := strings.Split(parentPath, "/")
|
||||||
|
|
||||||
|
for i, pattern := range patterns {
|
||||||
|
negative := false
|
||||||
|
|
||||||
|
if Exclusion(pattern) {
|
||||||
|
negative = true
|
||||||
|
pattern = pattern[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
match, err := filepath.Match(pattern, file)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Error matching: %s (pattern: %s)", file, pattern)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match && parentPath != "." {
|
||||||
|
// Check to see if the pattern matches one of our parent dirs.
|
||||||
|
if len(patDirs[i]) <= len(parentPathDirs) {
|
||||||
|
match, _ = filepath.Match(strings.Join(patDirs[i], "/"),
|
||||||
|
strings.Join(parentPathDirs[:len(patDirs[i])], "/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
matched = !negative
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
logrus.Debugf("Skipping excluded path: %s", file)
|
||||||
|
}
|
||||||
|
return matched, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CopyFile(src, dst string) (int64, error) {
|
func CopyFile(src, dst string) (int64, error) {
|
||||||
|
|
|
@ -79,3 +79,142 @@ func TestReadSymlinkedDirectoryToFile(t *testing.T) {
|
||||||
t.Errorf("failed to remove symlink: %s", err)
|
t.Errorf("failed to remove symlink: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWildcardMatches(t *testing.T) {
|
||||||
|
match, _ := Matches("fileutils.go", []string{"*"})
|
||||||
|
if match != true {
|
||||||
|
t.Errorf("failed to get a wildcard match, got %v", match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple pattern match should return true.
|
||||||
|
func TestPatternMatches(t *testing.T) {
|
||||||
|
match, _ := Matches("fileutils.go", []string{"*.go"})
|
||||||
|
if match != true {
|
||||||
|
t.Errorf("failed to get a match, got %v", match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An exclusion followed by an inclusion should return true.
|
||||||
|
func TestExclusionPatternMatchesPatternBefore(t *testing.T) {
|
||||||
|
match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"})
|
||||||
|
if match != true {
|
||||||
|
t.Errorf("failed to get true match on exclusion pattern, got %v", match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A folder pattern followed by an exception should return false.
|
||||||
|
func TestPatternMatchesFolderExclusions(t *testing.T) {
|
||||||
|
match, _ := Matches("docs/README.md", []string{"docs", "!docs/README.md"})
|
||||||
|
if match != false {
|
||||||
|
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A folder pattern followed by an exception should return false.
|
||||||
|
func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) {
|
||||||
|
match, _ := Matches("docs/README.md", []string{"docs/", "!docs/README.md"})
|
||||||
|
if match != false {
|
||||||
|
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A folder pattern followed by an exception should return false.
|
||||||
|
func TestPatternMatchesFolderWildcardExclusions(t *testing.T) {
|
||||||
|
match, _ := Matches("docs/README.md", []string{"docs/*", "!docs/README.md"})
|
||||||
|
if match != false {
|
||||||
|
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A pattern followed by an exclusion should return false.
|
||||||
|
func TestExclusionPatternMatchesPatternAfter(t *testing.T) {
|
||||||
|
match, _ := Matches("fileutils.go", []string{"*.go", "!fileutils.go"})
|
||||||
|
if match != false {
|
||||||
|
t.Errorf("failed to get false match on exclusion pattern, got %v", match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A filename evaluating to . should return false.
|
||||||
|
func TestExclusionPatternMatchesWholeDirectory(t *testing.T) {
|
||||||
|
match, _ := Matches(".", []string{"*.go"})
|
||||||
|
if match != false {
|
||||||
|
t.Errorf("failed to get false match on ., got %v", match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single ! pattern should return an error.
|
||||||
|
func TestSingleExclamationError(t *testing.T) {
|
||||||
|
_, err := Matches("fileutils.go", []string{"!"})
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed to get an error for a single exclamation point, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A string preceded with a ! should return true from Exclusion.
|
||||||
|
func TestExclusion(t *testing.T) {
|
||||||
|
exclusion := Exclusion("!")
|
||||||
|
if !exclusion {
|
||||||
|
t.Errorf("failed to get true for a single !, got %v", exclusion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An empty string should return true from Empty.
|
||||||
|
func TestEmpty(t *testing.T) {
|
||||||
|
empty := Empty("")
|
||||||
|
if !empty {
|
||||||
|
t.Errorf("failed to get true for an empty string, got %v", empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanPatterns(t *testing.T) {
|
||||||
|
cleaned, _, _, _ := CleanPatterns([]string{"docs", "config"})
|
||||||
|
if len(cleaned) != 2 {
|
||||||
|
t.Errorf("expected 2 element slice, got %v", len(cleaned))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanPatternsStripEmptyPatterns(t *testing.T) {
|
||||||
|
cleaned, _, _, _ := CleanPatterns([]string{"docs", "config", ""})
|
||||||
|
if len(cleaned) != 2 {
|
||||||
|
t.Errorf("expected 2 element slice, got %v", len(cleaned))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanPatternsExceptionFlag(t *testing.T) {
|
||||||
|
_, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md"})
|
||||||
|
if !exceptions {
|
||||||
|
t.Errorf("expected exceptions to be true, got %v", exceptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanPatternsLeadingSpaceTrimmed(t *testing.T) {
|
||||||
|
_, _, exceptions, _ := CleanPatterns([]string{"docs", " !docs/README.md"})
|
||||||
|
if !exceptions {
|
||||||
|
t.Errorf("expected exceptions to be true, got %v", exceptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanPatternsTrailingSpaceTrimmed(t *testing.T) {
|
||||||
|
_, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md "})
|
||||||
|
if !exceptions {
|
||||||
|
t.Errorf("expected exceptions to be true, got %v", exceptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanPatternsErrorSingleException(t *testing.T) {
|
||||||
|
_, _, _, err := CleanPatterns([]string{"!"})
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error on single exclamation point, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanPatternsFolderSplit(t *testing.T) {
|
||||||
|
_, dirs, _, _ := CleanPatterns([]string{"docs/config/CONFIG.md"})
|
||||||
|
if dirs[0][0] != "docs" {
|
||||||
|
t.Errorf("expected first element in dirs slice to be docs, got %v", dirs[0][1])
|
||||||
|
}
|
||||||
|
if dirs[0][1] != "config" {
|
||||||
|
t.Errorf("expected first element in dirs slice to be config, got %v", dirs[0][1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue