{"record":{"id":"f632229771a9c9f8","repo":"theonedev/onedev","slug":"overlapped-blob-path-blobpath","errorCode":null,"errorMessage":"Overlapped blob path: \" + blobPath","messagePattern":"Overlapped blob path: \" \\+ blobPath","errorType":"exception","errorClass":"ObjectAlreadyExistsException","httpStatus":null,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/git/service/DefaultGitService.java","lineNumber":773,"sourceCode":"\t\t\t\t\t\tSet<String> files = new HashSet<>();\n\t\t\t\t\t\tfor (Map.Entry<String, BlobContent> entry : currentNewBlobs.entrySet()) {\n\t\t\t\t\t\t\tString path = entry.getKey();\n\t\t\t\t\t\t\tif (!path.contains(\"/\")) {\n\t\t\t\t\t\t\t\tfiles.add(path);\n\t\t\t\t\t\t\t\tentries.add(new TreeFormatterEntry(path, entry.getValue().getMode(),\n\t\t\t\t\t\t\t\t\t\tinserter.insert(Constants.OBJ_BLOB, entry.getValue().getBytes())));\n\t\t\t\t\t\t\t\tfiles.add(path);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tSet<String> topLevelPathSegments = new LinkedHashSet<>();\n\t\t\t\t\t\tfor (String path : currentNewBlobs.keySet()) {\n\t\t\t\t\t\t\tif (path.contains(\"/\")) {\n\t\t\t\t\t\t\t\tString topLevelPathSegment = StringUtils.substringBefore(path, \"/\");\n\t\t\t\t\t\t\t\tif (files.contains(topLevelPathSegment)) {\n\t\t\t\t\t\t\t\t\tString blobPath = topLevelPathSegment;\n\t\t\t\t\t\t\t\t\tif (parentPath != null)\n\t\t\t\t\t\t\t\t\t\tblobPath = parentPath + \"/\" + path;\n\t\t\t\t\t\t\t\t\tthrow new ObjectAlreadyExistsException(\"Overlapped blob path: \" + blobPath);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\ttopLevelPathSegments.add(topLevelPathSegment);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (String topLevelPathSegment : topLevelPathSegments) {\n\t\t\t\t\t\t\tMap<String, BlobContent> childNewBlobs = new HashMap<>();\n\t\t\t\t\t\t\tfor (Map.Entry<String, BlobContent> entry : currentNewBlobs.entrySet()) {\n\t\t\t\t\t\t\t\tString path = entry.getKey();\n\t\t\t\t\t\t\t\tif (path.startsWith(topLevelPathSegment + \"/\"))\n\t\t\t\t\t\t\t\t\tchildNewBlobs.put(path.substring(topLevelPathSegment.length() + 1), entry.getValue());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (parentPath == null)\n\t\t\t\t\t\t\t\tparentPath = topLevelPathSegment;\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tparentPath += \"/\" + topLevelPathSegment;\n\t\t\t\t\t\t\tObjectId childTreeId = insertTree(revTree, treeWalk, inserter, parentPath,\n\t\t\t\t\t\t\t\t\tSets.newHashSet(), childNewBlobs);","sourceCodeStart":755,"sourceCodeEnd":791,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/git/service/DefaultGitService.java#L755-L791","documentation":"When inserting new blobs in a single commit, DefaultGitService rejects a request where a new file path (e.g. 'file.txt') collides with the top-level segment of another new nested path (e.g. 'file.txt/sub/file') — git cannot hold both a file and a directory with the same name. The overlap is detected by comparing top-level segments of new paths against new top-level files and ObjectAlreadyExistsException is thrown.","triggerScenarios":"Committing a batch of new blobs (BlobEdits.newBlobs) via the git service where one new path is a plain file 'X' and another new path starts with 'X/', in the same request.","commonSituations":"Bulk uploads/imports generating file trees programmatically where one component is both a file and a directory; adding a directory of files under a name that already exists as a file in the same batch; CI-generated configs writing both 'module' and 'module/x' paths.","solutions":["Rename either conflicting path so no name is used both as a file and as a directory in the same commit.","Split into two commits: first remove the file that must become a directory, then add the nested files.","Validate the new blob path set before committing: no path P and P/… may coexist."],"exampleFix":"// before\nedits.addNewBlob(\"config\", ...);\nedits.addNewBlob(\"config/app.yml\", ...); // conflict\n// after\nString file = \"config\", dir = \"config/app.yml\";\nif (dir.startsWith(file + \"/\")) throw new IllegalArgumentException(file + \" cannot be both file and directory\");","handlingStrategy":"validation","validationCode":"Set<String> files = newBlobs.keySet().stream().filter(p -> !p.contains(\"/\")).collect(Collectors.toSet());\nboolean conflict = newBlobs.keySet().stream().anyMatch(p -> p.contains(\"/\") && files.contains(StringUtils.substringBefore(p, \"/\")));\nif (conflict) throw new IllegalArgumentException(\"A new path is used both as file and directory\");","typeGuard":null,"tryCatchPattern":"try { commitBlobs(edits); } catch (ObjectAlreadyExistsException e) { log.error(\"Overlapping paths in batch: {}\", e.getMessage()); }","preventionTips":["Validate the new-path set for file/dir overlaps before submitting","Generate programmatic file trees through a single naming scheme","Split file-removal and directory-creation into separate commits"],"tags":["git","path-conflict","tree"],"backgroundTag":"file-already-exists","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}