This is an automated email from the ASF dual-hosted git repository.
sciabarracom pushed a commit to branch 0.9.0
in repository https://gitbox.apache.org/repos/asf/openserverless.git
The following commit(s) were added to refs/heads/0.9.0 by this push:
new 6ea5ce3 rebranding (#228)
6ea5ce3 is described below
commit 6ea5ce3371dcade93cbafe310674f6716d93d448
Author: Michele Sciabarra <[email protected]>
AuthorDate: Wed Aug 26 18:32:55 2026 +0200
rebranding (#228)
* rebrander
* rebrander with rename files
* rebrander
* do-it-for-real
* taskfile
* bug fixes
* bug fixes
---------
Co-authored-by: Trustable User <[email protected]>
---
.gitignore | 1 +
.licenserc.yaml | 1 +
Taskfile.yml | 3 +-
olaris-op | 2 +-
rebrander.md | 69 +++++++++
rebrander.py | 451 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 525 insertions(+), 2 deletions(-)
diff --git a/.gitignore b/.gitignore
index d5a38b0..0f23818 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@
network-cache/
ops
RELEASE
+rebrander.log
diff --git a/.licenserc.yaml b/.licenserc.yaml
index a8579c9..9a05917 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -40,3 +40,4 @@ header:
- 'NOTICE'
- 'CHANGES'
- 'RELEASE'
+ - 'rebrander.log'
diff --git a/Taskfile.yml b/Taskfile.yml
index c97f883..4f4fcdb 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -206,7 +206,6 @@ tasks:
then echo 'export PATH=~/.local/bin:"$PATH"' >>~/.profile
fi
-
gitinit:
desc: initialize git if missing
cmds:
@@ -224,6 +223,7 @@ tasks:
build:
desc: build all
cmds:
+ - python3 rebrander.py --do-it-for-real
- task: gitinit
- task: opslink
- task: openwhisk
@@ -269,6 +269,7 @@ tasks:
cmds:
- git tag "v{{.V}}"
- echo "v{{.V}}" >RELEASE
+ - python3 rebrander.py --do-it-for-real
- >
{{if eq OS "darwin"}}gtar{{else}}tar{{end}}
-C .. --exclude='.git'
diff --git a/olaris-op b/olaris-op
index 424ca16..579fddb 160000
--- a/olaris-op
+++ b/olaris-op
@@ -1 +1 @@
-Subproject commit 424ca16060edba3bb332f515a2c4735e938e2b89
+Subproject commit 579fddbfc1953169812e8c66d605417bbfe204e5
diff --git a/rebrander.md b/rebrander.md
new file mode 100644
index 0000000..7c6a931
--- /dev/null
+++ b/rebrander.md
@@ -0,0 +1,69 @@
+Write a rebrander.py python script with uv embedded deps.
+
+The goal is rebrand openserverless in openserverless
+
+It walks all the subfolders renaming with `git mv` all the folder
openserverless in openserverless
+
+Then it will rename with `git mv` the files including "openserverless" in the
name with the name including "openserverless"
+
+This file rename is mandatory, not optional: file names written inside other
+files are rebranded as plain text, so if the files themselves are not renamed
+the references point at paths that no longer exist. Rename folders first, then
+files, so each `git mv` targets a path that still exists. Dispatch each
+`git mv` to the repository that actually tracks the path, as with folders, and
+record a failure as a warning and skip that path.
+
+Log the renamed files the same way as the renamed folders.
+
+Then walks all the files replacing all the occurrenciens of
+- "openserverless" in "opensverveless"
+- "OpenServerless" in "OpenServerless"
+- "OPENSERVERLESS" to "OPENSERVERLESS"
+
+warn any usage of OpenServerless with other cases
+
+accept existing
+- OPENSERVERLESS
+- openserverless
+- Openserverless
+
+as common usages within the codebase,
+
+warn of differenct casing of openserverless and openserverless
+
+Leave a rebrander.log listing:
+
+- all the folders renamed
+- all the lines replaced
+- all the lines left as is
+
+show in the log for the lines: <file>:<line>:<newvalue>
+
+include in the log the excluded lines with prefix SKIP:<file>:<line>:<oldvalue>
+
+By default is it in dry run model.
+
+Execute with "--do-it-for-real"
+
+Exclusion rules:
+
+All the files:
+- .gitmodules
+
+All the lines:
+
+- lines matching "OPenServerless"
+- lines matchins nuvolaris-testing
+- lines matching registry.hub.docker.com/nuvolaris/
+- lines with @nuvolaris.io (email)
+- lines with ghcr.io/nuvolaris/ (ghcr.io references)
+- .go, go.mod and go.sum files matching "github.com/openserverless/*" (github
repo references)
+- lines with assets-global.website-files.com (hosted CDN asset keys: renaming
+ the URL does not rename the remote object). Only the URL is protected; the
+ surrounding text on the same line, such as alt text, must still be rebranded.
+- file build-step.md
+- file olaris/prereq.yml
+
+The same exclusion rules apply when the script is run inside a submodule
+(olaris-op, olaris, testing), which the top-level walk does not enter.
+
diff --git a/rebrander.py b/rebrander.py
new file mode 100755
index 0000000..8a0a200
--- /dev/null
+++ b/rebrander.py
@@ -0,0 +1,451 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Rebrand "nuvolaris" to "openserverless".
+
+Walks all subfolders renaming every folder named with "nuvolaris" in it,
+then every file whose name carries it (both with `git mv`, so the rename is
+staged rather than looking like a delete plus an untracked copy). Renaming
+the files is required, not cosmetic: a file name written inside another file
+is rebranded as plain text, so the reference would otherwise point at a path
+that no longer exists. Then walks all files replacing:
+
+ nuvolaris -> openserverless
+ Nuvolaris -> OpenServerless
+ NUVOLARIS -> OPENSERVERLESS
+
+Any *other* casing (nuVolaris, NuVoLaRiS, ...) is NOT rewritten: it is
+reported as a warning, in the log and on stderr, for a human to handle.
+Pre-existing casings of the new name are accepted without warning when
+they are one of the three common forms (openserverless, OpenServerless,
+OPENSERVERLESS, plus Openserverless); anything else is warned about,
+except the known "OPenServerless" spelling, whose lines are skipped.
+
+A few files are excluded wholesale (.gitmodules anywhere, plus build-step.md
+and olaris/prereq.yml, which carry upstream github URLs that must keep
+resolving). Lines naming external resources that still exist under the old
+name (nuvolaris-testing, registry.hub.docker.com/nuvolaris/, ghcr.io and
+github.com repo references, @nuvolaris.io addresses) are left as they are.
+
+Writes rebrander.log listing the folders and files renamed, every replaced
line as
+<file>:<line>:<newvalue>, and every line left as is by an exclusion rule
+as SKIP:<file>:<line>:<oldvalue>.
+
+By default nothing is modified: the script only reports what it would do.
+Pass --do-it-for-real to actually rename folders and files and rewrite them.
+
+Usage:
+ ./rebrander.py [ROOT] [--do-it-for-real]
+"""
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+
+OLD = "nuvolaris"
+NEW = "openserverless"
+
+# The only two casings that get rewritten; everything else is warned about.
+REPLACEMENTS = {
+ "nuvolaris": "openserverless",
+ "Nuvolaris": "OpenServerless",
+ "NUVOLARIS": "OPENSERVERLESS",
+}
+
+# Casings of the new name accepted as common usage; anything else is warned.
+ACCEPTED_NEW = {"openserverless", "OpenServerless", "OPENSERVERLESS",
+ "Openserverless"}
+
+LOG_NAME = "rebrander.log"
+
+# Never descend into these; renaming/rewriting them corrupts the checkout.
+SKIP_DIRS = {".git", ".hg", ".svn", "node_modules", "__pycache__",
+ ".venv", "venv", ".mypy_cache", ".pytest_cache", ".ruff_cache"}
+
+# Excluded files (spec): .gitmodules holds submodule URLs. Matched by base
+# name, so nested submodule files are covered too.
+SKIP_FILES = {".gitmodules"}
+
+# Excluded files given as a path relative to the root: unlike the above these
+# name one specific file, not every file that happens to share the base name
+# (there are several prereq.yml in the tree; only olaris/ is excluded).
+SKIP_PATHS = {"build-step.md", os.path.join("olaris", "prereq.yml")}
+
+# This script and its own log must not rewrite themselves mid-run.
+SELF_FILES = {LOG_NAME, os.path.basename(__file__)}
+
+# Excluded lines (spec). These are left byte-for-byte as they are and logged
+# under SKIP. The first three apply to every file; the github repo reference
+# is excluded only inside .go files.
+ODD_NEW_RE = re.compile(r"OPenServerless")
+EMAIL_RE = re.compile(r"@nuvolaris\.io", re.IGNORECASE)
+GHCR_RE = re.compile(r"ghcr\.io/nuvolaris/", re.IGNORECASE)
+GO_REPO_RE = re.compile(r"github\.com/nuvolaris/", re.IGNORECASE)
+# Live external resources registered under the old name: a GCP project /
+# cluster and Docker Hub images. Renaming the string does not rename them.
+TESTING_RE = re.compile(r"nuvolaris-testing", re.IGNORECASE)
+DOCKERHUB_RE = re.compile(r"registry\.hub\.docker\.com/nuvolaris/",
re.IGNORECASE)
+
+# Go module files pin the same repo references as the .go sources do.
+GO_MODULE_FILES = {"go.mod", "go.sum"}
+
+ANY_FILE_EXCLUDES = (ODD_NEW_RE, TESTING_RE, DOCKERHUB_RE, EMAIL_RE, GHCR_RE)
+
+MAX_BYTES = 20 * 1024 * 1024 # skip anything bigger; not source text
+
+OLD_ANY = re.compile(OLD, re.IGNORECASE)
+NEW_ANY = re.compile(NEW, re.IGNORECASE)
+
+
+def substitute(text: str):
+ """Replace the two accepted casings.
+
+ Returns (new_text, [odd casings of OLD left untouched]).
+ """
+ odd = []
+
+ def repl(m):
+ word = m.group(0)
+ if word in REPLACEMENTS:
+ return REPLACEMENTS[word]
+ odd.append(word) # unknown casing: leave it alone, warn instead
+ return word
+
+ return OLD_ANY.sub(repl, text), odd
+
+
+def odd_new_casings(text: str):
+ """Occurrences of the new name in a casing we don't consider canonical."""
+ return [m.group(0) for m in NEW_ANY.finditer(text)
+ if m.group(0) not in ACCEPTED_NEW]
+
+
+def line_excluded(path: str, line: str) -> bool:
+ """Spec: skip lines carrying "OPenServerless", a nuvolaris-testing or
+ registry.hub.docker.com/nuvolaris/ reference, a @nuvolaris.io address or
+ a ghcr.io/nuvolaris/ reference, plus github.com/nuvolaris/ inside the go
+ sources and the module files that pin them."""
+ if any(rx.search(line) for rx in ANY_FILE_EXCLUDES):
+ return True
+ base = os.path.basename(path)
+ if not (path.endswith(".go") or base in GO_MODULE_FILES):
+ return False
+ return GO_REPO_RE.search(line) is not None
+
+
+def is_binary(path: str) -> bool:
+ try:
+ with open(path, "rb") as f:
+ chunk = f.read(8000)
+ except OSError:
+ return True
+ return b"\0" in chunk
+
+
+def prune(dirnames):
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
+
+
+def git_toplevel(path: str):
+ """Repo that tracks `path`, or None if it is not under git control.
+
+ Submodules are separate repositories: a `git mv` has to run inside the one
+ that actually tracks the directory, not the outermost checkout.
+ """
+ try:
+ out = subprocess.run(["git", "-C", path, "rev-parse",
"--show-toplevel"],
+ capture_output=True, text=True, check=True)
+ except (OSError, subprocess.CalledProcessError):
+ return None
+ return out.stdout.strip() or None
+
+
+def git_mv(old_path: str, new_path: str):
+ """Move with `git mv` so the rename is staged, not seen as
delete+untracked.
+
+ Returns None on success, else a message. This matters because the run also
+ rewrites .gitignore rules that name the very directories being renamed: an
+ unstaged destination can be swallowed by the rewritten rule and silently
+ dropped from the tree.
+
+ Used for both folders and files; git resolves the destination against the
+ repo that tracks the source, so nested submodules are handled correctly.
+ """
+ repo = git_toplevel(os.path.dirname(old_path))
+ if repo is None:
+ return "not a git repository"
+ try:
+ r = subprocess.run(["git", "-C", repo, "mv", old_path, new_path],
+ capture_output=True, text=True)
+ except OSError as e:
+ return str(e)
+ if r.returncode == 0:
+ return None
+ return (r.stderr or r.stdout).strip() or f"git mv exited {r.returncode}"
+
+
+def rename_folders(root: str, dry_run: bool, warnings):
+ """Rename matching directories with `git mv`, deepest-first.
+
+ Deepest-first keeps every stored path valid: renaming a parent before its
+ child would invalidate the child's path.
+ """
+ renamed = []
+ targets = []
+ for dirpath, dirnames, _ in os.walk(root):
+ prune(dirnames)
+ for d in dirnames:
+ if OLD_ANY.search(d):
+ targets.append(os.path.join(dirpath, d))
+
+ for old_path in sorted(targets, key=lambda p: p.count(os.sep),
reverse=True):
+ parent, name = os.path.split(old_path)
+ new_name, odd = substitute(name)
+ rel_old = os.path.relpath(old_path, root)
+ for w in odd:
+ warnings.append((rel_old, 0, f"folder name has unhandled casing
{w!r}"))
+ if new_name == name:
+ continue
+ new_path = os.path.join(parent, new_name)
+ if os.path.exists(new_path):
+ warnings.append((rel_old, 0, f"rename target already exists:
{new_name!r}"))
+ continue
+ if not dry_run:
+ err = git_mv(old_path, new_path)
+ if err:
+ warnings.append((rel_old, 0, f"git mv failed: {err}"))
+ continue
+ renamed.append((rel_old, os.path.relpath(new_path, root)))
+ return renamed
+
+
+def rename_files(root: str, dry_run: bool, warnings):
+ """Rename matching files with `git mv`.
+
+ Runs after rename_folders so every path here is the post-rename one; a
+ file inside a renamed directory is reached under its new parent.
+
+ This is not optional: file names written inside other files are rebranded
+ as plain text, so leaving the files themselves alone would point those
+ references at paths that no longer exist.
+ """
+ renamed = []
+ targets = []
+ for dirpath, dirnames, filenames in os.walk(root):
+ prune(dirnames)
+ for fn in filenames:
+ if fn in SELF_FILES:
+ continue
+ if OLD_ANY.search(fn):
+ targets.append(os.path.join(dirpath, fn))
+
+ for old_path in sorted(targets):
+ parent, name = os.path.split(old_path)
+ new_name, odd = substitute(name)
+ rel_old = os.path.relpath(old_path, root)
+ for w in odd:
+ warnings.append((rel_old, 0, f"file name has unhandled casing
{w!r}"))
+ if new_name == name:
+ continue
+ if os.path.islink(old_path):
+ warnings.append((rel_old, 0, "symlink not renamed"))
+ continue
+ new_path = os.path.join(parent, new_name)
+ if os.path.exists(new_path):
+ warnings.append((rel_old, 0, f"rename target already exists:
{new_name!r}"))
+ continue
+ if not dry_run:
+ err = git_mv(old_path, new_path)
+ if err:
+ warnings.append((rel_old, 0, f"git mv failed: {err}"))
+ continue
+ renamed.append((rel_old, os.path.relpath(new_path, root)))
+ return renamed
+
+
+def rewrite_files(root: str, dry_run: bool, warnings):
+ """Replace occurrences inside files; return per-file changed line pairs."""
+ changes = [] # (relpath, [(lineno, newvalue), ...])
+ skipped = [] # (relpath, [(lineno, oldvalue), ...]) - excluded by a rule
+ for dirpath, dirnames, filenames in os.walk(root):
+ prune(dirnames)
+ for fn in filenames:
+ if fn in SELF_FILES or fn in SKIP_FILES:
+ continue
+ path = os.path.join(dirpath, fn)
+ rel = os.path.relpath(path, root)
+ if rel in SKIP_PATHS:
+ continue
+ if os.path.islink(path):
+ continue
+ try:
+ if os.path.getsize(path) > MAX_BYTES:
+ continue
+ except OSError:
+ continue
+ if is_binary(path):
+ continue
+ try:
+ with open(path, "r", encoding="utf-8", newline="") as f:
+ lines = f.readlines()
+ except (UnicodeDecodeError, OSError):
+ continue
+
+ hits = []
+ skips = []
+ out = []
+ for i, line in enumerate(lines, 1):
+ if line_excluded(rel, line):
+ out.append(line)
+ # Only worth reporting if the rule actually held something
+ # back; an excluded line with nothing to replace is noise.
+ if substitute(line)[0] != line:
+ skips.append((i, line.rstrip("\r\n")))
+ continue
+
+ new_line, odd = substitute(line)
+ out.append(new_line)
+
+ for w in odd:
+ warnings.append((rel, i, f"unhandled casing {w!r} left
as-is"))
+ for w in odd_new_casings(line):
+ warnings.append((rel, i, f"non-canonical {NEW!r} casing
{w!r}"))
+
+ if new_line != line:
+ hits.append((i, new_line.rstrip("\r\n")))
+
+ if skips:
+ skipped.append((rel, skips))
+ if not hits:
+ continue
+ if not dry_run:
+ try:
+ with open(path, "w", encoding="utf-8", newline="") as f:
+ f.writelines(out)
+ except OSError as e:
+ warnings.append((rel, 0, f"write failed: {e}"))
+ continue
+ changes.append((rel, hits))
+ return changes, skipped
+
+
+def write_log(root: str, renamed, renamed_files, changes, skipped, warnings,
+ dry_run: bool):
+ log_path = os.path.join(root, LOG_NAME)
+ total_lines = sum(len(h) for _, h in changes)
+ total_skipped = sum(len(h) for _, h in skipped)
+ bar = "=" * 70
+ with open(log_path, "w", encoding="utf-8") as log:
+ log.write(f"rebrander: {OLD} -> {NEW}\n")
+ log.write(f"root: {os.path.abspath(root)}\n")
+ if dry_run:
+ log.write("mode: DRY RUN - nothing modified "
+ "(pass --do-it-for-real to apply)\n")
+ else:
+ log.write("mode: APPLIED - changes written to disk\n")
+ log.write(f"folders renamed: {len(renamed)}\n")
+ log.write(f"files renamed: {len(renamed_files)}\n")
+ log.write(f"files changed: {len(changes)}\n")
+ log.write(f"lines replaced: {total_lines}\n")
+ log.write(f"lines left as is:{total_skipped}\n")
+ log.write(f"warnings: {len(warnings)}\n")
+
+ log.write(f"\n{bar}\nFOLDERS RENAMED\n{bar}\n")
+ if renamed:
+ for old, new in renamed:
+ log.write(f" {old}\n-> {new}\n")
+ else:
+ log.write(" (none)\n")
+
+ log.write(f"\n{bar}\nFILES RENAMED\n{bar}\n")
+ if renamed_files:
+ for old_rel, new_rel in renamed_files:
+ log.write(f" {old_rel}\n-> {new_rel}\n")
+ else:
+ log.write(" (none)\n")
+
+ log.write(f"\n{bar}\nWARNINGS\n{bar}\n")
+ if warnings:
+ for rel, lineno, msg in sorted(warnings):
+ where = f"{rel}:{lineno}" if lineno else rel
+ log.write(f" {where}: {msg}\n")
+ else:
+ log.write(" (none)\n")
+
+ log.write(f"\n{bar}\nLINES REPLACED\n{bar}\n")
+ if changes:
+ for relpath, hits in sorted(changes):
+ for lineno, after in hits:
+ log.write(f"{relpath}:{lineno}:{after}\n")
+ else:
+ log.write(" (none)\n")
+
+ log.write(f"\n{bar}\nLINES LEFT AS IS (excluded)\n{bar}\n")
+ if skipped:
+ for relpath, hits in sorted(skipped):
+ for lineno, before in hits:
+ log.write(f"SKIP:{relpath}:{lineno}:{before}\n")
+ else:
+ log.write(" (none)\n")
+ return log_path, total_lines, total_skipped
+
+
+def main():
+ ap = argparse.ArgumentParser(description=f"Rebrand {OLD} -> {NEW}")
+ ap.add_argument("root", nargs="?", default=".", help="root folder
(default: .)")
+ ap.add_argument("--do-it-for-real", action="store_true", dest="for_real",
+ help="actually rename and rewrite; without it nothing is "
+ "modified and only the log is produced")
+ args = ap.parse_args()
+ dry_run = not args.for_real
+
+ root = os.path.abspath(args.root)
+ if not os.path.isdir(root):
+ sys.exit(f"not a directory: {root}")
+
+ warnings = []
+ # Folders first, then files, so every `git mv` targets a path that still
+ # exists; the file walk then sees the final paths in the log.
+ renamed = rename_folders(root, dry_run, warnings)
+ renamed_files = rename_files(root, dry_run, warnings)
+ changes, skipped = rewrite_files(root, dry_run, warnings)
+ log_path, total_lines, total_skipped = write_log(
+ root, renamed, renamed_files, changes, skipped, warnings, dry_run)
+
+ for rel, lineno, msg in sorted(warnings)[:20]:
+ where = f"{rel}:{lineno}" if lineno else rel
+ print(f"WARN {where}: {msg}", file=sys.stderr)
+ if len(warnings) > 20:
+ print(f"WARN ... and {len(warnings) - 20} more (see {LOG_NAME})",
+ file=sys.stderr)
+
+ print(f"folders renamed: {len(renamed)}")
+ print(f"files renamed: {len(renamed_files)}")
+ print(f"files changed: {len(changes)}")
+ print(f"lines replaced: {total_lines}")
+ print(f"lines left as is:{total_skipped}")
+ print(f"warnings: {len(warnings)}")
+ print(f"log: {log_path}")
+ if dry_run:
+ print("(dry run - nothing was modified; "
+ "pass --do-it-for-real to apply)")
+
+
+if __name__ == "__main__":
+ main()