diff --git a/src/scripts/git_branch_set_upstream_to.py b/src/scripts/git_branch_set_upstream_to.py new file mode 100644 index 0000000..730d562 --- /dev/null +++ b/src/scripts/git_branch_set_upstream_to.py @@ -0,0 +1,68 @@ +import sys, scriptengine as script_engine, traceback + +REMOTE_NAME = "{REMOTE_NAME}" +BRANCH_NAME = "{BRANCH_NAME}" # may be empty -> defaults to current branch + +try: + print("DEBUG: git_branch_set_upstream_to: Project='%s', Remote='%s', Branch='%s'" % ( + PROJECT_FILE_PATH, REMOTE_NAME, BRANCH_NAME)) + + if not REMOTE_NAME.strip(): + raise ValueError("Remote name is empty.") + + primary_project = ensure_project_open(PROJECT_FILE_PATH) + + git = getattr(primary_project, 'git', None) + if git is None: + raise RuntimeError( + "Project '%s' is not bound to a Git repository (primary_project.git " + "is None). Run git_init first." % PROJECT_FILE_PATH) + + if not hasattr(git, 'branch_set_upstream_to'): + attrs = sorted([a for a in dir(git) if not a.startswith('_')]) + raise AttributeError( + "project.git does not expose branch_set_upstream_to(). " + "Available: %s" % attrs) + + if BRANCH_NAME: + print("DEBUG: calling git.branch_set_upstream_to('%s', '%s')" % ( + REMOTE_NAME, BRANCH_NAME)) + git.branch_set_upstream_to(REMOTE_NAME, BRANCH_NAME) + else: + print("DEBUG: calling git.branch_set_upstream_to('%s') -- defaults to current branch" % REMOTE_NAME) + git.branch_set_upstream_to(REMOTE_NAME) + print("DEBUG: branch_set_upstream_to returned without exception.") + + current = "?" + if hasattr(git, 'branch_show_current'): + try: + current = str(git.branch_show_current()) + except Exception: + pass + + if BRANCH_NAME: + print("Branch '%s' now tracks upstream '%s'" % (BRANCH_NAME, REMOTE_NAME)) + else: + print("Current branch '%s' now tracks upstream '%s'" % (current, REMOTE_NAME)) + print("SCRIPT_SUCCESS: git_branch_set_upstream_to complete.") + sys.exit(0) +except Exception as e: + detailed = traceback.format_exc() + raw = "%s" % e + if 'HasGitLicense' in raw or 'HasGitLicense' in detailed: + msg = ( + "CODESYS Git scripting requires an active CODESYS Professional " + "Developer Edition subscription license. The plug-in is installed " + "but the runtime 'HasGitLicense' rule returned False, so every " + "project.git.* operation (init/commit/status/...) is gated. " + "Activate a Professional Developer Edition subscription on this " + "CODESYS install (see https://store.codesys.com/en/codesys-git.html, " + "sections 'Additional Requirements' and 'Licensing'). Underlying " + "error from CODESYS: %s" % e + ) + else: + msg = "Error in git_branch_set_upstream_to for project '%s': %s\n%s" % ( + PROJECT_FILE_PATH, e, detailed) + print(msg) + print("SCRIPT_ERROR: %s" % msg) + sys.exit(1) diff --git a/src/server.ts b/src/server.ts index 70f7d04..9db61f8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1139,6 +1139,30 @@ export async function startMcpServer(config: ServerConfig): Promise { } ); + s.tool( + 'git_branch_set_upstream_to', + "Sets the upstream tracking ref of a local branch to a configured remote via project.git.branch_set_upstream_to(remoteName, branchName?). MANDATORY before the first push to a fresh remote -- per the helpme-codesys.com Git scripting docs (https://content.helpme-codesys.com/en/CODESYS%20Git/_git_using_scripting.html), 'After adding a remote, use project.git.branch_set_upstream_to(origin_remote) before pushing'. Without this call, push() fails with 'The branch X does not track an upstream branch.' Requires an existing git binding on the project, a configured remote (use git_remote_add), and an active CODESYS Professional Developer Edition subscription license -- without the subscription, the tool fails fast with a clear PDE-required message.", + { + projectFilePath: z.string().describe("Path to the project file."), + remoteName: z.string().min(1).describe("Remote name to track. Conventionally 'origin'."), + branchName: z.string().optional().describe("Local branch to configure. Optional; defaults to the current branch."), + }, + async (args: { projectFilePath: string; remoteName: string; branchName?: string }) => { + const escaped = resolvePath(args.projectFilePath, workspaceDir); + const script = scriptManager.prepareScriptWithHelpers( + 'git_branch_set_upstream_to', + { + PROJECT_FILE_PATH: escaped, + REMOTE_NAME: args.remoteName, + BRANCH_NAME: args.branchName || '', + }, + ['ensure_project_open'] + ); + const result = await executor.executeScript(script); + return formatToolResponse(result, `git_branch_set_upstream_to complete for ${args.projectFilePath}.`); + } + ); + s.tool( 'git_push', "Pushes the local branch to a configured remote via project.git.push(). If username + token are both provided, uses the 3-arg overload push(branch, user, SecureString(token)) and derives the current branch when branchName is omitted; otherwise calls push(branch) or push() and relies on git config / Windows Credential Manager / cached credentials. SECURITY NOTE: when token is supplied, it is templated into the IronPython script that the watcher executes -- briefly resident in the watcher's command file on disk. Prefer cached credentials (omit token) when feasible. Requires an existing git binding on the project, a configured remote (use git_remote_add), and an active CODESYS Professional Developer Edition subscription license -- without the subscription, the tool fails fast with a clear PDE-required message.",