diff --git a/src/scripts/git_init.py b/src/scripts/git_init.py index 72034d9..1a5595e 100644 --- a/src/scripts/git_init.py +++ b/src/scripts/git_init.py @@ -19,9 +19,39 @@ try: "the project does not yet have a git binding. The init() " "call below should establish one if a repo path is supplied.") - if not LOCAL_REPO_PATH: - # Default: init a repo in the project's own directory - LOCAL_REPO_PATH = os.path.dirname(PROJECT_FILE_PATH) + # CODESYS Git uses a dual-storage model: the .project file stays where it + # is, the git working tree lives in a SEPARATE empty directory. Pointing + # init() at the project's own folder fails with "DirectoryNotEmpty", and + # passing nothing previously dumped the user into that trap. Default to a + # '_git' sibling and auto-create it; verify it is empty + # before handing off to CODESYS so we surface a clear hint instead of + # CODESYS's lower-level error. + project_dir = os.path.dirname(PROJECT_FILE_PATH) + project_stem = os.path.splitext(os.path.basename(PROJECT_FILE_PATH))[0] + if not LOCAL_REPO_PATH or os.path.normcase(os.path.abspath(LOCAL_REPO_PATH)) == os.path.normcase(os.path.abspath(project_dir)): + if LOCAL_REPO_PATH: + print("DEBUG: requested LocalRepoPath equals the project dir; " + "rerouting to a sibling because CODESYS Git requires a separate empty dir.") + # Use a sibling so we don't write into the (likely shared) project folder + LOCAL_REPO_PATH = os.path.join(os.path.dirname(project_dir), project_stem + "_git") + print("DEBUG: defaulted LocalRepoPath to '%s'" % LOCAL_REPO_PATH) + + if not os.path.exists(LOCAL_REPO_PATH): + print("DEBUG: creating LocalRepoPath '%s'" % LOCAL_REPO_PATH) + os.makedirs(LOCAL_REPO_PATH) + elif not os.path.isdir(LOCAL_REPO_PATH): + raise RuntimeError( + "LocalRepoPath '%s' exists but is not a directory." % LOCAL_REPO_PATH) + else: + existing = os.listdir(LOCAL_REPO_PATH) + if existing: + raise RuntimeError( + "LocalRepoPath '%s' is not empty (contains %d entries: %s). " + "CODESYS Git's dual-storage model requires a fresh empty " + "directory separate from the project's own folder. Either " + "pass a different localRepoPath or empty this one, then " + "retry git_init." % ( + LOCAL_REPO_PATH, len(existing), existing[:5])) print("DEBUG: calling git.init('%s')" % LOCAL_REPO_PATH) git.init(LOCAL_REPO_PATH) diff --git a/src/server.ts b/src/server.ts index 54b0e86..708f9df 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1066,10 +1066,10 @@ export async function startMcpServer(config: ServerConfig): Promise { s.tool( 'git_init', - "Initialises a Git repository for the project's directory (or a custom path) via project.git.init(). One-shot setup; use git_status afterwards to confirm. Requires the CODESYS Git plug-in AND an active CODESYS Professional Developer Edition subscription license -- without the subscription, the tool fails fast with a clear PDE-required message (the runtime 'HasGitLicense' rule gates every project.git.* call).", + "Initialises a Git working tree for the project via project.git.init(). CODESYS Git uses a dual-storage model: the .project file stays where it is, the git repo lives in a SEPARATE empty directory. If localRepoPath is omitted (or equals the project's own folder), the tool auto-defaults to a '_git' sibling and auto-creates it; if it exists and is non-empty, the tool fails with a clear hint. One-shot setup; use git_status afterwards to confirm. Requires the CODESYS Git plug-in AND an active CODESYS Professional Developer Edition subscription license -- without the subscription, the tool fails fast with a clear PDE-required message (the runtime 'HasGitLicense' rule gates every project.git.* call).", { projectFilePath: z.string().describe("Path to the project file."), - localRepoPath: z.string().optional().describe("Filesystem path to initialise the repo at. Defaults to the project file's parent directory."), + localRepoPath: z.string().optional().describe("Filesystem path for the git working tree. Must be a separate empty directory, NOT the project's own folder. If omitted, defaults to a '_git' sibling and auto-creates it."), }, async (args: { projectFilePath: string; localRepoPath?: string }) => { const escaped = resolvePath(args.projectFilePath, workspaceDir);