{"record":{"id":"1f2420807a5d5de7","repo":"theonedev/onedev","slug":"path-already-exist-treewalk-getpathstring","errorCode":null,"errorMessage":"Path already exist: \" + treeWalk.getPathString()","messagePattern":"Path already exist: \" \\+ treeWalk\\.getPathString\\(\\)","errorType":"exception","errorClass":"ObjectAlreadyExistsException","httpStatus":null,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/git/service/DefaultGitService.java","lineNumber":704,"sourceCode":"\t\tLong projectId = project.getId();\n\t\tObjectId commitId = runOnProjectServer(projectId, new ClusterTask<>() {\n\n\t\t\tprivate ObjectId insertTree(RevTree revTree, TreeWalk treeWalk, ObjectInserter inserter,\n\t\t\t\t\t\t\t\t\t\tString parentPath, Set<String> currentOldPaths, Map<String, BlobContent> currentNewBlobs) {\n\t\t\t\ttry {\n\t\t\t\t\tList<TreeFormatterEntry> entries = new ArrayList<>();\n\t\t\t\t\twhile (revTree != null && treeWalk.next()) {\n\t\t\t\t\t\tString name = treeWalk.getNameString();\n\t\t\t\t\t\tif (currentOldPaths.contains(name)) {\n\t\t\t\t\t\t\tcurrentOldPaths.remove(name);\n\t\t\t\t\t\t\tBlobContent currentNewBlob = currentNewBlobs.remove(name);\n\t\t\t\t\t\t\tif (currentNewBlob != null) {\n\t\t\t\t\t\t\t\tObjectId blobId = inserter.insert(Constants.OBJ_BLOB, currentNewBlob.getBytes());\n\t\t\t\t\t\t\t\tentries.add(new TreeFormatterEntry(name, currentNewBlob.getMode(), blobId));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (currentNewBlobs.containsKey(name)) {\n\t\t\t\t\t\t\tif ((treeWalk.getRawMode(0) & FileMode.TYPE_MASK) == FileMode.TYPE_TREE) {\n\t\t\t\t\t\t\t\tthrow new ObjectAlreadyExistsException(\"Path already exist: \" + treeWalk.getPathString());\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tBlobContent currentNewBlob = currentNewBlobs.remove(name);\n\t\t\t\t\t\t\t\tObjectId blobId = inserter.insert(Constants.OBJ_BLOB, currentNewBlob.getBytes());\n\t\t\t\t\t\t\t\tentries.add(new TreeFormatterEntry(name, currentNewBlob.getMode(), blobId));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tSet<String> childOldPaths = new HashSet<>();\n\t\t\t\t\t\t\tfor (Iterator<String> it = currentOldPaths.iterator(); it.hasNext(); ) {\n\t\t\t\t\t\t\t\tString currentOldPath = it.next();\n\t\t\t\t\t\t\t\tif (currentOldPath.startsWith(name + \"/\")) {\n\t\t\t\t\t\t\t\t\tchildOldPaths.add(currentOldPath.substring(name.length() + 1));\n\t\t\t\t\t\t\t\t\tit.remove();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tMap<String, BlobContent> childNewBlobs = new HashMap<>();\n\t\t\t\t\t\t\tfor (Iterator<Map.Entry<String, BlobContent>> it = currentNewBlobs.entrySet().iterator();\n\t\t\t\t\t\t\t\t it.hasNext(); ) {\n\t\t\t\t\t\t\t\tMap.Entry<String, BlobContent> entry = it.next();","sourceCodeStart":686,"sourceCodeEnd":722,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/git/service/DefaultGitService.java#L686-L722","documentation":"During DefaultGitService tree insertion (used by commit/restore operations), when walking the old tree a path segment matches a new blob name that also exists as a directory conflict: if the existing entry at that position is a tree (TYPE_TREE) while a new blob of the same name is being added, it throws ObjectAlreadyExistsException(\"Path already exist: <path>\") — a blob and a tree cannot occupy the same path.","triggerScenarios":"Calling DefaultGitService methods that add/update files (e.g. commit file changes or create trees) where the change set contains a file whose path collides with an existing directory in the target tree — e.g. adding blob 'docs' when tree 'docs/' exists (or a sibling blob named the same in the same commit set).","commonSituations":"Adding a file named like an existing folder (case-sensitivity edge cases); API/scripts creating files without checking tree layout; committing a changeset that replaces a directory with a file of the same name in one step.","solutions":["Rename the new file so it does not collide with the existing directory path","If replacing a directory with a file, perform two commits: first delete all files under the directory, then add the file","Pre-validate in the caller that no new blob path prefixes an existing tree path"],"exampleFix":"// before\n// adding blob \"assets\" while tree assets/ exists\nfileChange = new FileChange(FileOperation.ADD, \"assets\", blobId);\n// after\nfileChange = new FileChange(FileOperation.ADD, \"assets/logo.png\", blobId); // unique path","handlingStrategy":"validation","validationCode":"// caller-side check: no new file path collides with an existing tree\nboolean collidesWithDir = existingTreePaths.stream()\n    .anyMatch(p -> p.equals(newFilePath)); // p is a known directory entry\nif (collidesWithDir)\n  throw new IllegalArgumentException(\"Path collides with existing directory: \" + newFilePath);","typeGuard":null,"tryCatchPattern":"try {\n  gitService.commitTreeChanges(...); // or equivalent tree insertion API\n} catch (ObjectAlreadyExistsException e) {\n  // e.getMessage() contains the offending path: rename or split commits\n}","preventionTips":["Check the target tree before adding files: a path may already be a directory","Split 'delete directory + add file of same name' into two commits","Watch for case-insensitive/case-sensitive path mismatches"],"tags":["git","tree-conflict","path-collision"],"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"}