diff --git a/bases/rsptx/author_server_api/IMPLEMENTATION_INSTRUCTIONS.md b/bases/rsptx/author_server_api/IMPLEMENTATION_INSTRUCTIONS.md new file mode 100644 index 00000000..a9b7f481 --- /dev/null +++ b/bases/rsptx/author_server_api/IMPLEMENTATION_INSTRUCTIONS.md @@ -0,0 +1,124 @@ +# Implementation Instructions for Git Commit Hash Display + +This document provides instructions on how to integrate the git commit hash functionality into the Runestone author server to address issue #733. + +## Files Added + +### 1. git_utils.py +This module provides utility functions for retrieving the current git commit hash using multiple fallback methods: +- Environment variable `GIT_COMMIT_HASH` (useful for CI/CD) +- Git command execution +- Reading from `.git` directory files + +## Integration Required + +### 1. Modify main.py + +Add the following import after the existing Local App imports: + +```python +from rsptx.author_server_api.git_utils import get_git_commit_info +``` + +Add a new API endpoint before the final endpoint: + +```python +@app.get("/author/commit_info") +async def get_commit_info(request: Request, user=Depends(auth_manager)): + """ + Get git commit hash information for debugging purposes. + + This endpoint returns the current git commit hash that the server was built from, + helping with debugging and version tracking as requested in issue #733. + """ + commit_info = get_git_commit_info() + return JSONResponse(commit_info) +``` + +Modify the home endpoint to include commit info in the template context. Find the `@app.get("/author/")` endpoint and update it: + +```python +@app.get("/author/") +async def home(request: Request, user=Depends(auth_manager)): + print(f"{request.state.user} OR user = {user}") + course = await fetch_course(user.course_name) + if user: + if not await is_author(user.id): + return RedirectResponse(url="/notauthorized") + if user: + name = user.first_name + book_list = await fetch_books_by_author(user.username) + book_list = [b.Library for b in book_list if b.Library is not None] + else: + name = "unknown person" + book_list = [] + # redirect them back somewhere.... + + # Add git commit info for debugging + git_info = get_git_commit_info() + + return templates.TemplateResponse( + "author/home.html", + context={ + "request": request, + "name": name, + "book_list": book_list, + "course": course, + "git_commit_info": git_info, # Add this line + }, + ) +``` + +### 2. Template Updates (Future) + +To display the commit hash in the Author Tools page, the `author/home.html` template would need to be updated to include: + +```html +{% if git_commit_info.available %} +
+
Build Version Info
+ + Built from commit: {{ git_commit_info.hash }} + (source: {{ git_commit_info.source }}) + +
+{% endif %} +``` + +### 3. Book Server Integration (Future Enhancement) + +For embedding the commit hash in books themselves, similar integration would be needed in: +- `bases/rsptx/book_server_api/main.py` +- Book building processes in the worker functions +- Book templates to display version info + +## Environment Variable Support + +For production deployments, set the `GIT_COMMIT_HASH` environment variable during the build process: + +```bash +# In your build script or CI/CD pipeline +export GIT_COMMIT_HASH=$(git rev-parse --short HEAD) +``` + +This ensures the commit hash is available even when the `.git` directory is not present in production containers. + +## API Usage + +Once implemented, the commit info will be available via: +- GET `/author/commit_info` - Returns JSON with git commit information +- Author home page will display the commit hash in the UI + +## Testing + +To test the functionality: +1. Check the API endpoint: `curl http://localhost:8000/author/commit_info` +2. Visit the author home page to see the commit hash displayed +3. Test with environment variable: `GIT_COMMIT_HASH=abc1234 python -m uvicorn main:app` + +## Benefits + +- **Debugging**: Easily identify which version of the code is running +- **Version tracking**: Know exactly what commit books were built from +- **Cache troubleshooting**: Identify when servers haven't been updated +- **Multiple fallbacks**: Works in development, CI/CD, and production environments diff --git a/bases/rsptx/author_server_api/git_utils.py b/bases/rsptx/author_server_api/git_utils.py new file mode 100644 index 00000000..8b1d16bf --- /dev/null +++ b/bases/rsptx/author_server_api/git_utils.py @@ -0,0 +1,121 @@ +# ************************************************ +# Git Utilities for displaying commit information +# ************************************************ +# This module provides utilities for getting git commit information +# to help with debugging and version tracking in Runestone books. + +import os +import subprocess +from typing import Optional + + +def get_git_commit_hash() -> Optional[str]: + """ + Get the current git commit hash. + + This function attempts to get the git commit hash using multiple methods: + 1. From environment variable GIT_COMMIT_HASH (useful for CI/CD) + 2. From git command if available + 3. From .git/refs/heads/ file if .git directory exists + + Returns: + str: The git commit hash (short form - first 7 characters) or None if not available + """ + # Method 1: Check environment variable (useful for CI/CD environments) + commit_hash = os.environ.get('GIT_COMMIT_HASH') + if commit_hash: + return commit_hash[:7] # Return short hash + + # Method 2: Try using git command + try: + result = subprocess.run( + ['git', 'rev-parse', '--short', 'HEAD'], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode == 0: + return result.stdout.strip() + except (subprocess.SubprocessError, FileNotFoundError, subprocess.TimeoutExpired): + pass + + # Method 3: Try reading from .git directory directly + try: + # Get the current branch + git_head_file = '.git/HEAD' + if os.path.exists(git_head_file): + with open(git_head_file, 'r') as f: + head_content = f.read().strip() + + # If HEAD contains a reference to a branch + if head_content.startswith('ref: '): + branch_ref = head_content[5:] # Remove 'ref: ' prefix + branch_file = os.path.join('.git', branch_ref) + if os.path.exists(branch_file): + with open(branch_file, 'r') as f: + commit_hash = f.read().strip() + return commit_hash[:7] # Return short hash + else: + # HEAD contains the commit hash directly (detached HEAD) + return head_content[:7] + except (OSError, IOError): + pass + + return None + + +def get_git_commit_info() -> dict: + """ + Get comprehensive git commit information. + + Returns: + dict: Dictionary containing git information including: + - hash: commit hash (short) + - available: whether git info is available + - source: source of the information (env, git_cmd, git_file) + """ + commit_hash = None + source = None + + # Try environment variable first + if os.environ.get('GIT_COMMIT_HASH'): + commit_hash = os.environ['GIT_COMMIT_HASH'][:7] + source = 'env' + else: + # Try git command + try: + result = subprocess.run( + ['git', 'rev-parse', '--short', 'HEAD'], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode == 0: + commit_hash = result.stdout.strip() + source = 'git_cmd' + except (subprocess.SubprocessError, FileNotFoundError, subprocess.TimeoutExpired): + # Try reading from .git files + try: + git_head_file = '.git/HEAD' + if os.path.exists(git_head_file): + with open(git_head_file, 'r') as f: + head_content = f.read().strip() + + if head_content.startswith('ref: '): + branch_ref = head_content[5:] + branch_file = os.path.join('.git', branch_ref) + if os.path.exists(branch_file): + with open(branch_file, 'r') as f: + commit_hash = f.read().strip()[:7] + source = 'git_file' + else: + commit_hash = head_content[:7] + source = 'git_file' + except (OSError, IOError): + pass + + return { + 'hash': commit_hash, + 'available': commit_hash is not None, + 'source': source + }