sketch: fix Discord notifications to show all commits in stack
Vibe-coding an improvement to the Discord notifications.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s9b380343c76d40dck
diff --git a/.github/scripts/discord_notify.py b/.github/scripts/discord_notify.py
index 2a794c5..c493f40 100755
--- a/.github/scripts/discord_notify.py
+++ b/.github/scripts/discord_notify.py
@@ -20,24 +20,48 @@
print("Error: DISCORD_WEBHOOK_FOR_COMMITS environment variable is required")
sys.exit(1)
-def get_commit_info():
+def get_commit_range():
+ """Get the range of commits from the GitHub event payload."""
+ event_path = os.environ.get('GITHUB_EVENT_PATH')
+ if not event_path:
+ print("Warning: GITHUB_EVENT_PATH not available, falling back to single commit")
+ return None, None
+
+ try:
+ with open(event_path, 'r') as f:
+ event_data = json.load(f)
+
+ before = event_data.get('before')
+ after = event_data.get('after')
+
+ # GitHub sends '0000000000000000000000000000000000000000' for new branches
+ if before and before != '0000000000000000000000000000000000000000':
+ return before, after
+ else:
+ return None, None
+ except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+ print(f"Warning: Could not parse GitHub event payload: {e}")
+ return None, None
+
+def get_commit_info(commit_sha=None):
"""Extract commit information using git commands."""
+ commit_ref = commit_sha or 'HEAD'
try:
# Get commit message (subject line)
commit_message = subprocess.check_output(
- ['git', 'log', '-1', '--pretty=format:%s'],
+ ['git', 'log', '-1', '--pretty=format:%s', commit_ref],
text=True, stderr=subprocess.DEVNULL
).strip()
# Get commit body (description)
commit_body = subprocess.check_output(
- ['git', 'log', '-1', '--pretty=format:%b'],
+ ['git', 'log', '-1', '--pretty=format:%b', commit_ref],
text=True, stderr=subprocess.DEVNULL
).strip()
# Get commit author
commit_author = subprocess.check_output(
- ['git', 'log', '-1', '--pretty=format:%an'],
+ ['git', 'log', '-1', '--pretty=format:%an', commit_ref],
text=True, stderr=subprocess.DEVNULL
).strip()
@@ -46,6 +70,34 @@
print(f"Failed to get commit information: {e}")
sys.exit(1)
+def get_commits_in_range(before, after):
+ """Get all commits in the range before..after."""
+ try:
+ # Get commit SHAs in the range
+ commit_shas = subprocess.check_output(
+ ['git', 'rev-list', '--reverse', f'{before}..{after}'],
+ text=True, stderr=subprocess.DEVNULL
+ ).strip().split('\n')
+
+ # Filter out empty strings
+ commit_shas = [sha for sha in commit_shas if sha]
+
+ commits = []
+ for sha in commit_shas:
+ message, body, author = get_commit_info(sha)
+ commits.append({
+ 'sha': sha,
+ 'short_sha': sha[:8],
+ 'message': message,
+ 'body': body,
+ 'author': author
+ })
+
+ return commits
+ except subprocess.CalledProcessError as e:
+ print(f"Failed to get commits in range: {e}")
+ sys.exit(1)
+
def truncate_text(text, max_length):
"""Truncate text to fit within Discord's limits."""
if len(text) <= max_length:
@@ -66,6 +118,93 @@
# Otherwise just truncate with ellipsis
return truncated + "..."
+def format_commit_for_discord(commit, repo_name):
+ """Format a single commit for Discord display."""
+ commit_url = f"https://github.com/{repo_name}/commit/{commit['sha']}"
+ return f"[`{commit['short_sha']}`]({commit_url}) {commit['message']} - {commit['author']}"
+
+def create_discord_payload_for_commits(commits, repo_name):
+ """Create Discord payload for multiple commits."""
+ timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%fZ')[:-3] + 'Z'
+
+ if len(commits) == 1:
+ # Single commit - use the original detailed format
+ commit = commits[0]
+ commit_url = f"https://github.com/{repo_name}/commit/{commit['sha']}"
+ title = truncate_text(commit['message'], 256)
+ description = truncate_text(commit['body'], 2000)
+
+ return {
+ "embeds": [
+ {
+ "title": title,
+ "description": description,
+ "color": 5814783,
+ "fields": [
+ {
+ "name": "Author",
+ "value": commit['author'],
+ "inline": True
+ },
+ {
+ "name": "Commit",
+ "value": f"[{commit['short_sha']}]({commit_url})",
+ "inline": True
+ },
+ ],
+ "timestamp": timestamp
+ }
+ ]
+ }
+ else:
+ # Multiple commits - use a compact format
+ commit_lines = []
+ for commit in commits:
+ commit_lines.append(format_commit_for_discord(commit, repo_name))
+
+ description = "\n".join(commit_lines)
+
+ # Truncate if too long
+ if len(description) > 2000:
+ # Try to fit as many commits as possible
+ truncated_lines = []
+ current_length = 0
+ for line in commit_lines:
+ if current_length + len(line) + 1 > 1900: # Leave room for "...and X more"
+ remaining = len(commit_lines) - len(truncated_lines)
+ truncated_lines.append(f"...and {remaining} more commits")
+ break
+ truncated_lines.append(line)
+ current_length += len(line) + 1
+ description = "\n".join(truncated_lines)
+
+ # Get unique authors
+ authors = list(set(commit['author'] for commit in commits))
+ author_text = ", ".join(authors) if len(authors) <= 3 else f"{authors[0]} and {len(authors) - 1} others"
+
+ return {
+ "embeds": [
+ {
+ "title": f"{len(commits)} commits pushed to main",
+ "description": description,
+ "color": 5814783,
+ "fields": [
+ {
+ "name": "Authors",
+ "value": author_text,
+ "inline": True
+ },
+ {
+ "name": "Commits",
+ "value": str(len(commits)),
+ "inline": True
+ },
+ ],
+ "timestamp": timestamp
+ }
+ ]
+ }
+
def main():
# Validate we're running in the correct environment
validate_environment()
@@ -74,45 +213,41 @@
if os.environ.get('DISCORD_TEST_MODE') == '1':
print("Running in test mode - will not send actual webhook")
- # Get commit information from git
- commit_message, commit_body, commit_author = get_commit_info()
-
- # Get remaining info from environment
- github_sha = os.environ.get('GITHUB_SHA')
- commit_sha = github_sha[:8]
- commit_url = f"https://github.com/{os.environ.get('GITHUB_REPOSITORY')}/commit/{github_sha}"
-
- # Create timestamp
- timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%fZ')[:-3] + 'Z'
-
- # Truncate fields to fit Discord's limits
- # Discord embed limits: title (256), description (4096), field value (1024)
- title = truncate_text(commit_message, 256)
- description = truncate_text(commit_body, 2000) # Use 2000 to be safe
+ # Get repository info
+ repo_name = os.environ.get('GITHUB_REPOSITORY')
+
+ # Try to get commit range from GitHub event
+ before, after = get_commit_range()
+
+ if before and after:
+ # Multiple commits pushed
+ commits = get_commits_in_range(before, after)
+ if not commits:
+ print("No commits found in range, falling back to single commit")
+ # Fall back to single commit
+ commit_message, commit_body, commit_author = get_commit_info()
+ github_sha = os.environ.get('GITHUB_SHA')
+ commits = [{
+ 'sha': github_sha,
+ 'short_sha': github_sha[:8],
+ 'message': commit_message,
+ 'body': commit_body,
+ 'author': commit_author
+ }]
+ else:
+ # Single commit or fallback
+ commit_message, commit_body, commit_author = get_commit_info()
+ github_sha = os.environ.get('GITHUB_SHA')
+ commits = [{
+ 'sha': github_sha,
+ 'short_sha': github_sha[:8],
+ 'message': commit_message,
+ 'body': commit_body,
+ 'author': commit_author
+ }]
# Create Discord webhook payload
- payload = {
- "embeds": [
- {
- "title": title,
- "description": description,
- "color": 5814783,
- "fields": [
- {
- "name": "Author",
- "value": commit_author,
- "inline": True
- },
- {
- "name": "Commit",
- "value": f"[{commit_sha}]({commit_url})",
- "inline": True
- },
- ],
- "timestamp": timestamp
- }
- ]
- }
+ payload = create_discord_payload_for_commits(commits, repo_name)
# Convert to JSON
json_payload = json.dumps(payload)
@@ -122,6 +257,7 @@
print(f"Payload size: {len(json_payload)} bytes")
print(f"Title length: {len(payload['embeds'][0]['title'])} chars")
print(f"Description length: {len(payload['embeds'][0]['description'])} chars")
+ print(f"Number of commits: {len(commits)}")
# Test mode - just print the payload
if os.environ.get('DISCORD_TEST_MODE') == '1':
@@ -145,7 +281,7 @@
try:
with urllib.request.urlopen(req) as response:
if response.status == 204:
- print("Discord notification sent successfully")
+ print(f"Discord notification sent successfully for {len(commits)} commit(s)")
else:
print(f"Discord webhook returned status: {response.status}")
response_body = response.read().decode('utf-8')
diff --git a/.github/workflows/discord-notify.yml b/.github/workflows/discord-notify.yml
index c9e2eae..3f4d073 100644
--- a/.github/workflows/discord-notify.yml
+++ b/.github/workflows/discord-notify.yml
@@ -13,7 +13,7 @@
steps:
- uses: actions/checkout@v4
with:
- fetch-depth: 2
+ fetch-depth: 0 # Fetch full history to handle commit ranges
- name: Send Discord notification
env: