Clean up exclude pattern handling

This commit is contained in:
Kovid Goyal 2023-02-26 09:12:12 +05:30
parent 5cc3d3cbfe
commit 6de77ce987
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 57 additions and 32 deletions

View File

@ -62,6 +62,9 @@ Detailed list of changes
- macOS: Fix the maximized window not taking up full space when the title bar is hidden or when :opt:`resize_in_steps` is configured (:iss:`6021`) - macOS: Fix the maximized window not taking up full space when the title bar is hidden or when :opt:`resize_in_steps` is configured (:iss:`6021`)
- ssh kitten: Change the syntax of glob patterns slightly to match common usage
elsewhere. Now the syntax is the same a "extendedglob" in most shells.
0.27.1 [2023-02-07] 0.27.1 [2023-02-07]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -38,10 +38,12 @@ type=list
A glob pattern. Files with names matching this pattern are excluded from being A glob pattern. Files with names matching this pattern are excluded from being
transferred. Useful when adding directories. Can transferred. Useful when adding directories. Can
be specified multiple times, if any of the patterns match the file will be be specified multiple times, if any of the patterns match the file will be
excluded. To exclude a directory use a pattern like :code:`**/directory_name/**`. excluded. If the pattern includes a :code:`/` then it will match against the full
Based on standard wildcards with the addition that ``/**/`` matches any number of directories path, not just the filename. In such patterns you can use :code:`/**/` to match zero
and patterns starting with a single :code:`*` (as opposed to two asterisks) match any filename prefix. or more directories. For example, to exclude a directory and everything under it use
See the :link:`detailed syntax <https://github.com/bmatcuk/doublestar#patterns>`. :code:`**/directory_name`.
See the :link:`detailed syntax <https://github.com/bmatcuk/doublestar#patterns>` for
how wildcards match.
--symlink-strategy --symlink-strategy
@ -52,7 +54,8 @@ the symlink, re-creating it on the remote machine. Setting this to :code:`resolv
will cause the symlink to be followed and its target used as the file/directory to copy. will cause the symlink to be followed and its target used as the file/directory to copy.
The value of :code:`keep-path` is the same as :code:`resolve` except that the remote The value of :code:`keep-path` is the same as :code:`resolve` except that the remote
file path is derived from the symlink's path instead of the path of the symlink's target. file path is derived from the symlink's path instead of the path of the symlink's target.
Note that this option does not apply to symlinks encountered while recursively copying directories. Note that this option does not apply to symlinks encountered while recursively copying directories,
those are always preserved.
''' '''

View File

@ -75,6 +75,8 @@ print(' '.join(map(str, buf)))'''), lines=13, cols=77)
os.makedirs(f'{local_home}/d1/d2/d3') os.makedirs(f'{local_home}/d1/d2/d3')
touch('d1/d2/x') touch('d1/d2/x')
touch('d1/d2/w.exclude') touch('d1/d2/w.exclude')
os.mkdir(f'{local_home}/d1/r')
touch('d1/r/noooo')
os.symlink('d2/x', f'{local_home}/d1/y') os.symlink('d2/x', f'{local_home}/d1/y')
os.symlink('simple-file', f'{local_home}/s1') os.symlink('simple-file', f'{local_home}/s1')
os.symlink('simple-file', f'{local_home}/s2') os.symlink('simple-file', f'{local_home}/s2')
@ -85,7 +87,7 @@ copy s1
copy --symlink-strategy=keep-path s2 copy --symlink-strategy=keep-path s2
copy --dest=a/sfa simple-file copy --dest=a/sfa simple-file
copy --glob g.* copy --glob g.*
copy --exclude **/w.* d1 copy --exclude **/w.* --exclude **/r d1
''' '''
self.check_bootstrap( self.check_bootstrap(
sh, remote_home, test_script='env; exit 0', SHELL_INTEGRATION_VALUE='', conf=conf, home=local_home, sh, remote_home, test_script='env; exit 0', SHELL_INTEGRATION_VALUE='', conf=conf, home=local_home,

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -230,7 +231,7 @@ type file_unique_id struct {
} }
func excluded(pattern, path string) bool { func excluded(pattern, path string) bool {
if strings.HasPrefix(pattern, "*") && !strings.HasPrefix(pattern, "**") { if !strings.ContainsRune(pattern, '/') {
path = filepath.Base(path) path = filepath.Base(path)
} }
if matched, err := doublestar.PathMatch(pattern, path); matched && err == nil { if matched, err := doublestar.PathMatch(pattern, path); matched && err == nil {
@ -239,13 +240,13 @@ func excluded(pattern, path string) bool {
return false return false
} }
func get_file_data(callback func(h *tar.Header, data []byte) error, seen map[file_unique_id]string, local_path, arcname string, exclude_patterns []string, recurse bool) error { func get_file_data(callback func(h *tar.Header, data []byte) error, seen map[file_unique_id]string, local_path, arcname string, exclude_patterns []string) error {
s, err := os.Lstat(local_path) s, err := os.Lstat(local_path)
if err != nil { if err != nil {
return err return err
} }
u, ok := s.Sys().(unix.Stat_t) u, ok := s.Sys().(unix.Stat_t)
cb := func(h *tar.Header, data []byte) error { cb := func(h *tar.Header, data []byte, arcname string) error {
h.Name = arcname h.Name = arcname
if h.Typeflag == tar.TypeDir { if h.Typeflag == tar.TypeDir {
h.Name = strings.TrimRight(h.Name, "/") + "/" h.Name = strings.TrimRight(h.Name, "/") + "/"
@ -270,41 +271,57 @@ func get_file_data(callback func(h *tar.Header, data []byte) error, seen map[fil
err = cb(&tar.Header{ err = cb(&tar.Header{
Typeflag: tar.TypeSymlink, Typeflag: tar.TypeSymlink,
Linkname: target, Linkname: target,
}, nil) }, nil, arcname)
if err != nil { if err != nil {
return err return err
} }
case fs.ModeDir: case fs.ModeDir:
err = cb(&tar.Header{Typeflag: tar.TypeDir}, nil) local_path = filepath.Clean(local_path)
type entry struct {
path, arcname string
}
stack := []entry{{local_path, arcname}}
for len(stack) > 0 {
x := stack[0]
stack = stack[1:]
entries, err := os.ReadDir(x.path)
if err != nil {
if x.path == local_path {
return err
}
continue
}
err = cb(&tar.Header{Typeflag: tar.TypeDir}, nil, x.arcname)
if err != nil { if err != nil {
return err return err
} }
if recurse { for _, e := range entries {
local_path = filepath.Clean(local_path) entry_path := filepath.Join(x.path, e.Name())
return filepath.WalkDir(local_path, func(path string, d fs.DirEntry, werr error) error { aname := path.Join(x.arcname, e.Name())
clean_path := filepath.Clean(path) ok := true
if clean_path == local_path {
return nil
}
for _, pat := range exclude_patterns { for _, pat := range exclude_patterns {
if excluded(pat, clean_path) { if excluded(pat, entry_path) {
return nil ok = false
break
} }
} }
if werr == nil { if !ok {
rel, err := filepath.Rel(local_path, path) continue
if err == nil { }
aname := filepath.Join(arcname, rel) if e.IsDir() {
return get_file_data(callback, seen, clean_path, aname, nil, false) stack = append(stack, entry{entry_path, aname})
} else {
err = get_file_data(callback, seen, entry_path, aname, exclude_patterns)
if err != nil {
return err
}
} }
} }
return nil
})
} }
case 0: // Regular file case 0: // Regular file
fid := file_unique_id{dev: u.Dev, inode: u.Ino} fid := file_unique_id{dev: u.Dev, inode: u.Ino}
if prev, ok := seen[fid]; ok { // Hard link if prev, ok := seen[fid]; ok { // Hard link
err = cb(&tar.Header{Typeflag: tar.TypeLink, Linkname: prev}, nil) err = cb(&tar.Header{Typeflag: tar.TypeLink, Linkname: prev}, nil, arcname)
if err != nil { if err != nil {
return err return err
} }
@ -314,7 +331,7 @@ func get_file_data(callback func(h *tar.Header, data []byte) error, seen map[fil
if err != nil { if err != nil {
return err return err
} }
err = cb(&tar.Header{Typeflag: tar.TypeReg}, data) err = cb(&tar.Header{Typeflag: tar.TypeReg}, data, arcname)
if err != nil { if err != nil {
return err return err
} }
@ -327,7 +344,7 @@ func (ci *CopyInstruction) get_file_data(callback func(h *tar.Header, data []byt
for _, folder_name := range []string{"__pycache__", ".DS_Store"} { for _, folder_name := range []string{"__pycache__", ".DS_Store"} {
ep = append(ep, "**/"+folder_name, "**/"+folder_name+"/**") ep = append(ep, "**/"+folder_name, "**/"+folder_name+"/**")
} }
return get_file_data(callback, seen, ci.local_path, ci.arcname, ep, true) return get_file_data(callback, seen, ci.local_path, ci.arcname, ep)
} }
type ConfigSet struct { type ConfigSet struct {

View File

@ -275,7 +275,7 @@ func make_tarfile(cd *connection_data, get_local_env func(string) (string, bool)
return return
} }
for _, ci := range cd.host_opts.Copy { for _, ci := range cd.host_opts.Copy {
err = get_file_data(add, seen, ci.local_path, ci.arcname, ci.exclude_patterns, true) err = ci.get_file_data(add, seen)
if err != nil { if err != nil {
return nil, err return nil, err
} }