Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
124 changes: 124 additions & 0 deletions bases/rsptx/author_server_api/IMPLEMENTATION_INSTRUCTIONS.md
Original file line number Diff line number Diff line change
@@ -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 %}
<div class="commit-info" style="margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 4px; border-left: 4px solid #007bff;">
<h6 style="margin-bottom: 5px; color: #495057;">Build Version Info</h6>
<small style="font-family: monospace; color: #6c757d;">
Built from commit: <strong>{{ git_commit_info.hash }}</strong>
(source: {{ git_commit_info.source }})
</small>
</div>
{% 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
121 changes: 121 additions & 0 deletions bases/rsptx/author_server_api/git_utils.py
Original file line number Diff line number Diff line change
@@ -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/<branch> 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
}