Skip to content

Filesharing Create Git Remote Task¤

task api name: create_remote_git

The create_remote_git task validates and registers a Git remote in worker memory. It initializes the private local repository and configures origin, but does not fetch or make repository content available.

Inputs¤

Parameter Required Default Description
name Yes Unique runtime remote name
url Yes Git repository URL
type Yes Must be git
branch For Git Branch cloned by git_clone
mount No name Relative publication path under nf://
description No Empty Operator-facing description
username No null HTTPS username; requires password
password No null HTTPS password or token; requires username
auto_sync No false Enable periodic synchronization
sync_interval No 30 Attempt interval in seconds, clamped to 5–86,400

Output¤

A newly registered remote returns its name with unsynchronized status:

{
  "name": "automation-assets",
  "status": "unsynchronized"
}

Examples¤

nf# filesharing git create-remote name automation-assets type git url https://github.com/example/automation-assets.git branch main mount repositories/automation-assets
from norfab.core.nfapi import NorFab

with NorFab(inventory="./inventory.yaml") as nf:
    client = nf.make_client()
    result = client.run_job(
        service="filesharing",
        task="create_remote_git",
        workers="filesharing-worker-1",
        kwargs={
            "name": "automation-assets",
            "type": "git",
            "url": "https://github.com/example/automation-assets.git",
            "branch": "main",
            "mount": "repositories/automation-assets",
        },
    )
    print(result)

Notes¤

  • The remote exists in memory until it is deleted or the worker restarts.
  • Inventory remotes are registered through this same task during startup.
  • Credentials are used for Git fetches and are redacted from remote listings.
  • Call git_clone to fetch content and make it available under nf://.

Task Command Shell Reference¤

nf#man tree filesharing.git.create-remote

R - required field, M - supports multiline input, D - dynamic key

root
└── filesharing:    File sharing service
    └── git:    Manage Git remotes
        └── create-remote:    Register and initialize a Git remote
            ├── timeout:    Job timeout
            ├── workers:    Filter workers to target, default 'all'
            ├── verbose-result:    Control output details, default 'False'
            ├── nowait:    Do not wait for job to complete, default 'False'
            ├── name (R):    Unique name used to identify the remote
            ├── mount:    Relative path to make the repository available at; defaults to name
            ├── description:    Optional human-readable remote description, default ''
            ├── url (R):    Git repository URL
            ├── branch:    Git branch to synchronize
            ├── type (R):    Remote driver type; use git
            ├── username:    HTTPS username for an authenticated remote
            ├── password:    HTTPS password or access token for an authenticated remote
            ├── auto_sync:    Periodically synchronize the remote after it is created, default 'False'
            └── sync_interval:    Seconds between automatic synchronization attempts, default '30'
nf#

Python API Reference¤

Initialize the private local repository for a configured Git remote.

The task creates <runtime>/remotes/<name>/repository, initializes it with GitPython and configures a credential-free origin URL. It does not contact the remote server, fetch commits, or make files available.

Parameters:

Name Type Description Default
job Job

Active worker job. Internal startup calls pass None and do not wait when the remote lock is held.

required
name str

Unique runtime name used by clone and delete tasks.

required
url str

Git repository URL.

required
type str

Remote driver type. This task accepts git.

required
mount str | None

Relative File Sharing publication path. Defaults to name.

None
description str

Optional description shown by remote listing tasks.

''
branch str | None

Git branch to fetch.

None
username str | None

Optional HTTPS username.

None
password str | None

Optional HTTPS password or access token.

None
auto_sync bool

Whether the worker periodically synchronizes the remote.

False
sync_interval int

Seconds between automatic synchronization attempts.

30

Returns:

Type Description
Result

A result containing the remote name and unsynchronized status.

Source code in norfab\workers\filesharing_worker\git_tasks.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
@Task(
    input=CreateRemoteGitInput,
    output=CreateRemoteGitResult,
    fastapi={"methods": ["POST"]},
    agent={"enabled": False},
    mcp={
        "annotations": {
            "title": "Create Git Remote",
            "readOnlyHint": False,
            "destructiveHint": False,
            "idempotentHint": True,
            "openWorldHint": False,
        }
    },
)
def create_remote_git(
    self,
    job: Job,
    name: str,
    url: str,
    type: str,
    mount: str | None = None,
    description: str = "",
    branch: str | None = None,
    username: str | None = None,
    password: str | None = None,
    auto_sync: bool = False,
    sync_interval: int = 30,
) -> Result:
    """Initialize the private local repository for a configured Git remote.

    The task creates ``<runtime>/remotes/<name>/repository``, initializes it
    with GitPython and configures a credential-free ``origin`` URL. It does
    not contact the remote server, fetch commits, or make files available.

    Args:
        job: Active worker job. Internal startup calls pass ``None`` and do
            not wait when the remote lock is held.
        name: Unique runtime name used by clone and delete tasks.
        url: Git repository URL.
        type: Remote driver type. This task accepts ``git``.
        mount: Relative File Sharing publication path. Defaults to name.
        description: Optional description shown by remote listing tasks.
        branch: Git branch to fetch.
        username: Optional HTTPS username.
        password: Optional HTTPS password or access token.
        auto_sync: Whether the worker periodically synchronizes the remote.
        sync_interval: Seconds between automatic synchronization attempts.

    Returns:
        A result containing the remote name and ``unsynchronized`` status.
    """
    msg = f"{self.name} - initializing Git remote '{name}'"
    log.info(msg)
    if job is not None:
        job.event(msg)

    mount = (mount or name).replace("\\", "/")
    remote = {
        "name": name,
        "url": url,
        "type": type,
        "mount": f"nf://{mount}",
        "description": description,
        "branch": branch,
        "username": username,
        "password": password,
        "auto_sync": auto_sync,
        "sync_interval": max(5, min(sync_interval, 86_400)),
        "last_sync_attempt": None,
        "last_sync_timer": None,
        "status": "unsynchronized",
        "repository": os.path.join(self.runtime_dir, "remotes", name, "repository"),
        "lock": threading.Lock(),
    }

    # Preserve synchronization state and the lock when inventory reloads a
    # remote that is already registered.
    existing_remote = self.remotes.get(name)
    if existing_remote is not None:
        remote["last_sync_attempt"] = existing_remote["last_sync_attempt"]
        remote["last_sync_timer"] = existing_remote["last_sync_timer"]
        remote["status"] = existing_remote["status"]
        remote["lock"] = existing_remote["lock"]
    if remote["type"] != "git":
        msg = f"{self.name} - remote '{name}' is not a git remote"
        log.warning(msg)
        if job is not None:
            job.event(msg, severity="ERROR")
        return Result(failed=True, errors=[f"Remote '{name}' is not a git remote"])

    # A mount cannot contain or be contained by another remote mount.
    mount_path = self._safe_path(remote["mount"])
    for existing_name, existing in self.remotes.items():
        if existing_name == name:
            continue
        existing_path = self._safe_path(existing["mount"])
        if os.path.commonpath([mount_path, existing_path]) in {
            mount_path,
            existing_path,
        }:
            msg = (
                f"{self.name} - remote mount '{remote['mount']}' collides with "
                f"remote '{existing_name}'"
            )
            log.error(msg)
            if job is not None:
                job.event(msg, severity="ERROR")
            return Result(
                failed=True,
                errors=[
                    f"Remote mount '{remote['mount']}' collides with another remote"
                ],
            )

    lock = remote["lock"]
    if job is None:
        acquired = lock.acquire(blocking=False)
    else:
        acquired = lock.acquire(
            timeout=job.timeout if job.timeout is not None else -1
        )
    if not acquired:
        msg = f"{self.name} - remote '{name}' is busy"
        log.warning(msg)
        if job is not None:
            job.event(msg, severity="WARNING")
        return Result(failed=True, errors=[f"Remote '{name}' is busy"])

    try:
        # The private repository is retained between syncs; only its
        # working tree is later made available in the File Sharing namespace.
        os.makedirs(remote["repository"], exist_ok=True)
        with Repo.init(remote["repository"]) as repository:
            if "origin" in repository.remotes:
                repository.remotes.origin.set_url(remote["url"])
            else:
                repository.create_remote("origin", remote["url"])
        self.remotes[name] = remote
    finally:
        lock.release()

    if remote["auto_sync"] and self.remote_sync_thread is None:
        self.remote_sync_thread = self.start_git_sync()
    msg = f"{self.name} - initialized Git remote '{name}' at {remote['mount']}"
    log.info(msg)
    if job is not None:
        job.event(msg)
    return Result(result={"name": remote["name"], "status": remote["status"]})