Introduction
Converting a unified diff into Markdown with strikethrough for removed lines and bold (or highlighted) text for added lines makes code changes readable in documents, pull request summaries, and changelogs. The approach is to parse the diff output, identify lines starting with - (removals) and + (additions), and wrap them in Markdown formatting. Strikethrough in Markdown uses ~~text~~ syntax. This can be done with a simple Python script, a shell pipeline, or JavaScript for web-based rendering.
1--- a/config.py
2+++ b/config.py
3@@ -10,4 +10,4 @@
4 HOST = "localhost"
5-PORT = 8080
6+PORT = 9090
7 DEBUG = True
8-LOG_LEVEL = "INFO"
9+LOG_LEVEL = "DEBUG"
Lines starting with - were removed, lines starting with + were added, and lines with a space prefix are unchanged context. The @@ line shows the line number range.
Python Converter
1def diff_to_markdown(diff_text):
2 lines = diff_text.strip().split('\n')
3 md_lines = []
4
5 for line in lines:
6 if line.startswith('---') or line.startswith('+++'):
7 # File header
8 md_lines.append(f"**{line}**")
9 elif line.startswith('@@'):
10 md_lines.append(f"*{line}*")
11 elif line.startswith('-'):
12 # Removed line — strikethrough
13 content = line[1:].strip()
14 md_lines.append(f"~~{content}~~")
15 elif line.startswith('+'):
16 # Added line — bold
17 content = line[1:].strip()
18 md_lines.append(f"**{content}**")
19 else:
20 # Context line
21 md_lines.append(line.lstrip(' '))
22
23 return '\n\n'.join(md_lines)
24
25diff = """--- a/config.py
26+++ b/config.py
27@@ -10,4 +10,4 @@
28 HOST = "localhost"
29-PORT = 8080
30+PORT = 9090
31 DEBUG = True"""
32
33print(diff_to_markdown(diff))
Output:
1**--- a/config.py**
2
3**+++ b/config.py**
4
5*@@ -10,4 +10,4 @@*
6
7HOST = "localhost"
8
9~~PORT = 8080~~
10
11**PORT = 9090**
12
13DEBUG = True
1def diff_to_table(diff_text):
2 lines = diff_text.strip().split('\n')
3 rows = []
4 rows.append("| Removed | Added |")
5 rows.append("|---|---|")
6
7 removed = []
8 added = []
9
10 for line in lines:
11 if line.startswith('---') or line.startswith('+++') or line.startswith('@@'):
12 continue
13 elif line.startswith('-'):
14 removed.append(line[1:].strip())
15 elif line.startswith('+'):
16 added.append(line[1:].strip())
17 else:
18 # Flush paired changes
19 max_len = max(len(removed), len(added))
20 for i in range(max_len):
21 r = f"~~{removed[i]}~~" if i < len(removed) else ""
22 a = f"**{added[i]}**" if i < len(added) else ""
23 rows.append(f"| {r} | {a} |")
24 removed.clear()
25 added.clear()
26 rows.append(f"| {line.strip()} | {line.strip()} |")
27
28 # Flush remaining
29 max_len = max(len(removed), len(added), 0)
30 for i in range(max_len):
31 r = f"~~{removed[i]}~~" if i < len(removed) else ""
32 a = f"**{added[i]}**" if i < len(added) else ""
33 rows.append(f"| {r} | {a} |")
34
35 return '\n'.join(rows)
36
37print(diff_to_table(diff))
This produces a Markdown table with removed lines (strikethrough) on the left and added lines (bold) on the right, similar to a side-by-side diff viewer.
Shell One-Liner
1# Convert git diff to markdown with strikethrough
2git diff HEAD~1 -- config.py | \
3 sed -e 's/^-\(.*\)/~~\1~~/' \
4 -e 's/^+\(.*\)/**\1**/' \
5 -e '/^@@/s/.*/*&*/' \
6 -e '/^---/s/.*/**&**/' \
7 -e '/^+++/s/.*/**&**/'
This uses sed to wrap removed lines in ~~ and added lines in **. It is a quick approach for simple diffs but does not handle edge cases like lines containing Markdown syntax.
JavaScript for Web Rendering
1function diffToMarkdown(diffText) {
2 return diffText
3 .split('\n')
4 .map(line => {
5 if (line.startsWith('---') || line.startsWith('+++')) {
6 return `**${line}**`;
7 }
8 if (line.startsWith('@@')) {
9 return `*${line}*`;
10 }
11 if (line.startsWith('-')) {
12 return `~~${line.slice(1)}~~`;
13 }
14 if (line.startsWith('+')) {
15 return `**${line.slice(1)}**`;
16 }
17 return line.slice(1); // Remove leading space from context
18 })
19 .join('\n\n');
20}
This JavaScript function is suitable for rendering diffs as Markdown in web applications, documentation generators, or GitHub Actions workflow summaries.
Using Colors Instead of Strikethrough
1<!-- For HTML output with colored backgrounds -->
2<style>
3 .diff-removed { background-color: #fdd; text-decoration: line-through; }
4 .diff-added { background-color: #dfd; font-weight: bold; }
5</style>
6
7<pre>
8<span class="diff-removed">PORT = 8080</span>
9<span class="diff-added">PORT = 9090</span>
10</pre>
1def diff_to_html(diff_text):
2 lines = diff_text.strip().split('\n')
3 html = ['<pre>']
4 for line in lines:
5 if line.startswith('-') and not line.startswith('---'):
6 html.append(f'<span class="diff-removed">{line}</span>')
7 elif line.startswith('+') and not line.startswith('+++'):
8 html.append(f'<span class="diff-added">{line}</span>')
9 else:
10 html.append(line)
11 html.append('</pre>')
12 return '\n'.join(html)
When Markdown strikethrough is not expressive enough, generate HTML with CSS classes for colored backgrounds. This works in tools that support HTML in Markdown (GitHub, Notion).
Common Pitfalls
Markdown inside diff lines: If a removed line contains ~~ or **, wrapping it in strikethrough Markdown creates broken formatting. Escape existing Markdown syntax before wrapping, or use code blocks instead.
Confusing file headers with content: Lines starting with --- and +++ are file headers, not content removals/additions. Always skip or handle these separately to avoid false strikethrough formatting.
Strikethrough not supported everywhere: ~~text~~ is a GitHub Flavored Markdown extension, not part of standard CommonMark. Verify that your rendering target supports strikethrough before using this approach.
Multiline changes spanning paragraphs: Markdown strikethrough does not work across paragraph boundaries (blank lines). Each line must be individually wrapped. Use <del> HTML tags if you need cross-paragraph strikethrough.
Binary diffs: Binary file diffs show "Binary files differ" without line-by-line content. The line-parsing approach does not work for binary diffs — check for this and skip or note it in the output.
Summary
Parse unified diff lines by prefix: - for removals, + for additions, space for context
Wrap removed lines in ~~text~~ for Markdown strikethrough
Wrap added lines in **text** for bold emphasis
Use a Markdown table for side-by-side comparison of changes
For richer formatting, generate HTML with <del> and <ins> tags or colored CSS classes
Escape existing Markdown syntax inside diff lines to prevent formatting conflicts