From 0e2cdc96d761e5baa6c3b48808bfd0bdace6d66d Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 11:45:14 -0400 Subject: [PATCH 01/12] add some database IO functions to lib.py for this Signed-off-by: Adrian Edwards --- collectoss/application/db/lib.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/collectoss/application/db/lib.py b/collectoss/application/db/lib.py index c5394365d..edd29d4ff 100644 --- a/collectoss/application/db/lib.py +++ b/collectoss/application/db/lib.py @@ -109,7 +109,25 @@ def get_repo_by_repo_id(repo_id): repo = execute_session_query(query, 'one') return repo - + +def get_clone_path_by_repo_id(repo_id: int) -> Optional[str]: + + with get_session() as session: + + query = session.query(CollectionStatus).filter(CollectionStatus.repo_id == repo_id) + return query.one_or_none().facade_clone_path + +def set_clone_path_by_repo_id(repo_id: int, clone_path: str): + + with get_session() as session: + + query = session.query(CollectionStatus).filter(CollectionStatus.repo_id == repo_id) + entry = query.one_or_none() + if entry: + entry.facade_clone_path = clone_path + session.commit() + + def get_github_repo_by_src_id(src_id): with get_session() as session: From 6978bcb7422a9bae36aa7cef79d0bff9b13279a3 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 11:46:20 -0400 Subject: [PATCH 02/12] add new column to operations table for this Signed-off-by: Adrian Edwards --- .../application/db/models/operations.py | 1 + .../versions/45_add_facade_path_column.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 collectoss/application/schema/alembic/versions/45_add_facade_path_column.py diff --git a/collectoss/application/db/models/operations.py b/collectoss/application/db/models/operations.py index 19eff3bae..d39aa6841 100644 --- a/collectoss/application/db/models/operations.py +++ b/collectoss/application/db/models/operations.py @@ -1216,6 +1216,7 @@ class CollectionStatus(Base): facade_status = Column(String,nullable=False, server_default=text("'Pending'")) facade_data_last_collected = Column(TIMESTAMP) facade_task_id = Column(String) + facade_clone_path = Column(String, server_default=None) ml_status = Column(String,nullable=False, server_default=text("'Pending'")) ml_data_last_collected = Column(TIMESTAMP) diff --git a/collectoss/application/schema/alembic/versions/45_add_facade_path_column.py b/collectoss/application/schema/alembic/versions/45_add_facade_path_column.py new file mode 100644 index 000000000..ca76ef557 --- /dev/null +++ b/collectoss/application/schema/alembic/versions/45_add_facade_path_column.py @@ -0,0 +1,27 @@ +"""add facade path column + +Revision ID: 45 +Revises: 44 +Create Date: 2026-09-28 11:44:57.974030 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '45' +down_revision = '44' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('collection_status', sa.Column('facade_clone_path', sa.String(), nullable=True), schema='operations') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('collection_status', 'facade_clone_path', schema='operations') + # ### end Alembic commands ### From 8652e21b6a1fce1d29407d10cd48203d5547322b Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 11:46:53 -0400 Subject: [PATCH 03/12] set clone path when fetching a new repo Signed-off-by: Adrian Edwards --- .../tasks/git/util/facade_worker/facade_worker/repofetch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py b/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py index 49f7fae21..1215a944c 100644 --- a/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py +++ b/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py @@ -35,7 +35,7 @@ from collectoss.application.db.models.data import * from collectoss.application.db.models.operations import CollectionStatus from collectoss.application.db.util import execute_session_query -from collectoss.application.db.lib import execute_sql, get_repo_by_repo_git +from collectoss.application.db.lib import execute_sql, get_repo_by_repo_git, set_clone_path_by_repo_id from typing_extensions import deprecated class GitCloneError(Exception): @@ -101,6 +101,9 @@ def git_repo_initialize(facade_helper, session, repo_git): """).bindparams(pathParam=path_identifier, nameParam=repo_name, idParam=row.repo_id) execute_sql(query) + + set_clone_path_by_repo_id(row.repo_id, f"{row.repo_id}-{path_identifier}") + # Check if there will be a storage path collision # If there is a collision, throw an error so that it updates the existing repo instead of trying # to reclone. From 1c0927ee07b59fd2bc36d4a6d3812874a46f42d9 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 11:47:44 -0400 Subject: [PATCH 04/12] use new clone path function to get the path to an existing repo in the initial clone process Signed-off-by: Adrian Edwards --- .../tasks/git/util/facade_worker/facade_worker/repofetch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py b/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py index 1215a944c..3ba850120 100644 --- a/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py +++ b/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py @@ -30,6 +30,8 @@ import os import pathlib import sqlalchemy as s + +from collectoss.tasks.git.util.helpers import get_absolute_clone_path from .utilitymethods import update_repo_log, get_absolute_repo_path from sqlalchemy.orm.exc import NoResultFound from collectoss.application.db.models.data import * @@ -316,8 +318,7 @@ def git_repo_updates(facade_helper, repo_git): # default_branch = '' - absolute_path = get_absolute_repo_path( - facade_helper.repo_base_directory, repo.repo_id, repo.repo_path, repo.repo_name) + absolute_path = get_absolute_clone_path(facade_helper.repo_base_directory, repo.repo_id) while attempt < 2: From fa098ba1b85484b38dbdad4078e31a8e7f322a18 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 11:48:03 -0400 Subject: [PATCH 05/12] deprecate the old function Signed-off-by: Adrian Edwards --- .../git/util/facade_worker/facade_worker/utilitymethods.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/collectoss/tasks/git/util/facade_worker/facade_worker/utilitymethods.py b/collectoss/tasks/git/util/facade_worker/facade_worker/utilitymethods.py index afba70fa2..72816547d 100644 --- a/collectoss/tasks/git/util/facade_worker/facade_worker/utilitymethods.py +++ b/collectoss/tasks/git/util/facade_worker/facade_worker/utilitymethods.py @@ -102,7 +102,8 @@ def trim_author(facade_helper, email): facade_helper.log_activity('Debug',f"Trimmed working author: {email}") -def get_absolute_repo_path(repo_base_dir, repo_id, repo_path,repo_name): +@deprecated("This method of storing the repo path is legacy and is being replaced") +def get_absolute_repo_path(repo_base_dir, repo_id, repo_path, repo_name): return f"{repo_base_dir}{repo_id}-{repo_path}/{repo_name}" From 68a85eaa2a25f3a0127b05ea56551ce967343581 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 11:55:18 -0400 Subject: [PATCH 06/12] swap uses of path fetching function Signed-off-by: Adrian Edwards --- .../git/dependency_libyear_tasks/core.py | 4 ++-- collectoss/tasks/git/dependency_tasks/core.py | 4 ++-- collectoss/tasks/git/facade_tasks.py | 19 ++++++++++--------- collectoss/tasks/git/scc_value_tasks/core.py | 4 ++-- .../facade_worker/facade_worker/repofetch.py | 2 +- .../facade_worker/utilitymethods.py | 5 +++-- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/collectoss/tasks/git/dependency_libyear_tasks/core.py b/collectoss/tasks/git/dependency_libyear_tasks/core.py index 56b8f1a5b..c3b3c2214 100644 --- a/collectoss/tasks/git/dependency_libyear_tasks/core.py +++ b/collectoss/tasks/git/dependency_libyear_tasks/core.py @@ -2,7 +2,7 @@ from collectoss.application.db.models import * from collectoss.application.db.lib import get_value, bulk_insert_dicts, get_repo_by_repo_git from collectoss.tasks.git.dependency_libyear_tasks.libyear_util.util import get_deps_libyear_data -from collectoss.tasks.git.util.facade_worker.facade_worker.utilitymethods import get_absolute_repo_path +from collectoss.tasks.git.util.helpers import get_absolute_clone_path def deps_libyear_model(logger,repo_git): """ Data collection and storage method @@ -15,7 +15,7 @@ def deps_libyear_model(logger,repo_git): repo = get_repo_by_repo_git(repo_git) - absolute_repo_path = get_absolute_repo_path(get_value("Facade", "repo_directory"),repo.repo_id,repo.repo_path,repo.repo_name) + absolute_repo_path = get_absolute_clone_path(get_value("Facade", "repo_directory"), repo.repo_id) #config.get_section("Facade")['repo_directory'] + relative_repo_path#self.config['repo_directory'] + relative_repo_path generate_deps_libyear_data(logger, repo.repo_id, absolute_repo_path) diff --git a/collectoss/tasks/git/dependency_tasks/core.py b/collectoss/tasks/git/dependency_tasks/core.py index 0648231b0..6504e4ace 100644 --- a/collectoss/tasks/git/dependency_tasks/core.py +++ b/collectoss/tasks/git/dependency_tasks/core.py @@ -3,10 +3,10 @@ from collectoss.application.db.models import * from collectoss.application.db.lib import bulk_insert_dicts, get_repo_by_repo_git, get_value from collectoss.application.environment import SystemEnv +from collectoss.tasks.git.util.helpers import get_absolute_clone_path from collectoss.tasks.github.util.github_api_key_handler import GithubApiKeyHandler from collectoss.tasks.git.dependency_tasks.dependency_util import dependency_calculator as dep_calc from collectoss.tasks.util.worker_util import parse_json_from_subprocess_call -from collectoss.tasks.git.util.facade_worker.facade_worker.utilitymethods import get_absolute_repo_path from collectoss.tasks.github.util.github_random_key_auth import GithubRandomKeyAuth from collectoss.tasks.util.metadata_exception import MetadataException @@ -22,7 +22,7 @@ def generate_deps_data(logger, repo_git): repo = get_repo_by_repo_git(repo_git) repo_id = repo.repo_id - path = get_absolute_repo_path(get_value("Facade", "repo_directory"),repo.repo_id,repo.repo_path,repo.repo_name) + path = get_absolute_clone_path(get_value("Facade", "repo_directory"), repo.repo_id) logger.debug(f"This is the deps model repo: {repo_git}.") diff --git a/collectoss/tasks/git/facade_tasks.py b/collectoss/tasks/git/facade_tasks.py index b610e1611..6c669abb3 100644 --- a/collectoss/tasks/git/facade_tasks.py +++ b/collectoss/tasks/git/facade_tasks.py @@ -8,10 +8,11 @@ from collectoss.application.db.lib import get_session, get_repo_by_repo_git, get_repo_by_repo_id, remove_working_commits_by_repo_id_and_hashes, get_working_commits_by_repo_id, facade_bulk_insert_commits, bulk_insert_dicts, get_missing_commit_message_hashes from collectoss.tasks.git.util.facade_worker.facade_worker.utilitymethods import trim_commits -from collectoss.tasks.git.util.facade_worker.facade_worker.utilitymethods import get_absolute_repo_path, get_parent_commits_set, get_existing_commits_set +from collectoss.tasks.git.util.facade_worker.facade_worker.utilitymethods import get_parent_commits_set, get_existing_commits_set from collectoss.tasks.git.util.facade_worker.facade_worker.analyzecommit import analyze_commit from collectoss.tasks.git.util.facade_worker.facade_worker.utilitymethods import get_repo_commit_count, update_facade_scheduling_fields, get_facade_weight_with_commit_count +from collectoss.tasks.git.util.helpers import get_absolute_clone_path from collectoss.tasks.github.facade_github.tasks import * from collectoss.tasks.git.util.facade_worker.facade_worker.config import FacadeHelper from collectoss.tasks.util.collection_state import CollectionState @@ -117,8 +118,8 @@ def trim_commits_post_analysis_facade_task(repo_git): repo = get_repo_by_repo_git(repo_git) #Get the huge list of commits to process. - absolute_path = get_absolute_repo_path(facade_helper.repo_base_directory, repo.repo_id, repo.repo_path,repo.repo_name) - repo_loc = (f"{absolute_path}/.git") + absolute_path = get_absolute_clone_path(facade_helper.repo_base_directory, repo.repo_id) + repo_loc = absolute_path.joinpath(".git") # Grab the parents of HEAD parent_commits = get_parent_commits_set(repo_loc, facade_helper) @@ -190,8 +191,8 @@ def facade_fetch_missing_commit_messages(repo_git): except (TypeError, IndexError): escaped_hash = hash - absolute_path = get_absolute_repo_path(facade_helper.repo_base_directory, repo.repo_id, repo.repo_path, repo.repo_name) - repo_loc = (f"{absolute_path}/.git") + absolute_path = get_absolute_clone_path(facade_helper.repo_base_directory, repo.repo_id) + repo_loc = absolute_path.joinpath(".git") try: commit_message = check_output( @@ -240,8 +241,8 @@ def analyze_commits_in_parallel(repo_git, multithreaded: bool)-> None: repo = get_repo_by_repo_id(repo_id) #Get the huge list of commits to process. - absolute_path = get_absolute_repo_path(facade_helper.repo_base_directory, repo.repo_id, repo.repo_path, repo.repo_name) - repo_loc = (f"{absolute_path}/.git") + absolute_path = get_absolute_clone_path(facade_helper.repo_base_directory, repo.repo_id) + repo_loc = absolute_path.joinpath(".git") # Grab the parents of HEAD parent_commits = get_parent_commits_set(repo_loc, facade_helper) @@ -262,8 +263,8 @@ def analyze_commits_in_parallel(repo_git, multithreaded: bool)-> None: queue = list(missing_commits) logger.info(f"Got to analysis!") - absolute_path = get_absolute_repo_path(facade_helper.repo_base_directory, repo.repo_id, repo.repo_path,repo.repo_name) - repo_loc = (f"{absolute_path}/.git") + absolute_path = get_absolute_clone_path(facade_helper.repo_base_directory, repo.repo_id) + repo_loc = absolute_path.joinpath(".git") pendingCommitRecordsToInsert = [] pendingCommitMessageRecordsToInsert = [] diff --git a/collectoss/tasks/git/scc_value_tasks/core.py b/collectoss/tasks/git/scc_value_tasks/core.py index 770165522..1b2683ca4 100644 --- a/collectoss/tasks/git/scc_value_tasks/core.py +++ b/collectoss/tasks/git/scc_value_tasks/core.py @@ -3,8 +3,8 @@ from collectoss.application.db.models import * from collectoss.application.db.lib import bulk_insert_dicts, get_repo_by_repo_git, get_value from collectoss.application.environment import SystemEnv +from collectoss.tasks.git.util.helpers import get_absolute_clone_path from collectoss.tasks.util.worker_util import parse_json_from_subprocess_call -from collectoss.tasks.git.util.facade_worker.facade_worker.utilitymethods import get_absolute_repo_path def value_model(logger,repo_git): """Runs scc on repo and stores data in database @@ -15,7 +15,7 @@ def value_model(logger,repo_git): repo = get_repo_by_repo_git(repo_git) repo_id = repo.repo_id - path = get_absolute_repo_path(get_value("Facade", "repo_directory"),repo_id,repo.repo_path,repo.repo_name) + path = get_absolute_clone_path(get_value("Facade", "repo_directory"), repo.repo_id) logger.info('Generating value data for repo') logger.info(f"Repo ID: {repo_id}, Path: {path}") diff --git a/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py b/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py index 3ba850120..f9ddf4bcd 100644 --- a/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py +++ b/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py @@ -32,7 +32,7 @@ import sqlalchemy as s from collectoss.tasks.git.util.helpers import get_absolute_clone_path -from .utilitymethods import update_repo_log, get_absolute_repo_path +from .utilitymethods import update_repo_log from sqlalchemy.orm.exc import NoResultFound from collectoss.application.db.models.data import * from collectoss.application.db.models.operations import CollectionStatus diff --git a/collectoss/tasks/git/util/facade_worker/facade_worker/utilitymethods.py b/collectoss/tasks/git/util/facade_worker/facade_worker/utilitymethods.py index 72816547d..dbbabc607 100644 --- a/collectoss/tasks/git/util/facade_worker/facade_worker/utilitymethods.py +++ b/collectoss/tasks/git/util/facade_worker/facade_worker/utilitymethods.py @@ -30,6 +30,7 @@ import os import sqlalchemy as s from collectoss.application.db.models import * +from collectoss.tasks.git.util.helpers import get_absolute_clone_path from .config import FacadeHelper as FacadeHelper from collectoss.tasks.util.worker_util import calculate_date_weight_from_timestamps from collectoss.application.db.lib import execute_sql, fetchall_data_from_sql_text, remove_working_commits_by_repo_id_and_hashes, remove_commits_by_repo_id_and_hashes, get_repo_by_repo_git, get_session @@ -151,8 +152,8 @@ def get_repo_commit_count(logger, facade_helper, repo_git): repo = get_repo_by_repo_git(repo_git) - absolute_path = get_absolute_repo_path(facade_helper.repo_base_directory, repo.repo_id, repo.repo_path,repo.repo_name) - repo_loc = (f"{absolute_path}/.git") + absolute_path = get_absolute_clone_path(facade_helper.repo_base_directory, repo.repo_id) + repo_loc = absolute_path.joinpath(".git") logger.debug(f"loc: {repo_loc}") logger.debug(f"path: {repo.repo_path}") From b709d7f8e54260956c5f4705a7a161cc11ace439 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 12:06:34 -0400 Subject: [PATCH 07/12] commit the new helpers file Signed-off-by: Adrian Edwards --- collectoss/tasks/git/util/helpers.py | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 collectoss/tasks/git/util/helpers.py diff --git a/collectoss/tasks/git/util/helpers.py b/collectoss/tasks/git/util/helpers.py new file mode 100644 index 000000000..52ab6df74 --- /dev/null +++ b/collectoss/tasks/git/util/helpers.py @@ -0,0 +1,57 @@ +from pathlib import Path + +from collectoss.application.db.lib import get_clone_path_by_repo_id, get_repo_by_repo_id, set_clone_path_by_repo_id + +def is_git_repo(path:Path) -> bool: + if not path.exists(): + return False + + gitdir = path.joinpath(".git") + if not gitdir.exists(): + return False + return True + +def get_absolute_clone_path(facade_base_directory: str | Path, repo_id: int) -> Path: + """Returns the absolute path to the clone of the repo on disk + + This method tries several methods to get the clone path and will automatically + update the database with the correct path if a less-than-ideal method is used. + + This method expects that the clone directory already exists. + See `git_repo_initialize` for the method that creates the clone directory. + + Args: + facade_base_directory (str): the configured base directory that all facade clones paths are relative to + repo_id (int): the id of the repo to get the clone path for + """ + + base_dir = Path(facade_base_directory) + + # check if [configured facade base dir] + [path from db operations table] exists and is a git repo (happy path/ideal case) + clone_path = get_clone_path_by_repo_id(repo_id) + if clone_path and is_git_repo(base_dir.joinpath(clone_path)): + return base_dir.joinpath(clone_path) + # if not, use the current path building technique ( [configured facade base dir] + [path from db data table] + [repo name]). if success, rewrite the facade path and return it + repo = get_repo_by_repo_id(repo_id) + + # absolute_path = get_absolute_repo_path(base_dir, repo.repo_id, repo.repo_path,repo.repo_name) + legacy_path = f"{repo_id}-{repo.repo_path}/{repo.repo_name}" + if legacy_path and is_git_repo(base_dir.joinpath(legacy_path)): + set_clone_path_by_repo_id(repo_id, legacy_path) + return base_dir.joinpath(legacy_path) + + # if not, discover it (check just the facade path from step 1, if it contains just one dir, use that and update the database else fail) + discover_path = base_dir.joinpath(f"{repo_id}-{repo.repo_path}") + if discover_path.exists(): + discovered_directories = [x for x in discover_path.iterdir() if x.is_dir()] + if len(discovered_directories) == 1 and is_git_repo(discover_path.joinpath(discovered_directories[0])): + set_clone_path_by_repo_id(repo_id, discover_path.joinpath(discovered_directories[0])) + return discover_path.joinpath(discovered_directories[0]) + + + raise ValueError(f"""No valid git repo path found for repo {repo_id} ({repo.repo_git}). + Attempted paths: + - {clone_path} + - {legacy_path} + - {discover_path} ({len(discovered_directories)} children) + """) \ No newline at end of file From bb5732deba4f9259e81533209bfbc30b00345465 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 14:20:53 -0400 Subject: [PATCH 08/12] add comments to repo path and name tables to indicate their status as a result of this change Note: modifying migrations like this normally isn't recommended, but these migrations only touched one DB (my local one) so its fine Signed-off-by: Adrian Edwards --- collectoss/application/db/models/data.py | 4 +-- .../versions/45_add_facade_path_column.py | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/collectoss/application/db/models/data.py b/collectoss/application/db/models/data.py index aa4d30482..4708d0ebf 100644 --- a/collectoss/application/db/models/data.py +++ b/collectoss/application/db/models/data.py @@ -855,8 +855,8 @@ class Repo(Base): repo_git = Column(String, nullable=False) #TODO: repo_path and repo_name should be generated columns in postgresql - repo_path = Column(String) - repo_name = Column(String) + repo_path = Column(String, comment="deprecated by the addition of facade_clone_path in the collection_status table") + repo_name = Column(String, comment="the current repo name") repo_added = Column( TIMESTAMP(precision=0), nullable=False, server_default=text("CURRENT_TIMESTAMP") ) diff --git a/collectoss/application/schema/alembic/versions/45_add_facade_path_column.py b/collectoss/application/schema/alembic/versions/45_add_facade_path_column.py index ca76ef557..bb0639378 100644 --- a/collectoss/application/schema/alembic/versions/45_add_facade_path_column.py +++ b/collectoss/application/schema/alembic/versions/45_add_facade_path_column.py @@ -18,10 +18,36 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.add_column('collection_status', sa.Column('facade_clone_path', sa.String(), nullable=True), schema='operations') + op.alter_column('repo', 'repo_path', + existing_type=sa.VARCHAR(), + comment='deprecated by the addition of facade_clone_path in the collection_status table', + existing_nullable=True, + existing_server_default=sa.text("'NULL'::character varying"), + schema='data') + op.alter_column('repo', 'repo_name', + existing_type=sa.VARCHAR(), + comment='the current repo name', + existing_nullable=True, + existing_server_default=sa.text("'NULL'::character varying"), + schema='data') # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('repo', 'repo_name', + existing_type=sa.VARCHAR(), + comment=None, + existing_comment='the current repo name', + existing_nullable=True, + existing_server_default=sa.text("'NULL'::character varying"), + schema='data') + op.alter_column('repo', 'repo_path', + existing_type=sa.VARCHAR(), + comment=None, + existing_comment='deprecated by the addition of facade_clone_path in the collection_status table', + existing_nullable=True, + existing_server_default=sa.text("'NULL'::character varying"), + schema='data') op.drop_column('collection_status', 'facade_clone_path', schema='operations') # ### end Alembic commands ### From aab9eee2e4ff02d9705efa5953b3fe0306a9cc19 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 14:21:27 -0400 Subject: [PATCH 09/12] print complete paths if the absolute clone path function throws an exception at the end Signed-off-by: Adrian Edwards --- collectoss/tasks/git/util/helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/collectoss/tasks/git/util/helpers.py b/collectoss/tasks/git/util/helpers.py index 52ab6df74..643b0f3d0 100644 --- a/collectoss/tasks/git/util/helpers.py +++ b/collectoss/tasks/git/util/helpers.py @@ -51,7 +51,7 @@ def get_absolute_clone_path(facade_base_directory: str | Path, repo_id: int) -> raise ValueError(f"""No valid git repo path found for repo {repo_id} ({repo.repo_git}). Attempted paths: - - {clone_path} - - {legacy_path} - - {discover_path} ({len(discovered_directories)} children) + - {base_dir.joinpath(clone_path)} + - {base_dir.joinpath(legacy_path)} + - {base_dir.joinpath(discover_path)} ({len(discovered_directories)} children) """) \ No newline at end of file From 01ed4537f291bdd0e51d7e5520441bc648cda398 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 28 Sep 2026 14:41:25 -0400 Subject: [PATCH 10/12] Bug fixes from AI code review - NameError using potentially uninitialized `discovered_directories` variable in the final exception - incorrect path stored at initial repo clone time - Discovery path stores absolute Path object, not relative str, leading to a potential latent issue if the facade base path is ever changed. - `get_clone_path_by_repo_id` crashes on missing `CollectionStatus` - minor cleanup Assisted-by: Claude Sonnet 4.6 Medium Signed-off-by: Adrian Edwards --- collectoss/application/db/lib.py | 3 ++- .../facade_worker/facade_worker/repofetch.py | 6 +++--- collectoss/tasks/git/util/helpers.py | 16 +++++++++------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/collectoss/application/db/lib.py b/collectoss/application/db/lib.py index edd29d4ff..719b97c4c 100644 --- a/collectoss/application/db/lib.py +++ b/collectoss/application/db/lib.py @@ -115,7 +115,8 @@ def get_clone_path_by_repo_id(repo_id: int) -> Optional[str]: with get_session() as session: query = session.query(CollectionStatus).filter(CollectionStatus.repo_id == repo_id) - return query.one_or_none().facade_clone_path + ops_repo = query.one_or_none() + return ops_repo.facade_clone_path if ops_repo else None def set_clone_path_by_repo_id(repo_id: int, clone_path: str): diff --git a/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py b/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py index f9ddf4bcd..71e94a4d8 100644 --- a/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py +++ b/collectoss/tasks/git/util/facade_worker/facade_worker/repofetch.py @@ -103,9 +103,6 @@ def git_repo_initialize(facade_helper, session, repo_git): """).bindparams(pathParam=path_identifier, nameParam=repo_name, idParam=row.repo_id) execute_sql(query) - - set_clone_path_by_repo_id(row.repo_id, f"{row.repo_id}-{path_identifier}") - # Check if there will be a storage path collision # If there is a collision, throw an error so that it updates the existing repo instead of trying # to reclone. @@ -127,6 +124,8 @@ def git_repo_initialize(facade_helper, session, repo_git): repo_name=:nameParam WHERE repo_id=:idParam """).bindparams(pathParam=path_identifier, nameParam=repo_name, idParam=row.repo_id) + set_clone_path_by_repo_id(row.repo_id, f"{row.repo_id}-{path_identifier}") + execute_sql(query) return @@ -167,6 +166,7 @@ def git_repo_initialize(facade_helper, session, repo_git): # Mark the entire project for an update, so that under normal # circumstances caches are rebuilt only once per waiting period. update_repo_log(logger, facade_helper, row.repo_id, 'Up-to-date') + set_clone_path_by_repo_id(row.repo_id, f"{row.repo_id}-{path_identifier}/{repo_name}") facade_helper.log_activity('Info', f"Cloned {git}") else: diff --git a/collectoss/tasks/git/util/helpers.py b/collectoss/tasks/git/util/helpers.py index 643b0f3d0..c40395714 100644 --- a/collectoss/tasks/git/util/helpers.py +++ b/collectoss/tasks/git/util/helpers.py @@ -34,24 +34,26 @@ def get_absolute_clone_path(facade_base_directory: str | Path, repo_id: int) -> # if not, use the current path building technique ( [configured facade base dir] + [path from db data table] + [repo name]). if success, rewrite the facade path and return it repo = get_repo_by_repo_id(repo_id) - # absolute_path = get_absolute_repo_path(base_dir, repo.repo_id, repo.repo_path,repo.repo_name) + # absolute_path = get_absolute_repo_path(base_dir, repo.repo_id, repo.repo_path,repo.repo_name) legacy_path = f"{repo_id}-{repo.repo_path}/{repo.repo_name}" if legacy_path and is_git_repo(base_dir.joinpath(legacy_path)): set_clone_path_by_repo_id(repo_id, legacy_path) return base_dir.joinpath(legacy_path) - + # if not, discover it (check just the facade path from step 1, if it contains just one dir, use that and update the database else fail) discover_path = base_dir.joinpath(f"{repo_id}-{repo.repo_path}") + discovered_directories = [] if discover_path.exists(): discovered_directories = [x for x in discover_path.iterdir() if x.is_dir()] if len(discovered_directories) == 1 and is_git_repo(discover_path.joinpath(discovered_directories[0])): - set_clone_path_by_repo_id(repo_id, discover_path.joinpath(discovered_directories[0])) - return discover_path.joinpath(discovered_directories[0]) + discovered_repo = discovered_directories[0] # already an absolute Path + set_clone_path_by_repo_id(repo_id, str(discovered_repo.relative_to(base_dir))) + return discovered_repo raise ValueError(f"""No valid git repo path found for repo {repo_id} ({repo.repo_git}). Attempted paths: - - {base_dir.joinpath(clone_path)} + - {base_dir.joinpath(clone_path) if clone_path else '(not set)'} - {base_dir.joinpath(legacy_path)} - - {base_dir.joinpath(discover_path)} ({len(discovered_directories)} children) - """) \ No newline at end of file + - {discover_path} ({len(discovered_directories)} children) + """) From 5ebe29f086fca7c3205ab02b71725e6f6968a9f5 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Tue, 29 Sep 2026 09:17:36 -0400 Subject: [PATCH 11/12] update docs for new facade path resolution and other misc things Assisted-by: Sonnet 4.6 Medium via Cursor Signed-off-by: Adrian Edwards --- .../workers/creating_a_new_worker.rst | 23 +++++++++++++++++++ .../getting-started/collecting-data.rst | 2 +- docs/source/schema/overview.rst | 4 ++-- docs/source/schema/regularly_used_data.rst | 3 +++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/source/development-guide/workers/creating_a_new_worker.rst b/docs/source/development-guide/workers/creating_a_new_worker.rst index a34d73f4b..8f14e9981 100644 --- a/docs/source/development-guide/workers/creating_a_new_worker.rst +++ b/docs/source/development-guide/workers/creating_a_new_worker.rst @@ -8,6 +8,29 @@ Worker Setup 1. If you are hitting an API on a platform like GitHub, or GitLab, follow the pattern in those workers. 2. If you are analyzing CollectOSS data, the `value_worker` provides a good example. +Accessing Cloned Repositories on Disk +-------------------------------------- + +If your task needs to access a repository's local clone (e.g. to run a tool against the source code or git repo), use the ``get_absolute_clone_path`` function from ``collectoss.tasks.git.util.helpers``: + +.. code-block:: python + + from collectoss.tasks.git.util.helpers import get_absolute_clone_path + from collectoss.application.config import SystemConfig + from collectoss.application.db.session import DatabaseSession + from collectoss.application.db import get_engine + + with DatabaseSession(logger, get_engine()) as session: + config = SystemConfig(logger, session) + repo_directory = config.get_value("Facade", "repo_directory") + + repo_path = get_absolute_clone_path(repo_directory, repo_id) + +This function returns a ``pathlib.Path`` object pointing to the root of the cloned repository. It reads the path from ``collection_status.facade_clone_path``. + +.. warning:: + Do **not** construct clone paths manually from ``repo.repo_path`` and ``repo.repo_name``. Those columns are deprecated for path resolution purposes and ``repo.repo_name`` will change when a repository is moved. Always use the ``get_absolute_clone_path`` function. + What are the key sections? ----------------------------------- diff --git a/docs/source/getting-started/collecting-data.rst b/docs/source/getting-started/collecting-data.rst index efc7980f8..1eff9d81e 100644 --- a/docs/source/getting-started/collecting-data.rst +++ b/docs/source/getting-started/collecting-data.rst @@ -70,7 +70,7 @@ Next up are the configuration options specific to some collection tasks (but som ``Facade`` :::::::::::::::::: -- ``repo_directory``, which is the local directory where the facade tasks will clone the repositories it needs to analyze. You should have been prompted for this during installation, but if you need to change it, make sure that it's an absolute path (environment variables like ``$HOME`` are not supported) and that the directory already exists. Defaults to ``repos/``, but it's highly recommended you change this. +- ``repo_directory``, which is the local directory where the facade tasks will clone the repositories it needs to analyze. Make sure that it's an absolute path (environment variables like ``$HOME`` are not yet supported) and that the directory already exists. Defaults to ``repos/``, but it's highly recommended you change this. The exact subdirectory layout within ``repo_directory`` is managed automatically by CollectOSS. - ``limited_run``, toggle between 0 and 1 to determine whether to run all facade tasks or not. Runs all tasks if set to 0 - ``pull_repos``, toggle whether to pull updates from repos after cloning them. If turned off updates to repos will not be collected. - ``run_analysis``, toggle whether to process commit data at all. If turned off will only clone repos and run tertiary tasks such as resolving contributors from any existing commits or collecting dependency relationships. Mainly used for testing. diff --git a/docs/source/schema/overview.rst b/docs/source/schema/overview.rst index 58f0340a9..11b156259 100644 --- a/docs/source/schema/overview.rst +++ b/docs/source/schema/overview.rst @@ -69,9 +69,9 @@ store information including API keys. Some key tables in this schema include: -- ``config``, which contains the config options for the application. Key options include the facade repo_directory as well as primary api key. +- ``config``, which contains a copy of the config options for the application. Key options include the facade repo_directory as well as primary api key. -- ``collection_status``, contains the status of each aspect of data collection for each repo added to CollectOSS. For example, it shows the status of the facade jobs for every repository. +- ``collection_status``, contains the status of each aspect of data collection for each repo added to CollectOSS. For example, it shows the status of the facade jobs for every repository. It also stores the path to each repository's local clone via the ``facade_clone_path`` column, which is the canonical source for resolving where a repo lives on disk. The ``facade_clone_path`` value is a path relative to the configured ``repo_directory`` (facade base path) and is set automatically by CollectOSS when a repository is cloned. SPDX ------------------------------------------------------- diff --git a/docs/source/schema/regularly_used_data.rst b/docs/source/schema/regularly_used_data.rst index aab64a2c8..f33d43001 100644 --- a/docs/source/schema/regularly_used_data.rst +++ b/docs/source/schema/regularly_used_data.rst @@ -235,6 +235,9 @@ Repo A list of all the repositories. + .. note:: + The ``repo_path`` column on this table is **deprecated** and will be removed in a future release. Additonally the ``repo_name`` column is not recommended for locating a repository's clone on disk. The canonical source for clone paths is now ``collection_status.facade_clone_path`` which is accessed via the ``get_absolute_clone_path`` function from ``collectoss.tasks.git.util.helpers``. + .. image:: images/repo.png :width: 200 From 8eb003b03a6c94fe04e633663a08472eee66b3c1 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Tue, 29 Sep 2026 16:21:10 -0400 Subject: [PATCH 12/12] fix typo Signed-off-by: Adrian Edwards --- docs/source/schema/regularly_used_data.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/schema/regularly_used_data.rst b/docs/source/schema/regularly_used_data.rst index f33d43001..1db119f8f 100644 --- a/docs/source/schema/regularly_used_data.rst +++ b/docs/source/schema/regularly_used_data.rst @@ -236,7 +236,7 @@ Repo A list of all the repositories. .. note:: - The ``repo_path`` column on this table is **deprecated** and will be removed in a future release. Additonally the ``repo_name`` column is not recommended for locating a repository's clone on disk. The canonical source for clone paths is now ``collection_status.facade_clone_path`` which is accessed via the ``get_absolute_clone_path`` function from ``collectoss.tasks.git.util.helpers``. + The ``repo_path`` column on this table is **deprecated** and will be removed in a future release. Additionally the ``repo_name`` column is not recommended for locating a repository's clone on disk. The canonical source for clone paths is now ``collection_status.facade_clone_path`` which is accessed via the ``get_absolute_clone_path`` function from ``collectoss.tasks.git.util.helpers``. .. image:: images/repo.png :width: 200