While preparing the v2.1.0 release, I found myself asking: "How much time am I actually spending on writing release notes?"
Beautiful HD Wallpapers has users across 30+ countries on both iOS and Android, so I write release notes in at least Japanese and English for every update. For bigger releases, this alone takes an hour or two. As a solo indie developer with 50 million cumulative downloads across my apps, that felt like a real bottleneck I needed to address.
The experiment: use Gemini API to automatically generate release notes from git log.
Why git log?
Commit messages are the most accurate first-hand record of what changed and why. During the v2.1.0 development, I documented everything in commits — the RecyclerView IndexOutOfBoundsException fix with defensive copies, the coreLibraryDesugaringEnabled workaround for Glide 5.0.5 on Android 6.x, and the back button ad gate redesign, among others.
Passing these to Gemini API and asking it to convert them into user-facing release notes is exactly the kind of task LLMs excel at: transforming structured, context-rich text into a different format. What I didn't expect was how well it would handle the translation from technical language to user-friendly descriptions.
This isn't documented in any official guide, but in practice, even bullet-point-style commit messages produce decent user-facing copy — as long as you have enough commits to give the model context.
Implementation
Here's the core Python script based on what I used for v2.1.0:
import subprocess
import os
import json
import google.generativeai as genai
genai.configure(api_key=os.environ["YOUR_GEMINI_API_KEY"])
model = genai.GenerativeModel("gemini-2.5-flash")
def get_git_log(from_tag: str, to_ref: str = "HEAD") -> str:
"""Fetch commit messages between two refs."""
result = subprocess.run(
["git", "log", f"{from_tag}..{to_ref}", "--pretty=format:%s%n%b", "--no-merges"],
capture_output=True,
text=True
)
return result.stdout.strip()
def generate_release_notes(commits: str, version: str) -> dict:
"""Generate bilingual release notes from commit messages."""
prompt = f"""Below are commit messages for Android app v{version}.
Generate user-facing release notes in both Japanese and English.
Rules:
- No internal implementation details (e.g., instead of "Fixed IndexOutOfBoundsException with defensive copy", write "Improved stability of the wallpaper list")
- Only include changes users can actually experience
- 3–5 bullet points per language
- Output valid JSON only, with keys "ja" and "en"
Commits:
{commits}
Output JSON only (no ```json wrapper):"""
response = model.generate_content(
prompt,
generation_config=genai.GenerationConfig(
response_mime_type="application/json"
)
)
return json.loads(response.text)
if __name__ == "__main__":
commits = get_git_log("v2.0.0")
notes = generate_release_notes(commits, "2.1.0")
print("=== Japanese ===")
print(notes["ja"])
print("\n=== English ===")
print(notes["en"])One key detail: setting response_mime_type="application/json" in the generation config almost completely eliminates the issue of Gemini wrapping its output in a Markdown code block. Before I added this, json.loads() would regularly fail because of the triple-backtick wrapper.
What the output actually looked like for v2.1.0
Running the script against ~40 commits from the v2.1.0 cycle produced English release notes roughly like this:
• Fixed a rare crash that occurred while browsing wallpapers
• Resolved a display issue affecting devices running Android 6.0 and above
• Improved responsiveness when switching between categories
• Slideshow on the home screen now transitions more smoothly
• Optimized certain animations for better performance
The most impressive translation was the Android 6.x display issue. My commit message read something like: "Fix Glide 5.0.5 + AGP 9.x Java 8 Supplier NoClassDefFoundError by enabling coreLibraryDesugaringEnabled." Gemini correctly abstracted this into a user-facing message without me needing to prompt it to do so.
Caveats worth knowing
Garbage in, garbage out
If your commit messages are vague (fix bug, update, wip), the output will be equally vague. The v2.1.0 results were good partly because I'd been writing descriptive commits. This is a forcing function to improve commit hygiene — not a bad side effect.
Technical terms occasionally slip through
In my run, 1–2 of the 40 commits produced output that still contained terms like "RecyclerView." A second-pass prompt or a regex cleanup step handles this well enough in practice.
Connecting to App Store and Play Store
The output drops cleanly into the "What's New" field in App Store Connect and the release notes field in Google Play Console. If you want to go further and support 30+ languages, you can pipe the Japanese and English output through another Gemini API call for translation — I documented that full pipeline in the article on auto-generating app metadata in 30 languages for iOS and Android.
My next step is wiring this into a GitHub Actions workflow that triggers automatically when I push a tag. For a solo developer maintaining multiple apps, small automations like this add up quickly — and this one genuinely saves me an hour or more per release.