func mkdirAll(base, path string, perm os.FileMode) error {
	abs := filepath.Join(base, path)
	st, err := os.Stat(abs)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		if err := os.MkdirAll(abs, perm); err != nil {
			return err
		}
	} else if !st.IsDir() {
		return errPathExistsAsFile(abs)
	}
	return ensureAll(base, path, perm)
}
mkdirAll is essentially os.MkdirAll(filepath.Join(base, path), perm), but
applies the given permission regardless of the user's umask.
package workspace
import (
	"archive/zip"
	"context"
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"
	"strings"
	"github.com/cockroachdb/errors"
	"github.com/hashicorp/go-multierror"
	batcheslib "github.com/sourcegraph/sourcegraph/lib/batches"
	"github.com/sourcegraph/sourcegraph/lib/batches/git"
	"github.com/sourcegraph/src-cli/internal/batches/graphql"
	"github.com/sourcegraph/src-cli/internal/batches/repozip"
	"github.com/sourcegraph/src-cli/internal/batches/util"
)
type dockerBindWorkspaceCreator struct {
	Dir string
}
var _ Creator = &dockerBindWorkspaceCreator{}
func (wc *dockerBindWorkspaceCreator) Type() CreatorType { return CreatorTypeBind }
func (wc *dockerBindWorkspaceCreator) Create(ctx context.Context, repo *graphql.Repository, steps []batcheslib.Step, archive repozip.Archive) (Workspace, error) {
	w, err := wc.unzipToWorkspace(ctx, repo, archive.Path())
	if err != nil {
		return nil, errors.Wrap(err, "unzipping the repository")
	}
	if err := wc.copyToWorkspace(ctx, w, archive.AdditionalFilePaths()); err != nil {
		return nil, errors.Wrap(err, "copying additional files into workspace")
	}
	return w, errors.Wrap(wc.prepareGitRepo(ctx, w), "preparing local git repo")
}
func (*dockerBindWorkspaceCreator) prepareGitRepo(ctx context.Context, w *dockerBindWorkspace) error {
	if _, err := runGitCmd(ctx, w.dir, "init"); err != nil {
		return errors.Wrap(err, "git init failed")
	}
	if _, err := runGitCmd(ctx, w.dir, "add", "--force", "--all"); err != nil {
		return errors.Wrap(err, "git add failed")
	}
	if _, err := runGitCmd(ctx, w.dir, "commit", "--quiet", "--all", "--allow-empty", "-m", "src-action-exec"); err != nil {
		return errors.Wrap(err, "git commit failed")
	}
	return nil
}
func (wc *dockerBindWorkspaceCreator) unzipToWorkspace(ctx context.Context, repo *graphql.Repository, zip string) (*dockerBindWorkspace, error) {
	prefix := "workspace-" + util.SlugForRepo(repo.Name, repo.Rev())
	workspace, err := unzipToTempDir(ctx, zip, wc.Dir, prefix)
	if err != nil {
		return nil, errors.Wrap(err, "unzipping the ZIP archive")
	}
	return &dockerBindWorkspace{tempDir: wc.Dir, dir: workspace}, nil
}
func (wc *dockerBindWorkspaceCreator) copyToWorkspace(ctx context.Context, w *dockerBindWorkspace, files map[string]string) error {
	for name, src := range files {
		srcStat, err := os.Stat(src)
		if err != nil {
			return err
		}
		if !srcStat.Mode().IsRegular() {
			return fmt.Errorf("%s is not a regular file", src)
		}
		srcFile, err := os.Open(src)
		if err != nil {
			return err
		}
		destPath := path.Join(w.dir, name)
		destFile, err := prepareCopyDestinationFile(srcStat, destPath)
		if err != nil {
			return err
		}
		_, err = io.Copy(destFile, srcFile)
		if err != nil {
			return err
		}
		if cerr := destFile.Close(); cerr != nil {
			return errors.Wrap(cerr, "closing destination file failed")
		}
		if cerr := srcFile.Close(); cerr != nil {
			return errors.Wrap(cerr, "closing source file failed")
		}
	}
	return nil
}
type dockerBindWorkspace struct {
	tempDir string
	dir string
}
var _ Workspace = &dockerBindWorkspace{}
func (w *dockerBindWorkspace) Close(ctx context.Context) error {
	return os.RemoveAll(w.dir)
}
func (w *dockerBindWorkspace) DockerRunOpts(ctx context.Context, target string) ([]string, error) {
	return []string{
		"--mount",
		fmt.Sprintf("type=bind,source=%s,target=%s", w.dir, target),
	}, nil
}
func (w *dockerBindWorkspace) WorkDir() *string { return &w.dir }
func (w *dockerBindWorkspace) Changes(ctx context.Context) (*git.Changes, error) {
	if _, err := runGitCmd(ctx, w.dir, "add", "--all"); err != nil {
		return nil, errors.Wrap(err, "git add failed")
	}
	statusOut, err := runGitCmd(ctx, w.dir, "status", "--porcelain")
	if err != nil {
		return nil, errors.Wrap(err, "git status failed")
	}
	changes, err := git.ParseGitStatus(statusOut)
	if err != nil {
		return nil, errors.Wrap(err, "parsing git status output")
	}
	return &changes, nil
}
func (w *dockerBindWorkspace) Diff(ctx context.Context) ([]byte, error) {
	return runGitCmd(ctx, w.dir, "diff", "--cached", "--no-prefix", "--binary")
}
func (w *dockerBindWorkspace) ApplyDiff(ctx context.Context, diff []byte) error {
	tmp, err := os.CreateTemp(w.tempDir, "bind-workspace-test-*")
	if err != nil {
		return errors.Wrap(err, "saving cached diff to temporary file")
	}
	defer os.Remove(tmp.Name())
	if _, err := tmp.Write(diff); err != nil {
		return errors.Wrap(err, "writing to temporary file")
	}
	if err := tmp.Close(); err != nil {
		return errors.Wrap(err, "closing temporary file")
	}
	if _, err = runGitCmd(ctx, w.dir, "apply", "-p0", tmp.Name()); err != nil {
		return errors.Wrap(err, "applying cached diff")
	}
	_, err = runGitCmd(ctx, w.dir, "add", "--all")
	return err
}
func unzipToTempDir(ctx context.Context, zipFile, tempDir, tempFilePrefix string) (string, error) {
	volumeDir, err := os.MkdirTemp(tempDir, tempFilePrefix)
	if err != nil {
		return "", err
	}
	if err := os.Chmod(volumeDir, 0777); err != nil {
		return "", err
	}
	return volumeDir, unzip(ctx, zipFile, volumeDir)
}
func unzip(ctx context.Context, zipFile, dest string) error {
	r, err := zip.OpenReader(zipFile)
	if err != nil {
		return err
	}
	defer r.Close()
	outputBase := filepath.Clean(dest) + string(os.PathSeparator)
	for _, f := range r.File {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}
		fpath := filepath.Join(dest, f.Name)
		if !strings.HasPrefix(fpath, outputBase) {
			return fmt.Errorf("%s: illegal file path", fpath)
		}
		if f.FileInfo().IsDir() {
			if err := mkdirAll(dest, f.Name, 0777); err != nil {
				return err
			}
			continue
		}
		outFile, err := prepareCopyDestinationFile(f.FileInfo(), fpath)
		if err != nil {
			return err
		}
		rc, err := f.Open()
		if err != nil {
			outFile.Close()
			return err
		}
		_, err = io.Copy(outFile, rc)
		rc.Close()
		cerr := outFile.Close()
		if err != nil {
			return errors.Wrapf(err, "copying %q failed", f.Name)
		}
		if cerr != nil {
			return errors.Wrap(err, "closing output file failed")
		}
	}
	return nil
}
type moder interface {
	Mode() os.FileMode
}
func prepareCopyDestinationFile(sourceInfo moder, dest string) (*os.File, error) {
	if err := mkdirAll(filepath.Dir(dest), "", 0777); err != nil {
		return nil, err
	}
	outFile, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, sourceInfo.Mode())
	if err != nil {
		return nil, errors.Wrap(err, "opening destination file failed")
	}
	if sourceInfo.Mode()&0111 != 0 {
		err = os.Chmod(outFile.Name(), 0777)
	} else {
		err = os.Chmod(outFile.Name(), 0666)
	}
	if err != nil {
		outFile.Close()
		return nil, err
	}
	return outFile, nil
}
type errPathExistsAsFile string
var _ error = errPathExistsAsFile("")
func (e errPathExistsAsFile) Error() string {
	return fmt.Sprintf("path already exists, but not as a directory: %s", string(e))
}