AlistGo/alist · error

cannot copy a submodule

Error message

cannot copy a submodule

What it means

In the copy helper used by Copy/Move, after fetching the source parent tree the driver scans for the source entry; an entry of Type == "commit" is a git submodule (gitlink). Git submodule pointers reference commits in foreign repositories and cannot be duplicated into an arbitrary destination tree by this driver, so the copy is rejected.

Source

Thrown at drivers/github/driver.go:903

func (d *Github) copyWithoutRenewTree(srcObj, dstDir model.Obj) (dstSha, newSha, srcParentSha string, srcParentTree *TreeResp, err error) {
	dst, err := d.get(dstDir.GetPath())
	if err != nil {
		return "", "", "", nil, err
	}
	if dst.Entries == nil {
		return "", "", "", nil, errs.NotFolder
	}
	dstSha = dst.Sha
	srcParentPath := stdpath.Dir(srcObj.GetPath())
	srcParentTree, srcParentSha, err = d.getTreeDirectly(srcParentPath)
	if err != nil {
		return "", "", "", nil, err
	}
	var src *TreeObjReq = nil
	for _, t := range srcParentTree.Trees {
		if t.Path == srcObj.GetName() {
			if t.Type == "commit" {
				return "", "", "", nil, errors.New("cannot copy a submodule")
			}
			src = &t.TreeObjReq
			break
		}
	}
	if src == nil {
		return "", "", "", nil, errs.ObjectNotFound
	}

	newTree := make([]interface{}, 0, 2)
	newTree = append(newTree, *src)
	if len(dst.Entries) == 1 && dst.Entries[0].Name == ".gitkeep" {
		newTree = append(newTree, TreeObjReq{
			Path: ".gitkeep",
			Mode: "100644",
			Type: "blob",
			Sha:  nil,
		})

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Clone locally and use git: move the submodule path, update .gitmodules, commit, push
  2. Copy the submodule's upstream URL and re-add it at the destination with git submodule add
  3. Replace the submodule with plain vendored files if it must be copyable through the driver

Example fix

# before: Copy submodule-folder -> error
# after: in a local clone
git submodule deinit -f <old-path> && git rm <old-path>
git submodule add <url> <new-path>
git commit -m "move submodule" && git push
Defensive patterns

Strategy: validation

Validate before calling

// Before Copy/Move, verify the source is not a submodule
tree, _, err := d.getTreeDirectly(stdpath.Dir(src.GetPath()))
if err != nil { return err }
for _, t := range tree.Trees {
	if t.Path == src.GetName() && t.Type == "commit" {
		return errors.New("source is a submodule; copy it with git instead")
	}
}
return d.Copy(ctx, src, dst)

Type guard

func isSubmoduleEntry(t TreeEntry) bool { return t.Type == "commit" }

Try / catch

if err := d.Copy(ctx, src, dst); err != nil {
	if strings.Contains(err.Error(), "cannot copy a submodule") {
		return fmt.Errorf("%s is a submodule and must be copied via git", src.GetPath())
	}
	return err
}

Prevention

When it happens

Trigger: Calling Copy or Move where srcObj is a submodule directory — the tree walk hits t.Type == "commit" for the matching path and returns before any tree mutation.

Common situations: Trying to duplicate or relocate a vendored submodule (themes, SDKs) into another folder of the same repo via the OpenList UI.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/a6f66e2599858534. Report an issue: GitHub.