Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5b1a314
feat: add model for tracking git mirror of a codebase
sgfost Jan 9, 2025
a46013b
feat: add codebase git repository fs api
sgfost Jan 9, 2025
895eb83
feat: add github integration api and mirroring tasks
sgfost Jan 9, 2025
deb2300
feat: github integration ui and mirroring feature
sgfost Jan 9, 2025
6e94713
feat: rework git integration models and github/fs apis
sgfost Jan 25, 2025
a7df0f5
feat: views/routing for github integration config
sgfost Jan 28, 2025
c6dcf9d
feat: add github sync configuration page
sgfost Jan 29, 2025
b99c161
feat: add create/update functionality to the gh sync config
sgfost Jan 29, 2025
2aee72c
feat: add guidance for connecting acct in gh sync config
sgfost Jan 30, 2025
13bc7a1
feat: add new feature overview/explainer page for gh sync
sgfost Jan 30, 2025
7592359
feat: add external/archived release type
sgfost Feb 4, 2025
ed4c8ee
feat(WIP): user-owned repos webhook + workflows
sgfost Feb 7, 2025
e15a013
feat: user-owned repos are working
sgfost Feb 7, 2025
cd86ac5
feat: remove github sync org option, refine UI and overview/help page
sgfost Feb 12, 2025
240f757
fix: rename github -> CML process 'archiving' -> 'importing'
sgfost Feb 13, 2025
088cda7
fix: improve syncing workflow and transparency
sgfost Feb 14, 2025
d66d1c8
feat(WIP): github release importing
sgfost Feb 21, 2025
6c88830
feat(WIP): add release FS API for imported releases
sgfost Mar 5, 2025
aba9006
feat: github release importing
sgfost Mar 7, 2025
1ef8a55
feat: imported-specific release editor UI
sgfost Mar 19, 2025
2a0a9f5
feat: extract metadata from imported releases to populate CML release
sgfost Mar 21, 2025
0cc94e1
fix: add configurable github app webhook secret
sgfost Mar 27, 2025
edefe6b
feat: add gh sync config settings model, clarify overview
sgfost Jun 12, 2025
d05742d
feat: improve gh sync configuration UI
sgfost Jun 13, 2025
7500569
refactor: github install status method for any user
sgfost Jun 20, 2025
f12b313
feat: add archive preview in release editor for published releases
sgfost Jun 23, 2025
874c71c
fix: resolve tags when importing releases
sgfost Jun 23, 2025
3333ec9
feat: improve github sync overview page
sgfost Jun 24, 2025
76e6db9
feat: slim down model library preamble thing
sgfost Jun 25, 2025
2b10004
feat: github sync config app link and soft feature flag
sgfost Jun 25, 2025
6b45b0e
fix: clarify and correct coerce_codemeta/cff
sgfost Jun 26, 2025
2f7a1b6
fix: imported release peer review
sgfost Jun 26, 2025
489024c
test: add github sync importer/webhook/etc tests
sgfost Jun 27, 2025
8caf811
fix: dont try to create review draft copy of unpublished releases
sgfost Jul 9, 2025
98952ef
fix: conditionally send diff versions of revisions requested email
sgfost Jul 9, 2025
853d0a2
feat: add some indicators for imported releases
sgfost Jul 9, 2025
5dce1eb
feat: switch to a manual release syncing workflow
sgfost Dec 1, 2025
0b37f09
refactor: simplify outbound git ref tracking
sgfost Dec 3, 2025
8c0f5f1
fix: prevent recursive update loops when building git repos
sgfost Dec 5, 2025
401e11b
feat: better github integration release management
sgfost Dec 10, 2025
3dc08da
fix: migration, mark github integration info page as outdated
sgfost Dec 11, 2025
e855ec0
feat: another gh integration overview page rework
sgfost Dec 12, 2025
273142e
content: initial github overview page with placeholder videos
sgfost Dec 15, 2025
b71d7f2
fix: imported release metadata extraction + ui fixes
sgfost Dec 16, 2025
1f1c10e
feat: more info for imported releases under review
sgfost Dec 17, 2025
ecc1d3a
fix: additional note on "pushing" to github
sgfost Dec 17, 2025
a70cfc0
fix: improve messaging and feedback for github connections
sgfost Dec 29, 2025
efd09af
fix: include git repositories in borg repo
sgfost Dec 30, 2025
29cfbeb
test: update github integration tests
sgfost Dec 31, 2025
021838c
fix: validate integration app access before connecting to repos
sgfost Jan 5, 2026
83a46df
fix: fail main branch push jobs when they fail
sgfost Jan 5, 2026
bd273b2
feat: gh integration feature flag and repo disconnection button
sgfost Jan 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ SECRETS_DIR=${BUILD_DIR}/secrets
DB_PASSWORD_PATH=${SECRETS_DIR}/db_password
PGPASS_PATH=${SECRETS_DIR}/.pgpass
SECRET_KEY_PATH=${SECRETS_DIR}/django_secret_key
EXT_SECRETS=hcaptcha_secret github_client_secret orcid_client_secret discourse_api_key discourse_sso_secret mail_api_key datacite_api_password youtube_api_key
EXT_SECRETS=hcaptcha_secret github_client_secret orcid_client_secret discourse_api_key discourse_sso_secret mail_api_key datacite_api_password youtube_api_key github_integration_app_private_key github_integration_app_webhook_secret
GENERATED_SECRETS=$(DB_PASSWORD_PATH) $(PGPASS_PATH) $(SECRET_KEY_PATH)

ENVREPLACE := deploy/scripts/envreplace
Expand Down
6 changes: 6 additions & 0 deletions base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ services:
- django_secret_key
- github_client_secret
- orcid_client_secret
- github_integration_app_private_key
- github_integration_app_webhook_secret
- hcaptcha_secret
- mail_api_key
- youtube_api_key
Expand Down Expand Up @@ -99,6 +101,10 @@ secrets:
file: ./build/secrets/django_secret_key
github_client_secret:
file: ./build/secrets/github_client_secret
github_integration_app_private_key:
file: ./build/secrets/github_integration_app_private_key
github_integration_app_webhook_secret:
file: ./build/secrets/github_integration_app_webhook_secret
hcaptcha_secret:
file: ./build/secrets/hcaptcha_secret
mail_api_key:
Expand Down
6 changes: 6 additions & 0 deletions deploy/conf/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ DATACITE_DRY_RUN="true" # allowed values: "true" or "false"
# youtube api settings
YOUTUBE_CHANNEL_ID=

# github integration app
GITHUB_INTEGRATION_APP_ID=
GITHUB_INTEGRATION_APP_NAME=
GITHUB_INTEGRATION_APP_INSTALLATION_ID=
GITHUB_MODEL_LIBRARY_ORG_NAME=

# test
TEST_USER_ID=10000000
TEST_USERNAME=__test_user__
Expand Down
8 changes: 8 additions & 0 deletions django/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,14 @@ def github_url(self):
"""
return self.get_social_account_profile_url("github")

@property
def github_username(self):
github_account = self.get_social_account("github")
if github_account:
return github_account.extra_data.get("login")
else:
return None

def get_social_account_profile_url(self, provider_name):
social_acct = self.get_social_account(provider_name)
if social_acct:
Expand Down
14 changes: 14 additions & 0 deletions django/core/settings/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,20 @@ def set_environment(env: Environment):
GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID", "")
GITHUB_CLIENT_SECRET = read_secret("github_client_secret")

GITHUB_INTEGRATION_APP_ID = int(os.getenv("GITHUB_INTEGRATION_APP_ID") or 0)
GITHUB_INTEGRATION_APP_NAME = os.getenv("GITHUB_INTEGRATION_APP_NAME", "")
GITHUB_INTEGRATION_APP_PRIVATE_KEY = read_secret("github_integration_app_private_key")
GITHUB_INTEGRATION_APP_INSTALLATION_ID = int(
os.getenv("GITHUB_INTEGRATION_APP_INSTALLATION_ID") or 0
)
GITHUB_INTEGRATION_APP_WEBHOOK_SECRET = read_secret(
"github_integration_app_webhook_secret"
)
GITHUB_MODEL_LIBRARY_ORG_NAME = os.getenv("GITHUB_MODEL_LIBRARY_ORG_NAME", "")
GITHUB_INDIVIDUAL_FILE_SIZE_LIMIT = int(
os.getenv("GITHUB_INDIVIDUAL_FILE_SIZE_LIMIT") or 100 * 1024 * 1024
)

TEST_BASIC_AUTH_PASSWORD = os.getenv("TEST_BASIC_AUTH_PASSWORD", "test password")
TEST_USER_ID = os.getenv("TEST_USER_ID", 1000000)
TEST_USERNAME = os.getenv("TEST_USERNAME", "__test_user__")
Expand Down
8 changes: 8 additions & 0 deletions django/core/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,13 @@ def initialize_test_shared_folders():
)


def clear_test_shared_folder(dir=settings.REPOSITORY_ROOT):
for fs in os.scandir(dir):
if fs.is_dir():
shutil.rmtree(os.path.join(dir, fs.name), ignore_errors=True)
elif fs.is_file():
os.remove(os.path.join(dir, fs.name))


def destroy_test_shared_folders():
shutil.rmtree(settings.SHARE_DIR, ignore_errors=True)
2 changes: 2 additions & 0 deletions django/curator/fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
def fsck(queryset):
results = OrderedDict()
for release in queryset:
if release.is_imported:
continue
rfsc = CodebaseReleaseFileConsistencyChecker(release)
errors = rfsc.check()
if errors:
Expand Down
28 changes: 22 additions & 6 deletions django/curator/invoke_tasks/borg.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

DEFAULT_LIBRARY_BASENAME = os.path.basename(settings.LIBRARY_ROOT)
DEFAULT_MEDIA_BASENAME = os.path.basename(settings.MEDIA_ROOT)
DEFAULT_REPOSITORY_BASENAME = os.path.basename(settings.REPOSITORY_ROOT)


@task(aliases=["init"])
Expand Down Expand Up @@ -48,12 +49,14 @@ def backup(ctx):
archive = "{utcnow}"
library = os.path.relpath(settings.LIBRARY_ROOT, share)
media = os.path.relpath(settings.MEDIA_ROOT, share)
repository = os.path.relpath(settings.REPOSITORY_ROOT, share)
database = os.path.relpath(os.path.join(settings.BACKUP_ROOT, "latest"), share)

error_msgs = []
for p in (
settings.LIBRARY_ROOT,
settings.MEDIA_ROOT,
settings.REPOSITORY_ROOT,
os.path.join(settings.BACKUP_ROOT, "latest"),
):
if not os.path.exists(p):
Expand All @@ -63,44 +66,57 @@ def backup(ctx):

with ctx.cd(share):
ctx.run(
f'borg create --stats --compression lz4 {repo}::"{archive}" {library} {media} {database}',
f'borg create --stats --compression lz4 {repo}::"{archive}" {library} {media} {repository} {database}',
echo=True,
env=environment(),
)


def delete_latest_uncompressed_backup(
src_library=DEFAULT_LIBRARY_BASENAME, src_media=DEFAULT_MEDIA_BASENAME
src_library=DEFAULT_LIBRARY_BASENAME,
src_media=DEFAULT_MEDIA_BASENAME,
src_repository=DEFAULT_REPOSITORY_BASENAME,
):
latest_dest_library = os.path.join(settings.PREVIOUS_SHARE_ROOT, src_library)
latest_dest_media = os.path.join(settings.PREVIOUS_SHARE_ROOT, src_media)
latest_dest_repository = os.path.join(settings.PREVIOUS_SHARE_ROOT, src_repository)

shutil.rmtree(latest_dest_library, ignore_errors=True)
shutil.rmtree(latest_dest_media, ignore_errors=True)
shutil.rmtree(latest_dest_repository, ignore_errors=True)


def rotate_library_and_media_files(
working_directory,
src_library=DEFAULT_LIBRARY_BASENAME,
src_media=DEFAULT_MEDIA_BASENAME,
src_repository=DEFAULT_REPOSITORY_BASENAME,
):
"""
Rotate the current library and media files
Rotate the current library, media, and repository files

Current library and media files are moved to the '.latest' folder in case of problems during the restore process.
Current library, media, and repository files are moved to the '.latest' folder in case of problems during the restore process.
Files in .latest can be deleted if the restore was successful
"""
print("rotating library and media files")
delete_latest_uncompressed_backup(src_library=src_library, src_media=src_media)
print("rotating library, media, and repository files")
delete_latest_uncompressed_backup(
src_library=src_library, src_media=src_media, src_repository=src_repository
)

os.makedirs(settings.PREVIOUS_SHARE_ROOT, exist_ok=True)
if os.path.exists(settings.LIBRARY_ROOT):
shutil.move(settings.LIBRARY_ROOT, settings.PREVIOUS_SHARE_ROOT)
if os.path.exists(settings.MEDIA_ROOT):
shutil.move(settings.MEDIA_ROOT, settings.PREVIOUS_SHARE_ROOT)
if os.path.exists(settings.REPOSITORY_ROOT):
shutil.move(settings.REPOSITORY_ROOT, settings.PREVIOUS_SHARE_ROOT)

shutil.move(os.path.join(working_directory, src_library), settings.SHARE_DIR)
shutil.move(os.path.join(working_directory, src_media), settings.SHARE_DIR)
# repository dir may not exist in older backups
src_repository_path = os.path.join(working_directory, src_repository)
if os.path.exists(src_repository_path):
shutil.move(src_repository_path, settings.SHARE_DIR)


def environment():
Expand Down
4 changes: 2 additions & 2 deletions django/curator/tests/test_dump_restore.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from core.tests.base import EventFactory, JobFactory
from library.fs import import_archive
from library.models import Codebase
from library.tests.base import CodebaseFactory
from library.tests.base import CodebaseFactory, TEST_SAMPLES_DIR

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -51,7 +51,7 @@ def setUp(self):
fs_api = self.release.get_fs_api()
import_archive(
codebase_release=self.release,
nested_code_folder_name="library/tests/archives/nestedcode",
nested_code_folder_name=TEST_SAMPLES_DIR / "archives" / "nestedcode",
fs_api=fs_api,
)

Expand Down
Loading
Loading