ronantakizawa/codereview-bench huggingface dataset:
Repository: rclone/rclone
File: backend/seafile/webapi.go (Go)

Code before:
		case *fs.SeekOption:
			start = x.Offset
			partialContent = true
		case *fs.RangeOption:
			if x.Start >= 0 {
				start = x.Start
				if x.End > 0 && x.End < size {
					end = x.End + 1
				}
			} else {
				// {-1, 20} should load the last 20 characters [len-20:len]
				start = size - x.End
			}
			partialContent = true
		default:
			if option.Mandatory() {
				fs.Logf(nil, "Unsupported mandatory option: %v", option)
			}
		}
	}
	// Build the http request
	opts := rest.Opts{
		Method:  "GET",
		Options: options,
	}
	parsedURL, _ := url.Parse(downloadLink)
	if parsedURL.IsAbs() {
		opts.RootURL = downloadLink
	} else {
		opts.Path = downloadLink
	}
	var resp *http.Response
	var err error
	err = f.pacer.Call(func() (bool, error) {
		resp, err = f.srv.Call(ctx, &opts)
		return f.shouldRetry(ctx, resp, err)
	})
	if err != nil {
		if resp != nil {
			if resp.StatusCode == 404 {
				return nil, fmt.Errorf("file not found '%s'", downloadLink)
			}
		}
		return nil, err
	}
	// Non-encrypted libraries are accepting the HTTP Range header,
	// BUT encrypted libraries are simply ignoring it
	if partialContent && resp.StatusCode == 200 {
		// Partial content was requested through a Range header, but a full content was sent instead
		rangeDownloadNotice.Do(func() {
			fs.Logf(nil, "%s ignored our request of partial content. This is probably because encrypted libraries are not accepting range requests. Loading this file might be slow!", f.String())

Review comment (suggestion):
This needs to check the error returned by `url.Parse` if err !=nil then `parsedURL` won't be valid.

Code after:
		case *fs.SeekOption:
			start = x.Offset
			partialContent = true
		case *fs.RangeOption:
			if x.Start >= 0 {
				start = x.Start
				if x.End > 0 && x.End < size {
					end = x.End + 1
				}
			} else {
				// {-1, 20} should load the last 20 characters [len-20:len]
				start = size - x.End
			}
			partialContent = true
		default:
			if option.Mandatory() {
				fs.Logf(nil, "Unsupported mandatory option: %v", option)
			}
		}
	}
	// Build the http request
	opts := rest.Opts{
		Method:  "GET",
		Options: options,
	}
	parsedURL, err := url.Parse(downloadLink)
	if err != nil {
		return nil, fmt.Errorf("failed to parse download url: %w", err)
	}
	if parsedURL.IsAbs() {
		opts.RootURL = downloadLink
	} else {
		opts.Path = downloadLink
	}
	var resp *http.Response
	err = f.pacer.Call(func() (bool, error) {
		resp, err = f.srv.Call(ctx, &opts)
		return f.shouldRetry(ctx, resp, err)
	})
	if err != nil {
		if resp != nil {
			if resp.StatusCode == 404 {
				return nil, fmt.Errorf("file not found '%s'", downloadLink)
			}
		}
		return nil, err
	}
	// Non-encrypted libraries are accepting the HTTP Range header,
	// BUT encrypted libraries are simply ignoring it
	if partialContent && resp.StatusCode == 200 {
		// Partial content was requested through a Range header, but a full content was sent instead
Repository: rclone/rclone
File: cmd/bisync/queue.go (Go)

Code before:
				} else {
					direrr = fmt.Errorf("invalid operation. Expected 'make' or 'remove', received '%q'", operation)
				}

				if direrr != nil {
					fs.Debugf(nil, "Error syncing directory: %v", direrr)
				} else {
					*results = append(*results, rSrc, rDst)
				}
			}
		}
	}
}

func (b *bisyncRun) saveQueue(files bilib.Names, jobName string) error {
	if !b.opt.SaveQueues {
		return nil
	}
	queueFile := fmt.Sprintf("%s.%s.que", b.basePath, jobName)
	return files.Save(queueFile)
}

func naptime(totalWait time.Duration) {
	expireTime := time.Now().Add(totalWait)
	fs.Logf(nil, "will retry in %v at %v", totalWait, expireTime.Format("2006-01-02 15:04:05 MST"))
	for i := 0; time.Until(expireTime) > 0; i++ {
		if i > 0 && i%10 == 0 {
			fs.Infof(nil, Color(terminal.Dim, "retrying in %v..."), time.Until(expireTime).Round(1*time.Second))
		} else {
			fs.Debugf(nil, Color(terminal.Dim, "retrying in %v..."), time.Until(expireTime).Round(1*time.Second))
		}
		time.Sleep(1 * time.Second)
	}
}


Review comment (suggestion):
This is cute!

The implementation we use in cmd is here :-)

https://github.com/rclone/rclone/blob/91b54aafccc3aed01a5389864e8ef3902286b406/cmd/cmd.go#L293

That made me think though - could `bisync` use the `cmd.Run` function which does `--retries` and `--retries-sleep` for the other commands? That would make it more consistent. You could improve the function to make it work better with this nice function

https://github.com/rclone/rclone/blob/91b54aafccc3aed01a5389864e8ef3902286b406/cmd/cmd.go#L243



Code after:
					rDst.Sigil = operations.MissingOnDst
					rSrc.Src = s
					rDst.Src = s
				} else {
					direrr = fmt.Errorf("invalid operation. Expected 'make' or 'remove', received '%q'", operation)
				}

				if direrr != nil {
					fs.Debugf(nil, "Error syncing directory: %v", direrr)
				} else {
					*results = append(*results, rSrc, rDst)
				}
			}
		}
	}
}

func (b *bisyncRun) saveQueue(files bilib.Names, jobName string) error {
	if !b.opt.SaveQueues {
		return nil
	}
	queueFile := fmt.Sprintf("%s.%s.que", b.basePath, jobName)
	return files.Save(queueFile)
}

func naptime(totalWait time.Duration) {
	expireTime := time.Now().Add(totalWait)
	fs.Logf(nil, "will retry in %v at %v", totalWait, expireTime.Format("2006-01-02 15:04:05 MST"))
	for i := 0; time.Until(expireTime) > 0; i++ {
		if i > 0 && i%10 == 0 {
			fs.Infof(nil, Color(terminal.Dim, "retrying in %v..."), time.Until(expireTime).Round(1*time.Second))
		} else {
			fs.Debugf(nil, Color(terminal.Dim, "retrying in %v..."), time.Until(expireTime).Round(1*time.Second))
		}
		time.Sleep(1 * time.Second)
	}
}
Repository: rclone/rclone
File: vfs/dir.go (Go)

Code before:
// readDir forces a refresh of the directory
func (d *Dir) readDir() error {
	d.mu.Lock()
	defer d.mu.Unlock()
	d.read = time.Time{}
	return d._readDir()
}

var ciLock sync.Mutex

// stat a single item in the directory
//
// returns ENOENT if not found.
// returns a custom error if directory on a case-insensitive file system
// contains files with names that differ only by case.
func (d *Dir) stat(leaf string) (Node, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	err := d._readDir()
	if err != nil {
		return nil, err
	}
	item, ok := d.items[leaf]

	ctx := context.TODO()
	ci := fs.GetConfig(ctx)
	ciLock.Lock()
	defer ciLock.Unlock()
	if d.vfs.Opt.CaseInsensitive {
		ci.IgnoreCaseSync = true
	}
	if !ok && (d.vfs.Opt.CaseInsensitive || ci.IgnoreCaseSync || !ci.NoUnicodeNormalization) {
		leafNormalized := operations.ApplyTransforms(ctx, leaf) // this handles both case and unicode normalization
		for name, node := range d.items {
			if operations.ApplyTransforms(ctx, name) == leafNormalized {
				if ok {
					// duplicate normalized match is an error
					return nil, fmt.Errorf("duplicate filename %q detected with case/unicode normalization settings", leaf)
				}
				// found a normalized match
				ok = true
				item = node
			}
		}
	}

	if !ok {
		return nil, ENOENT
	}
	return item, nil
}

Review comment (suggestion):
I think you probably meant `ci, ctx := fs.AddConfig(ctx)` here, then you can get rid of the `cilock` mutex. You are setting the global config and not resetting it which may/may not be what you intended.

Also can you put this context twiddling inside the `if !ok && (d.vfs.Opt.CaseInsensitiv...` then it doesn't need to be done for backends which don't need it.

Code after:

// stat a single item in the directory
//
// returns ENOENT if not found.
// returns a custom error if directory on a case-insensitive file system
// contains files with names that differ only by case.
func (d *Dir) stat(leaf string) (Node, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	err := d._readDir()
	if err != nil {
		return nil, err
	}
	item, ok := d.items[leaf]

	ci := fs.GetConfig(context.TODO())
	normUnicode := !ci.NoUnicodeNormalization
	normCase := ci.IgnoreCaseSync || d.vfs.Opt.CaseInsensitive
	if !ok && (normUnicode || normCase) {
		leafNormalized := operations.ToNormal(leaf, normUnicode, normCase) // this handles both case and unicode normalization
		for name, node := range d.items {
			if operations.ToNormal(name, normUnicode, normCase) == leafNormalized {
				if ok {
					// duplicate normalized match is an error
					return nil, fmt.Errorf("duplicate filename %q detected with case/unicode normalization settings", leaf)
				}
				// found a normalized match
				ok = true
				item = node
			}
		}
	}

	if !ok {
		return nil, ENOENT
	}
	return item, nil
}

// Check to see if a directory is empty
func (d *Dir) isEmpty() (bool, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	err := d._readDir()
	if err != nil {
		return false, err
	}
	return len(d.items) == 0, nil
}

// ModTime returns the modification time of the directory
Repository: rclone/rclone
File: vfs/dir.go (Go)

Code before:
	return nil
}

// readDir forces a refresh of the directory
func (d *Dir) readDir() error {
	d.mu.Lock()
	defer d.mu.Unlock()
	d.read = time.Time{}
	return d._readDir()
}

// stat a single item in the directory
//
// returns ENOENT if not found.
// returns a custom error if directory on a case-insensitive file system
// contains files with names that differ only by case.
func (d *Dir) stat(leaf string) (Node, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	err := d._readDir()
	if err != nil {
		return nil, err
	}
	item, ok := d.items[leaf]

	_, ci := fs.AddConfig(context.TODO())
	normUnicode := !ci.NoUnicodeNormalization
	normCase := ci.IgnoreCaseSync || d.vfs.Opt.CaseInsensitive
	if !ok && (normUnicode || normCase) {
		leafNormalized := operations.ToNormal(leaf, normUnicode, normCase) // this handles both case and unicode normalization
		for name, node := range d.items {
			if operations.ToNormal(name, normUnicode, normCase) == leafNormalized {
				if ok {
					// duplicate normalized match is an error
					return nil, fmt.Errorf("duplicate filename %q detected with case/unicode normalization settings", leaf)
				}
				// found a normalized match
				ok = true
				item = node
			}
		}
	}

	if !ok {
		return nil, ENOENT
	}
	return item, nil
}

// Check to see if a directory is empty
func (d *Dir) isEmpty() (bool, error) {

Review comment (suggestion):
Should this be changed back to `fs.GetConfig` now? (Does it matter?)

Code after:
	return nil
}

// readDir forces a refresh of the directory
func (d *Dir) readDir() error {
	d.mu.Lock()
	defer d.mu.Unlock()
	d.read = time.Time{}
	return d._readDir()
}

// stat a single item in the directory
//
// returns ENOENT if not found.
// returns a custom error if directory on a case-insensitive file system
// contains files with names that differ only by case.
func (d *Dir) stat(leaf string) (Node, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	err := d._readDir()
	if err != nil {
		return nil, err
	}
	item, ok := d.items[leaf]

	ci := fs.GetConfig(context.TODO())
	normUnicode := !ci.NoUnicodeNormalization
	normCase := ci.IgnoreCaseSync || d.vfs.Opt.CaseInsensitive
	if !ok && (normUnicode || normCase) {
		leafNormalized := operations.ToNormal(leaf, normUnicode, normCase) // this handles both case and unicode normalization
		for name, node := range d.items {
			if operations.ToNormal(name, normUnicode, normCase) == leafNormalized {
				if ok {
					// duplicate normalized match is an error
					return nil, fmt.Errorf("duplicate filename %q detected with case/unicode normalization settings", leaf)
				}
				// found a normalized match
				ok = true
				item = node
			}
		}
	}

	if !ok {
		return nil, ENOENT
	}
	return item, nil
}

// Check to see if a directory is empty
func (d *Dir) isEmpty() (bool, error) {
Repository: rclone/rclone
File: cmd/serve/nfs/handler.go (Go)

Code before:
	}
	return nil
}

// FSStat provides information about a filesystem.
func (h *BackendAuthHandler) FSStat(ctx context.Context, f billy.Filesystem, s *nfs.FSStat) error {
	total, _, free := h.vfs.Statfs()
	s.TotalSize = uint64(total)
	s.FreeSize = uint64(free)
	s.AvailableSize = uint64(free)
	return nil
}

// ToHandle handled by CachingHandler
func (h *BackendAuthHandler) ToHandle(f billy.Filesystem, s []string) []byte {
	return h.cache.ToHandle(f, s)
}

// FromHandle handled by CachingHandler
func (h *BackendAuthHandler) FromHandle(b []byte) (billy.Filesystem, []string, error) {
	return h.cache.FromHandle(b)
}

// HandleLimit handled by cachingHandler
func (h *BackendAuthHandler) HandleLimit() int {
	if h.vfs.Opt.CacheMaxSize < 0 {
		return 1000000
	}
	if h.vfs.Opt.CacheMaxSize <= 5 {
		return 5
	}
	return int(h.vfs.Opt.CacheMaxSize)
}

// InvalidateHandle is called on removes or renames
func (h *BackendAuthHandler) InvalidateHandle(billy.Filesystem, []byte) error {
	return nil
}

func newHandler(vfs *vfs.VFS) nfs.Handler {
	handler := NewBackendAuthHandler(vfs)
	nfs.SetLogger(&LogIntercepter{Level: nfs.DebugLevel})
	return handler
}

func cacheHelper(handler nfs.Handler, limit int) nfs.Handler {
	cacheHelper := nfshelper.NewCachingHandler(handler, limit)
	return cacheHelper
}

// LogIntercepter intercepts noisy go-nfs logs and reroutes them to DEBUG

Review comment (performance):
I don't like the re-using of --vfs-cache-max-size here as the parameter...

I'd suggest making an extra flag for `cmd/serve/nfs` - it has one already add another?

Code after:
// Change provides an interface for updating file attributes.
func (h *BackendAuthHandler) Change(fs billy.Filesystem) billy.Change {
	if c, ok := fs.(billy.Change); ok {
		return c
	}
	return nil
}

// FSStat provides information about a filesystem.
func (h *BackendAuthHandler) FSStat(ctx context.Context, f billy.Filesystem, s *nfs.FSStat) error {
	total, _, free := h.vfs.Statfs()
	s.TotalSize = uint64(total)
	s.FreeSize = uint64(free)
	s.AvailableSize = uint64(free)
	return nil
}

// ToHandle handled by CachingHandler
func (h *BackendAuthHandler) ToHandle(f billy.Filesystem, s []string) []byte {
	return h.cache.ToHandle(f, s)
}

// FromHandle handled by CachingHandler
func (h *BackendAuthHandler) FromHandle(b []byte) (billy.Filesystem, []string, error) {
	return h.cache.FromHandle(b)
}

// HandleLimit handled by cachingHandler
func (h *BackendAuthHandler) HandleLimit() int {
	return h.opt.HandleLimit
}

// InvalidateHandle is called on removes or renames
func (h *BackendAuthHandler) InvalidateHandle(billy.Filesystem, []byte) error {
	return nil
}

func newHandler(vfs *vfs.VFS, opt *Options) nfs.Handler {
	handler := NewBackendAuthHandler(vfs, opt)
	nfs.SetLogger(&LogIntercepter{Level: nfs.DebugLevel})
	return handler
}

func cacheHelper(handler nfs.Handler, limit int) nfs.Handler {
	cacheHelper := nfshelper.NewCachingHandler(handler, limit)
	return cacheHelper
}

// Limit overrides the --nfs-cache-handle-limit value if out-of-range
func (o *Options) Limit() int {
	if o.HandleLimit < 0 {
Repository: rclone/rclone
File: cmd/bisync/log.go (Go)

Code before:

	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/lib/encoder"
	"github.com/rclone/rclone/lib/terminal"
)

func (b *bisyncRun) indentf(tag, file, format string, args ...interface{}) {
	b.indent(tag, file, fmt.Sprintf(format, args...))
}

func (b *bisyncRun) indent(tag, file, msg string) {
	logf := fs.Infof
	switch {
	case tag == "ERROR":
		tag = ""
		logf = fs.Errorf
	case tag == "INFO":
		tag = ""
	case strings.HasPrefix(tag, "!"):
		tag = tag[1:]
		logf = fs.Logf
	}

	if b.opt.DryRun {
		logf = fs.Logf
		msg += " in dry-run mode"
	}

	if tag == "Path1" {
		tag = Color(terminal.CyanFg, "Path1")
	} else if tag == "Path2" {
		tag = Color(terminal.CyanFg, "Path2")
	} else {
		tag = Color(terminal.BlueFg, tag)
	}

	msg = Color(terminal.MagentaFg, msg)
	msg = strings.Replace(msg, "Queue copy to", Color(terminal.GreenFg, "Queue copy to Path2"), -1)
	msg = strings.Replace(msg, "Queue delete", Color(terminal.RedFg, "Queue delete from Path1"), -1)
	file = Color(terminal.CyanFg, escapePath(file, false))

	if b.opt.DryRun && (strings.Contains(msg, "Queue copy to Path2") || strings.Contains(msg, "Queue delete from Path1")) {
		logf(nil, "- %-18s%-43s - %s", tag, msg, file)
	} else {
		logf(nil, "- %-18s%-43s - %s", tag, msg, file)
	}
}

// escapePath will escape control characters in path.
// It won't quote just due to backslashes on Windows.
func escapePath(path string, forceQuotes bool) string {

Review comment (refactor):
The `logf` part is great! I'm not sure we need the `msg` part. I think it's already clear we're in dry run mode from other lines, like `file.txt: Skipped copy as --dry-run is set`

It would also read a little awkwardly in some places, like:

```
NOTICE: - Path2    Do queued copies to in dry-run mode - Path1
```

Code after:

	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/lib/encoder"
	"github.com/rclone/rclone/lib/terminal"
)

func (b *bisyncRun) indentf(tag, file, format string, args ...interface{}) {
	b.indent(tag, file, fmt.Sprintf(format, args...))
}

func (b *bisyncRun) indent(tag, file, msg string) {
	logf := fs.Infof
	switch {
	case tag == "ERROR":
		tag = ""
		logf = fs.Errorf
	case tag == "INFO":
		tag = ""
	case strings.HasPrefix(tag, "!"):
		tag = tag[1:]
		logf = fs.Logf
	}

	if b.opt.DryRun {
		logf = fs.Logf
	}

	if tag == "Path1" {
		tag = Color(terminal.CyanFg, "Path1")
	} else {
		tag = Color(terminal.BlueFg, tag)
	}
	msg = Color(terminal.MagentaFg, msg)
	msg = strings.Replace(msg, "Queue copy to", Color(terminal.GreenFg, "Queue copy to"), -1)
	msg = strings.Replace(msg, "Queue delete", Color(terminal.RedFg, "Queue delete"), -1)
	file = Color(terminal.CyanFg, escapePath(file, false))
	logf(nil, "- %-18s%-43s - %s", tag, msg, file)
}

// escapePath will escape control characters in path.
// It won't quote just due to backslashes on Windows.
func escapePath(path string, forceQuotes bool) string {
	path = encode(path)
	test := path
	if runtime.GOOS == "windows" {
		test = strings.ReplaceAll(path, "\\", "/")
	}
	if strconv.Quote(test) != `"`+test+`"` {
		return strconv.Quote(path)
	}
	if forceQuotes {
Repository: rclone/rclone
File: cmd/bisync/log.go (Go)

Code before:

func (b *bisyncRun) indentf(tag, file, format string, args ...interface{}) {
	b.indent(tag, file, fmt.Sprintf(format, args...))
}

func (b *bisyncRun) indent(tag, file, msg string) {
	logf := fs.Infof
	switch {
	case tag == "ERROR":
		tag = ""
		logf = fs.Errorf
	case tag == "INFO":
		tag = ""
	case strings.HasPrefix(tag, "!"):
		tag = tag[1:]
		logf = fs.Logf
	}

	if b.opt.DryRun {
		logf = fs.Logf
		msg += " in dry-run mode"
	}

	if tag == "Path1" {
		tag = Color(terminal.CyanFg, "Path1")
	} else if tag == "Path2" {
		tag = Color(terminal.CyanFg, "Path2")
	} else {
		tag = Color(terminal.BlueFg, tag)
	}

	msg = Color(terminal.MagentaFg, msg)
	msg = strings.Replace(msg, "Queue copy to", Color(terminal.GreenFg, "Queue copy to Path2"), -1)
	msg = strings.Replace(msg, "Queue delete", Color(terminal.RedFg, "Queue delete from Path1"), -1)
	file = Color(terminal.CyanFg, escapePath(file, false))

	if b.opt.DryRun && (strings.Contains(msg, "Queue copy to Path2") || strings.Contains(msg, "Queue delete from Path1")) {
		logf(nil, "- %-18s%-43s - %s", tag, msg, file)
	} else {
		logf(nil, "- %-18s%-43s - %s", tag, msg, file)
	}
}

// escapePath will escape control characters in path.
// It won't quote just due to backslashes on Windows.
func escapePath(path string, forceQuotes bool) string {
	path = encode(path)
	test := path
	if runtime.GOOS == "windows" {
		test = strings.ReplaceAll(path, "\\", "/")
	}

Review comment (suggestion):
The idea here was to use different colors for Path1 and Path2, to make it easier to visually differentiate in the terminal. So I would suggest either removing this change or using a different color.

Code after:

func (b *bisyncRun) indentf(tag, file, format string, args ...interface{}) {
	b.indent(tag, file, fmt.Sprintf(format, args...))
}

func (b *bisyncRun) indent(tag, file, msg string) {
	logf := fs.Infof
	switch {
	case tag == "ERROR":
		tag = ""
		logf = fs.Errorf
	case tag == "INFO":
		tag = ""
	case strings.HasPrefix(tag, "!"):
		tag = tag[1:]
		logf = fs.Logf
	}

	if b.opt.DryRun {
		logf = fs.Logf
	}

	if tag == "Path1" {
		tag = Color(terminal.CyanFg, "Path1")
	} else {
		tag = Color(terminal.BlueFg, tag)
	}
	msg = Color(terminal.MagentaFg, msg)
	msg = strings.Replace(msg, "Queue copy to", Color(terminal.GreenFg, "Queue copy to"), -1)
	msg = strings.Replace(msg, "Queue delete", Color(terminal.RedFg, "Queue delete"), -1)
	file = Color(terminal.CyanFg, escapePath(file, false))
	logf(nil, "- %-18s%-43s - %s", tag, msg, file)
}

// escapePath will escape control characters in path.
// It won't quote just due to backslashes on Windows.
func escapePath(path string, forceQuotes bool) string {
	path = encode(path)
	test := path
	if runtime.GOOS == "windows" {
		test = strings.ReplaceAll(path, "\\", "/")
	}
	if strconv.Quote(test) != `"`+test+`"` {
		return strconv.Quote(path)
	}
	if forceQuotes {
		return `"` + path + `"`
	}
	return path
}
Repository: rclone/rclone
File: cmd/bisync/log.go (Go)

Code before:
	switch {
	case tag == "ERROR":
		tag = ""
		logf = fs.Errorf
	case tag == "INFO":
		tag = ""
	case strings.HasPrefix(tag, "!"):
		tag = tag[1:]
		logf = fs.Logf
	}

	if b.opt.DryRun {
		logf = fs.Logf
		msg += " in dry-run mode"
	}

	if tag == "Path1" {
		tag = Color(terminal.CyanFg, "Path1")
	} else if tag == "Path2" {
		tag = Color(terminal.CyanFg, "Path2")
	} else {
		tag = Color(terminal.BlueFg, tag)
	}

	msg = Color(terminal.MagentaFg, msg)
	msg = strings.Replace(msg, "Queue copy to", Color(terminal.GreenFg, "Queue copy to Path2"), -1)
	msg = strings.Replace(msg, "Queue delete", Color(terminal.RedFg, "Queue delete from Path1"), -1)
	file = Color(terminal.CyanFg, escapePath(file, false))

	if b.opt.DryRun && (strings.Contains(msg, "Queue copy to Path2") || strings.Contains(msg, "Queue delete from Path1")) {
		logf(nil, "- %-18s%-43s - %s", tag, msg, file)
	} else {
		logf(nil, "- %-18s%-43s - %s", tag, msg, file)
	}
}

// escapePath will escape control characters in path.
// It won't quote just due to backslashes on Windows.
func escapePath(path string, forceQuotes bool) string {
	path = encode(path)
	test := path
	if runtime.GOOS == "windows" {
		test = strings.ReplaceAll(path, "\\", "/")
	}
	if strconv.Quote(test) != `"`+test+`"` {
		return strconv.Quote(path)
	}
	if forceQuotes {
		return `"` + path + `"`
	}
	return path

Review comment (refactor):
This part needs another look, as it could be either direction here (1 to 2 or 2 to 1). So, as it stands now it would result in confusing lines like:

```
NOTICE: - Path2    Queue copy to Path2 Path1 in dry-run mode - file.txt
```

Here's an example of how this function is called:
https://github.com/rclone/rclone/blob/6e28edeb9aa1c18e27faf641729b2cbc259fe13a/cmd/bisync/deltas.go#L479

Code after:
	switch {
	case tag == "ERROR":
		tag = ""
		logf = fs.Errorf
	case tag == "INFO":
		tag = ""
	case strings.HasPrefix(tag, "!"):
		tag = tag[1:]
		logf = fs.Logf
	}

	if b.opt.DryRun {
		logf = fs.Logf
	}

	if tag == "Path1" {
		tag = Color(terminal.CyanFg, "Path1")
	} else {
		tag = Color(terminal.BlueFg, tag)
	}
	msg = Color(terminal.MagentaFg, msg)
	msg = strings.Replace(msg, "Queue copy to", Color(terminal.GreenFg, "Queue copy to"), -1)
	msg = strings.Replace(msg, "Queue delete", Color(terminal.RedFg, "Queue delete"), -1)
	file = Color(terminal.CyanFg, escapePath(file, false))
	logf(nil, "- %-18s%-43s - %s", tag, msg, file)
}

// escapePath will escape control characters in path.
// It won't quote just due to backslashes on Windows.
func escapePath(path string, forceQuotes bool) string {
	path = encode(path)
	test := path
	if runtime.GOOS == "windows" {
		test = strings.ReplaceAll(path, "\\", "/")
	}
	if strconv.Quote(test) != `"`+test+`"` {
		return strconv.Quote(path)
	}
	if forceQuotes {
		return `"` + path + `"`
	}
	return path
}

func quotePath(path string) string {
	return escapePath(path, true)
}

var Colors bool // Colors controls whether terminal colors are enabled

// Color handles terminal colors for bisync