From ab4b8351261d62e4319ad743473a0bb96d9b7dd9 Mon Sep 17 00:00:00 2001 From: atif09 Date: Sun, 18 Jan 2026 01:07:23 +0530 Subject: [PATCH 1/3] [build] Enable building docs with local changes #251 Contributors could not preview local documentation changes because the build system always cloned from GitHub instead of using local files. This fix modifies build.py to detect local openwisp-docs builds and symlink local files to staging-dir/ instead of cloning. This works only in non-PRODUCTION mode, so CI/CD behavior is unchanged. Also fixed git fetch/checkout commands to work with branches and tags. Fixes #251 --- build.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/build.py b/build.py index e0e8b226..2142f7b5 100755 --- a/build.py +++ b/build.py @@ -271,6 +271,32 @@ def clone_or_update_repo(name, branch, dir_name, owner="openwisp", dest=None): If the repository already exists, update it. Otherwise, clone the repository. """ repository = f"{owner}/{name}" + + # Support for building with local changes + if name == "openwisp-docs" and not os.environ.get("PRODUCTION"): + print(f"Using local directory for '{name}'") + + os.makedirs("staging-dir", exist_ok=True) + exclude_items = {"staging-dir", "modules", "_build", ".git", "__pycache__"} + for item in os.listdir("."): + if item.startswith("."): + continue + + if item in exclude_items: + continue + + # skip virtual environments (detect with pyvenv.cfg) + if os.path.isdir(item) and os.path.exists(os.path.join(item, "pyvenv.cfg")): + continue + + src_path = os.path.abspath(item) + dest_path = os.path.join("staging-dir", item) + if os.path.islink(dest_path): + os.unlink(dest_path) + if not os.path.exists(dest_path): + os.symlink(src_path, dest_path) + return + if os.environ.get("SSH"): # SSH cloning is a convenient option for local development, as it # allows you to commit changes directly to the repository, but it @@ -294,7 +320,13 @@ def clone_or_update_repo(name, branch, dir_name, owner="openwisp", dest=None): # "-c advice.detachedHead=false" is used to suppress the warning # about being in a detached HEAD state when checking out tags. subprocess.run( - ["git", "-c", "advice.detachedHead=false", "checkout", f"refs/heads/{branch}"], + [ + "git", + "-c", + "advice.detachedHead=false", + "checkout", + branch, + ], cwd=clone_path, check=True, ) From d98c821887fa1cdc8062a9dc4e3e25d55bd36248 Mon Sep 17 00:00:00 2001 From: atif09 Date: Sun, 18 Jan 2026 02:22:51 +0530 Subject: [PATCH 2/3] [build] Add safeguard for staging-dir symlink edge case As suggested by CodeRabbit review, add check to ensure staging-dir is a real directory before creating symlinks inside it. --- build.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index 2142f7b5..2a228849 100755 --- a/build.py +++ b/build.py @@ -271,11 +271,12 @@ def clone_or_update_repo(name, branch, dir_name, owner="openwisp", dest=None): If the repository already exists, update it. Otherwise, clone the repository. """ repository = f"{owner}/{name}" - # Support for building with local changes if name == "openwisp-docs" and not os.environ.get("PRODUCTION"): print(f"Using local directory for '{name}'") + if os.path.islink("staging-dir") or os.path.isfile("staging-dir"): + os.unlink("staging-dir") os.makedirs("staging-dir", exist_ok=True) exclude_items = {"staging-dir", "modules", "_build", ".git", "__pycache__"} for item in os.listdir("."): @@ -325,7 +326,7 @@ def clone_or_update_repo(name, branch, dir_name, owner="openwisp", dest=None): "-c", "advice.detachedHead=false", "checkout", - branch, + f"refs/heads/{branch}", ], cwd=clone_path, check=True, From 4b9525a60ae33658cbea24aab37b8661bb9e638b Mon Sep 17 00:00:00 2001 From: atif09 Date: Mon, 19 Jan 2026 00:56:50 +0530 Subject: [PATCH 3/3] [build] Fix git fetch for repeated builds #251 The original refspec tried to fetch directly into checked-out branches, causing 'refusing to fetch into checked-out branch' errors on repeated builds This fix fetches into remote-tracking branches (refs/remotes/origin/) instead, which can always be updated, then uses 'checkout -B' to create/reset the local branch from the remote. This maintains branch/tag disambiguation while fixing the repeated build issue Fixes #251 --- build.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/build.py b/build.py index 2a228849..8c113ea7 100755 --- a/build.py +++ b/build.py @@ -313,8 +313,13 @@ def clone_or_update_repo(name, branch, dir_name, owner="openwisp", dest=None): if os.path.exists(clone_path): print(f"Repository '{name}' already exists. Updating...") subprocess.run( - # Clears ambiguity when git tags and branches have identical names - ["git", "fetch", "origin", f"refs/heads/{branch}:refs/heads/{branch}"], + # Update remote-tracking ref (avoid fetching into checked-out branch) + [ + "git", + "fetch", + "origin", + f"+refs/heads/{branch}:refs/remotes/origin/{branch}", + ], cwd=clone_path, check=True, ) @@ -326,7 +331,9 @@ def clone_or_update_repo(name, branch, dir_name, owner="openwisp", dest=None): "-c", "advice.detachedHead=false", "checkout", - f"refs/heads/{branch}", + "-B", + branch, + f"refs/remotes/origin/{branch}", ], cwd=clone_path, check=True,