diff --git a/package.json b/package.json
index c9d4488..15700f4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "parse-git-diff",
-  "version": "0.0.16",
+  "version": "0.0.19",
   "description": "A parser for git diff",
   "main": "./build/cjs/index.js",
   "module": "./build/mjs/index.js",
diff --git a/src/__fixtures__/31-no-prefix b/src/__fixtures__/31-no-prefix
new file mode 100644
index 0000000..209812f
--- /dev/null
+++ b/src/__fixtures__/31-no-prefix
@@ -0,0 +1,235 @@
+diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.test.ts var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.test.ts
+new file mode 100644
+index 0000000..e69de29
+diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/playground/dn2ncwjsbmo/index.tsx var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.tsx
+index 4d68325..fd576f7 100644
+--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/playground/dn2ncwjsbmo/index.tsx
++++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.tsx
+@@ -1,190 +1,54 @@
+-import { createContext, useEffect, useState, use, useCallback } from 'react'
++import { Suspense, useSyncExternalStore } from 'react'
+ import * as ReactDOM from 'react-dom/client'
+-import {
+-       type BlogPost,
+-       generateGradient,
+-       getMatchingPosts,
+-} from '#shared/blog-posts'
+-import { setGlobalSearchParams } from '#shared/utils'
+ 
+-type SearchParamsTuple = readonly [
+-       URLSearchParams,
+-       typeof setGlobalSearchParams,
+-]
+-const SearchParamsContext = createContext<SearchParamsTuple>([
+-       new URLSearchParams(window.location.search),
+-       setGlobalSearchParams,
+-])
+-
+-function SearchParamsProvider({ children }: { children: React.ReactNode }) {
+-       const [searchParams, setSearchParamsState] = useState(
+-               () => new URLSearchParams(window.location.search),
+-       )
++export function makeMediaQueryStore(mediaQuery: string) {
++       function getSnapshot() {
++               return window.matchMedia(mediaQuery).matches
++       }
+ 
+-       useEffect(() => {
+-               function updateSearchParams() {
+-                       setSearchParamsState((prevParams) => {
+-                               const newParams = new URLSearchParams(window.location.search)
+-                               return prevParams.toString() === newParams.toString()
+-                                       ? prevParams
+-                                       : newParams
+-                       })
++       function subscribe(callback: () => void) {
++               const mediaQueryList = window.matchMedia(mediaQuery)
++               mediaQueryList.addEventListener('change', callback)
++               return () => {
++                       mediaQueryList.removeEventListener('change', callback)
+                }
+-               window.addEventListener('popstate', updateSearchParams)
+-               return () => window.removeEventListener('popstate', updateSearchParams)
+-       }, [])
+-
+-       const setSearchParams = useCallback(
+-               (...args: Parameters<typeof setGlobalSearchParams>) => {
+-                       const searchParams = setGlobalSearchParams(...args)
+-                       setSearchParamsState((prevParams) => {
+-                               return prevParams.toString() === searchParams.toString()
+-                                       ? prevParams
+-                                       : searchParams
+-                       })
+-                       return searchParams
+-               },
+-               [],
+-       )
+-
+-       const searchParamsTuple = [searchParams, setSearchParams] as const
+-
+-       return (
+-               <SearchParamsContext value={searchParamsTuple}>
+-                       {children}
+-               </SearchParamsContext>
+-       )
+-}
+-
+-function useSearchParams() {
+-       return use(SearchParamsContext)
+-}
+-
+-const getQueryParam = (params: URLSearchParams) => params.get('query') ?? ''
+-
+-function App() {
+-       return (
+-               <SearchParamsProvider>
+-                       <div className="app">
+-                               <Form />
+-                               <MatchingPosts />
+-                       </div>
+-               </SearchParamsProvider>
+-       )
+-}
+-
+-function Form() {
+-       const [searchParams, setSearchParams] = useSearchParams()
+-       const query = getQueryParam(searchParams)
+-
+-       const words = query.split(' ').map((w) => w.trim())
+-
+-       const dogChecked = words.includes('dog')
+-       const catChecked = words.includes('cat')
+-       const caterpillarChecked = words.includes('caterpillar')
+-
+-       function handleCheck(tag: string, checked: boolean) {
+-               const newWords = checked ? [...words, tag] : words.filter((w) => w !== tag)
+-               setSearchParams(
+-                       { query: newWords.filter(Boolean).join(' ').trim() },
+-                       { replace: true },
+-               )
+        }
+ 
+-       return (
+-               <form onSubmit={(e) => e.preventDefault()}>
+-                       <div>
+-                               <label htmlFor="searchInput">Search:</label>
+-                               <input
+-                                       id="searchInput"
+-                                       name="query"
+-                                       type="search"
+-                                       value={query}
+-                                       onChange={(e) =>
+-                                               setSearchParams({ query: e.currentTarget.value }, { replace: true })
+-                                       }
+-                               />
+-                       </div>
+-                       <div>
+-                               <label>
+-                                       <input
+-                                               type="checkbox"
+-                                               checked={dogChecked}
+-                                               onChange={(e) => handleCheck('dog', e.currentTarget.checked)}
+-                                       />{' '}
+-                                       🐶 dog
+-                               </label>
+-                               <label>
+-                                       <input
+-                                               type="checkbox"
+-                                               checked={catChecked}
+-                                               onChange={(e) => handleCheck('cat', e.currentTarget.checked)}
+-                                       />{' '}
+-                                       🐱 cat
+-                               </label>
+-                               <label>
+-                                       <input
+-                                               type="checkbox"
+-                                               checked={caterpillarChecked}
+-                                               onChange={(e) =>
+-                                                       handleCheck('caterpillar', e.currentTarget.checked)
+-                                               }
+-                                       />{' '}
+-                                       🐛 caterpillar
+-                               </label>
+-                       </div>
+-               </form>
+-       )
++       return function useMediaQuery() {
++               return useSyncExternalStore(subscribe, getSnapshot)
++       }
+ }
+ 
+-function MatchingPosts() {
+-       const [searchParams] = useSearchParams()
+-       const query = getQueryParam(searchParams)
+-       const matchingPosts = getMatchingPosts(query)
++const useNarrowMediaQuery = makeMediaQueryStore('(max-width: 600px)')
+ 
+-       return (
+-               <ul className="post-list">
+-                       {matchingPosts.map((post) => (
+-                               <Card key={post.id} post={post} />
+-                       ))}
+-               </ul>
+-       )
++function NarrowScreenNotifier() {
++       const isNarrow = useNarrowMediaQuery()
++       return isNarrow ? 'You are on a narrow screen' : 'You are on a wide screen'
+ }
+ 
+-function Card({ post }: { post: BlogPost }) {
+-       const [isFavorited, setIsFavorited] = useState(false)
++function App() {
+        return (
+-               <li>
+-                       {isFavorited ? (
+-                               <button
+-                                       aria-label="Remove favorite"
+-                                       onClick={() => setIsFavorited(false)}
+-                               >
+-                                       ❤️
+-                               </button>
+-                       ) : (
+-                               <button aria-label="Add favorite" onClick={() => setIsFavorited(true)}>
+-                                       🤍
+-                               </button>
+-                       )}
+-                       <div
+-                               className="post-image"
+-                               style={{ background: generateGradient(post.id) }}
+-                       />
+-                       <a
+-                               href={post.id}
+-                               onClick={(event) => {
+-                                       event.preventDefault()
+-                                       alert(`Great! Let's go to ${post.id}!`)
+-                               }}
+-                       >
+-                               <h2>{post.title}</h2>
+-                               <p>{post.description}</p>
+-                       </a>
+-               </li>
++               <div>
++                       <div>This is your narrow screen state:</div>
++                       <Suspense fallback="">
++                               <NarrowScreenNotifier />
++                       </Suspense>
++               </div>
+        )
+ }
+ 
+ const rootEl = document.createElement('div')
+ document.body.append(rootEl)
+-ReactDOM.createRoot(rootEl).render(<App />)
++// 🦉 here's how we pretend we're server-rendering
++rootEl.innerHTML = (await import('react-dom/server')).renderToString(<App />)
++
++// 🦉 here's how we simulate a delay in hydrating with client-side js
++await new Promise((resolve) => setTimeout(resolve, 1000))
++
++ReactDOM.hydrateRoot(rootEl, <App />, {
++       onRecoverableError(error) {
++               if (String(error).includes('Missing getServerSnapshot')) return
++
++               console.error(error)
++       },
++})
\ No newline at end of file
diff --git a/src/__fixtures__/added-empty-file b/src/__fixtures__/added-empty-file
new file mode 100644
index 0000000..b8e4b8f
--- /dev/null
+++ b/src/__fixtures__/added-empty-file
@@ -0,0 +1,3 @@
+diff --git a/src/empty b/src/empty
+new file mode 100644
+index 0000000..e69de29
\ No newline at end of file
diff --git a/src/__fixtures__/consecutive-empty-files b/src/__fixtures__/consecutive-empty-files
new file mode 100644
index 0000000..98a3f8e
--- /dev/null
+++ b/src/__fixtures__/consecutive-empty-files
@@ -0,0 +1,14 @@
+diff --git a/content b/content
+new file mode 100644
+index 0000000..6b584e8
+--- /dev/null
++++ b/content
+@@ -0,0 +1 @@
++content
+\ No newline at end of file
+diff --git a/empty b/empty
+new file mode 100644
+index 0000000..e69de29
+diff --git a/empty2 b/empty2
+new file mode 100644
+index 0000000..e69de29
\ No newline at end of file
diff --git a/src/__fixtures__/deleted-empty-file b/src/__fixtures__/deleted-empty-file
new file mode 100644
index 0000000..94c6d26
--- /dev/null
+++ b/src/__fixtures__/deleted-empty-file
@@ -0,0 +1,3 @@
+diff --git a/src/empty b/src/empty
+deleted file mode 100644
+index e69de29..0000000
\ No newline at end of file
diff --git a/src/__fixtures__/renamed-empty b/src/__fixtures__/renamed-empty
new file mode 100644
index 0000000..8fee8b8
--- /dev/null
+++ b/src/__fixtures__/renamed-empty
@@ -0,0 +1,4 @@
+diff --git a/src/empty b/src/renamed-empty
+similarity index 100%
+rename from src/empty
+rename to src/renamed-empty
\ No newline at end of file
diff --git a/src/__fixtures__/unified b/src/__fixtures__/unified
new file mode 100644
index 0000000..9c7e37f
--- /dev/null
+++ b/src/__fixtures__/unified
@@ -0,0 +1,6 @@
+diff --git a/test.txt b/test.txt
+index 27b30e2..62944e1 100644
+--- a/test.txt
++++ b/test.txt
+@@ -2,0 +3 @@ bbb
++ddd
\ No newline at end of file
diff --git a/src/__tests__/31-no-prefix.test.ts b/src/__tests__/31-no-prefix.test.ts
new file mode 100644
index 0000000..f620690
--- /dev/null
+++ b/src/__tests__/31-no-prefix.test.ts
@@ -0,0 +1,10 @@
+import { getFixture } from './test-utils';
+import parseGitDiff from '../parse-git-diff';
+
+describe('issue 31-no-prefix', () => {
+  const fixture = getFixture('31-no-prefix');
+
+  it('parse `31-no-prefix`', () => {
+    expect(parseGitDiff(fixture, { noPrefix: true })).toMatchSnapshot();
+  });
+});
diff --git a/src/__tests__/31.test.ts b/src/__tests__/31.test.ts
index b073b88..4c77c1b 100644
--- a/src/__tests__/31.test.ts
+++ b/src/__tests__/31.test.ts
@@ -1,7 +1,7 @@
 import { getFixture } from './test-utils';
 import parseGitDiff from '../parse-git-diff';
 
-describe.only('issue 31', () => {
+describe('issue 31', () => {
   const fixture = getFixture('31');
 
   it('parse `31`', () => {
diff --git a/src/__tests__/__snapshots__/31-no-prefix.test.ts.snap b/src/__tests__/__snapshots__/31-no-prefix.test.ts.snap
new file mode 100644
index 0000000..f6f0ad0
--- /dev/null
+++ b/src/__tests__/__snapshots__/31-no-prefix.test.ts.snap
@@ -0,0 +1,1186 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`issue 31-no-prefix parse \`31-no-prefix\` 1`] = `
+{
+  "files": [
+    {
+      "chunks": [],
+      "path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.test.ts",
+      "type": "AddedFile",
+    },
+    {
+      "chunks": [
+        {
+          "changes": [
+            {
+              "content": "import { createContext, useEffect, useState, use, useCallback } from 'react'",
+              "lineBefore": 1,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "import { Suspense, useSyncExternalStore } from 'react'",
+              "lineAfter": 1,
+              "type": "AddedLine",
+            },
+            {
+              "content": "import * as ReactDOM from 'react-dom/client'",
+              "lineAfter": 2,
+              "lineBefore": 2,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "import {",
+              "lineBefore": 3,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       type BlogPost,",
+              "lineBefore": 4,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       generateGradient,",
+              "lineBefore": 5,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       getMatchingPosts,",
+              "lineBefore": 6,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "} from '#shared/blog-posts'",
+              "lineBefore": 7,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "import { setGlobalSearchParams } from '#shared/utils'",
+              "lineBefore": 8,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 3,
+              "lineBefore": 9,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "type SearchParamsTuple = readonly [",
+              "lineBefore": 10,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       URLSearchParams,",
+              "lineBefore": 11,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       typeof setGlobalSearchParams,",
+              "lineBefore": 12,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "]",
+              "lineBefore": 13,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "const SearchParamsContext = createContext<SearchParamsTuple>([",
+              "lineBefore": 14,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       new URLSearchParams(window.location.search),",
+              "lineBefore": 15,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       setGlobalSearchParams,",
+              "lineBefore": 16,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "])",
+              "lineBefore": 17,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 18,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "function SearchParamsProvider({ children }: { children: React.ReactNode }) {",
+              "lineBefore": 19,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const [searchParams, setSearchParamsState] = useState(",
+              "lineBefore": 20,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               () => new URLSearchParams(window.location.search),",
+              "lineBefore": 21,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       )",
+              "lineBefore": 22,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "export function makeMediaQueryStore(mediaQuery: string) {",
+              "lineAfter": 4,
+              "type": "AddedLine",
+            },
+            {
+              "content": "       function getSnapshot() {",
+              "lineAfter": 5,
+              "type": "AddedLine",
+            },
+            {
+              "content": "               return window.matchMedia(mediaQuery).matches",
+              "lineAfter": 6,
+              "type": "AddedLine",
+            },
+            {
+              "content": "       }",
+              "lineAfter": 7,
+              "type": "AddedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 8,
+              "lineBefore": 23,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "       useEffect(() => {",
+              "lineBefore": 24,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               function updateSearchParams() {",
+              "lineBefore": 25,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       setSearchParamsState((prevParams) => {",
+              "lineBefore": 26,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               const newParams = new URLSearchParams(window.location.search)",
+              "lineBefore": 27,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               return prevParams.toString() === newParams.toString()",
+              "lineBefore": 28,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       ? prevParams",
+              "lineBefore": 29,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       : newParams",
+              "lineBefore": 30,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       })",
+              "lineBefore": 31,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       function subscribe(callback: () => void) {",
+              "lineAfter": 9,
+              "type": "AddedLine",
+            },
+            {
+              "content": "               const mediaQueryList = window.matchMedia(mediaQuery)",
+              "lineAfter": 10,
+              "type": "AddedLine",
+            },
+            {
+              "content": "               mediaQueryList.addEventListener('change', callback)",
+              "lineAfter": 11,
+              "type": "AddedLine",
+            },
+            {
+              "content": "               return () => {",
+              "lineAfter": 12,
+              "type": "AddedLine",
+            },
+            {
+              "content": "                       mediaQueryList.removeEventListener('change', callback)",
+              "lineAfter": 13,
+              "type": "AddedLine",
+            },
+            {
+              "content": "               }",
+              "lineAfter": 14,
+              "lineBefore": 32,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "               window.addEventListener('popstate', updateSearchParams)",
+              "lineBefore": 33,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               return () => window.removeEventListener('popstate', updateSearchParams)",
+              "lineBefore": 34,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       }, [])",
+              "lineBefore": 35,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 36,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const setSearchParams = useCallback(",
+              "lineBefore": 37,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               (...args: Parameters<typeof setGlobalSearchParams>) => {",
+              "lineBefore": 38,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       const searchParams = setGlobalSearchParams(...args)",
+              "lineBefore": 39,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       setSearchParamsState((prevParams) => {",
+              "lineBefore": 40,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               return prevParams.toString() === searchParams.toString()",
+              "lineBefore": 41,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       ? prevParams",
+              "lineBefore": 42,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       : searchParams",
+              "lineBefore": 43,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       })",
+              "lineBefore": 44,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       return searchParams",
+              "lineBefore": 45,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               },",
+              "lineBefore": 46,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               [],",
+              "lineBefore": 47,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       )",
+              "lineBefore": 48,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 49,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const searchParamsTuple = [searchParams, setSearchParams] as const",
+              "lineBefore": 50,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 51,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       return (",
+              "lineBefore": 52,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               <SearchParamsContext value={searchParamsTuple}>",
+              "lineBefore": 53,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       {children}",
+              "lineBefore": 54,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               </SearchParamsContext>",
+              "lineBefore": 55,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       )",
+              "lineBefore": 56,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "}",
+              "lineBefore": 57,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 58,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "function useSearchParams() {",
+              "lineBefore": 59,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       return use(SearchParamsContext)",
+              "lineBefore": 60,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "}",
+              "lineBefore": 61,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 62,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "const getQueryParam = (params: URLSearchParams) => params.get('query') ?? ''",
+              "lineBefore": 63,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 64,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "function App() {",
+              "lineBefore": 65,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       return (",
+              "lineBefore": 66,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               <SearchParamsProvider>",
+              "lineBefore": 67,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       <div className="app">",
+              "lineBefore": 68,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <Form />",
+              "lineBefore": 69,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <MatchingPosts />",
+              "lineBefore": 70,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       </div>",
+              "lineBefore": 71,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               </SearchParamsProvider>",
+              "lineBefore": 72,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       )",
+              "lineBefore": 73,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "}",
+              "lineBefore": 74,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 75,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "function Form() {",
+              "lineBefore": 76,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const [searchParams, setSearchParams] = useSearchParams()",
+              "lineBefore": 77,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const query = getQueryParam(searchParams)",
+              "lineBefore": 78,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 79,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const words = query.split(' ').map((w) => w.trim())",
+              "lineBefore": 80,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 81,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const dogChecked = words.includes('dog')",
+              "lineBefore": 82,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const catChecked = words.includes('cat')",
+              "lineBefore": 83,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const caterpillarChecked = words.includes('caterpillar')",
+              "lineBefore": 84,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "",
+              "lineBefore": 85,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       function handleCheck(tag: string, checked: boolean) {",
+              "lineBefore": 86,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               const newWords = checked ? [...words, tag] : words.filter((w) => w !== tag)",
+              "lineBefore": 87,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               setSearchParams(",
+              "lineBefore": 88,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       { query: newWords.filter(Boolean).join(' ').trim() },",
+              "lineBefore": 89,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       { replace: true },",
+              "lineBefore": 90,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               )",
+              "lineBefore": 91,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       }",
+              "lineAfter": 15,
+              "lineBefore": 92,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 16,
+              "lineBefore": 93,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "       return (",
+              "lineBefore": 94,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               <form onSubmit={(e) => e.preventDefault()}>",
+              "lineBefore": 95,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       <div>",
+              "lineBefore": 96,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <label htmlFor="searchInput">Search:</label>",
+              "lineBefore": 97,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <input",
+              "lineBefore": 98,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       id="searchInput"",
+              "lineBefore": 99,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       name="query"",
+              "lineBefore": 100,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       type="search"",
+              "lineBefore": 101,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       value={query}",
+              "lineBefore": 102,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       onChange={(e) =>",
+              "lineBefore": 103,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               setSearchParams({ query: e.currentTarget.value }, { replace: true })",
+              "lineBefore": 104,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       }",
+              "lineBefore": 105,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               />",
+              "lineBefore": 106,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       </div>",
+              "lineBefore": 107,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       <div>",
+              "lineBefore": 108,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <label>",
+              "lineBefore": 109,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       <input",
+              "lineBefore": 110,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               type="checkbox"",
+              "lineBefore": 111,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               checked={dogChecked}",
+              "lineBefore": 112,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               onChange={(e) => handleCheck('dog', e.currentTarget.checked)}",
+              "lineBefore": 113,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       />{' '}",
+              "lineBefore": 114,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       🐶 dog",
+              "lineBefore": 115,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               </label>",
+              "lineBefore": 116,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <label>",
+              "lineBefore": 117,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       <input",
+              "lineBefore": 118,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               type="checkbox"",
+              "lineBefore": 119,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               checked={catChecked}",
+              "lineBefore": 120,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               onChange={(e) => handleCheck('cat', e.currentTarget.checked)}",
+              "lineBefore": 121,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       />{' '}",
+              "lineBefore": 122,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       🐱 cat",
+              "lineBefore": 123,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               </label>",
+              "lineBefore": 124,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <label>",
+              "lineBefore": 125,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       <input",
+              "lineBefore": 126,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               type="checkbox"",
+              "lineBefore": 127,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               checked={caterpillarChecked}",
+              "lineBefore": 128,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               onChange={(e) =>",
+              "lineBefore": 129,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                                       handleCheck('caterpillar', e.currentTarget.checked)",
+              "lineBefore": 130,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                               }",
+              "lineBefore": 131,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       />{' '}",
+              "lineBefore": 132,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       🐛 caterpillar",
+              "lineBefore": 133,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               </label>",
+              "lineBefore": 134,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       </div>",
+              "lineBefore": 135,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               </form>",
+              "lineBefore": 136,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       )",
+              "lineBefore": 137,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       return function useMediaQuery() {",
+              "lineAfter": 17,
+              "type": "AddedLine",
+            },
+            {
+              "content": "               return useSyncExternalStore(subscribe, getSnapshot)",
+              "lineAfter": 18,
+              "type": "AddedLine",
+            },
+            {
+              "content": "       }",
+              "lineAfter": 19,
+              "type": "AddedLine",
+            },
+            {
+              "content": "}",
+              "lineAfter": 20,
+              "lineBefore": 138,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 21,
+              "lineBefore": 139,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "function MatchingPosts() {",
+              "lineBefore": 140,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const [searchParams] = useSearchParams()",
+              "lineBefore": 141,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const query = getQueryParam(searchParams)",
+              "lineBefore": 142,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const matchingPosts = getMatchingPosts(query)",
+              "lineBefore": 143,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "const useNarrowMediaQuery = makeMediaQueryStore('(max-width: 600px)')",
+              "lineAfter": 22,
+              "type": "AddedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 23,
+              "lineBefore": 144,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "       return (",
+              "lineBefore": 145,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               <ul className="post-list">",
+              "lineBefore": 146,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       {matchingPosts.map((post) => (",
+              "lineBefore": 147,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <Card key={post.id} post={post} />",
+              "lineBefore": 148,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       ))}",
+              "lineBefore": 149,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               </ul>",
+              "lineBefore": 150,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       )",
+              "lineBefore": 151,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "function NarrowScreenNotifier() {",
+              "lineAfter": 24,
+              "type": "AddedLine",
+            },
+            {
+              "content": "       const isNarrow = useNarrowMediaQuery()",
+              "lineAfter": 25,
+              "type": "AddedLine",
+            },
+            {
+              "content": "       return isNarrow ? 'You are on a narrow screen' : 'You are on a wide screen'",
+              "lineAfter": 26,
+              "type": "AddedLine",
+            },
+            {
+              "content": "}",
+              "lineAfter": 27,
+              "lineBefore": 152,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 28,
+              "lineBefore": 153,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "function Card({ post }: { post: BlogPost }) {",
+              "lineBefore": 154,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "       const [isFavorited, setIsFavorited] = useState(false)",
+              "lineBefore": 155,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "function App() {",
+              "lineAfter": 29,
+              "type": "AddedLine",
+            },
+            {
+              "content": "       return (",
+              "lineAfter": 30,
+              "lineBefore": 156,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "               <li>",
+              "lineBefore": 157,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       {isFavorited ? (",
+              "lineBefore": 158,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <button",
+              "lineBefore": 159,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       aria-label="Remove favorite"",
+              "lineBefore": 160,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       onClick={() => setIsFavorited(false)}",
+              "lineBefore": 161,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               >",
+              "lineBefore": 162,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       ❤️",
+              "lineBefore": 163,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               </button>",
+              "lineBefore": 164,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       ) : (",
+              "lineBefore": 165,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <button aria-label="Add favorite" onClick={() => setIsFavorited(true)}>",
+              "lineBefore": 166,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       🤍",
+              "lineBefore": 167,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               </button>",
+              "lineBefore": 168,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       )}",
+              "lineBefore": 169,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       <div",
+              "lineBefore": 170,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               className="post-image"",
+              "lineBefore": 171,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               style={{ background: generateGradient(post.id) }}",
+              "lineBefore": 172,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       />",
+              "lineBefore": 173,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       <a",
+              "lineBefore": 174,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               href={post.id}",
+              "lineBefore": 175,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               onClick={(event) => {",
+              "lineBefore": 176,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       event.preventDefault()",
+              "lineBefore": 177,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                                       alert(\`Great! Let's go to \${post.id}!\`)",
+              "lineBefore": 178,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               }}",
+              "lineBefore": 179,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       >",
+              "lineBefore": 180,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <h2>{post.title}</h2>",
+              "lineBefore": 181,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                               <p>{post.description}</p>",
+              "lineBefore": 182,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "                       </a>",
+              "lineBefore": 183,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               </li>",
+              "lineBefore": 184,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "               <div>",
+              "lineAfter": 31,
+              "type": "AddedLine",
+            },
+            {
+              "content": "                       <div>This is your narrow screen state:</div>",
+              "lineAfter": 32,
+              "type": "AddedLine",
+            },
+            {
+              "content": "                       <Suspense fallback="">",
+              "lineAfter": 33,
+              "type": "AddedLine",
+            },
+            {
+              "content": "                               <NarrowScreenNotifier />",
+              "lineAfter": 34,
+              "type": "AddedLine",
+            },
+            {
+              "content": "                       </Suspense>",
+              "lineAfter": 35,
+              "type": "AddedLine",
+            },
+            {
+              "content": "               </div>",
+              "lineAfter": 36,
+              "type": "AddedLine",
+            },
+            {
+              "content": "       )",
+              "lineAfter": 37,
+              "lineBefore": 185,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "}",
+              "lineAfter": 38,
+              "lineBefore": 186,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 39,
+              "lineBefore": 187,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "const rootEl = document.createElement('div')",
+              "lineAfter": 40,
+              "lineBefore": 188,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "document.body.append(rootEl)",
+              "lineAfter": 41,
+              "lineBefore": 189,
+              "type": "UnchangedLine",
+            },
+            {
+              "content": "ReactDOM.createRoot(rootEl).render(<App />)",
+              "lineBefore": 190,
+              "type": "DeletedLine",
+            },
+            {
+              "content": "// 🦉 here's how we pretend we're server-rendering",
+              "lineAfter": 42,
+              "type": "AddedLine",
+            },
+            {
+              "content": "rootEl.innerHTML = (await import('react-dom/server')).renderToString(<App />)",
+              "lineAfter": 43,
+              "type": "AddedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 44,
+              "type": "AddedLine",
+            },
+            {
+              "content": "// 🦉 here's how we simulate a delay in hydrating with client-side js",
+              "lineAfter": 45,
+              "type": "AddedLine",
+            },
+            {
+              "content": "await new Promise((resolve) => setTimeout(resolve, 1000))",
+              "lineAfter": 46,
+              "type": "AddedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 47,
+              "type": "AddedLine",
+            },
+            {
+              "content": "ReactDOM.hydrateRoot(rootEl, <App />, {",
+              "lineAfter": 48,
+              "type": "AddedLine",
+            },
+            {
+              "content": "       onRecoverableError(error) {",
+              "lineAfter": 49,
+              "type": "AddedLine",
+            },
+            {
+              "content": "               if (String(error).includes('Missing getServerSnapshot')) return",
+              "lineAfter": 50,
+              "type": "AddedLine",
+            },
+            {
+              "content": "",
+              "lineAfter": 51,
+              "type": "AddedLine",
+            },
+            {
+              "content": "               console.error(error)",
+              "lineAfter": 52,
+              "type": "AddedLine",
+            },
+            {
+              "content": "       },",
+              "lineAfter": 53,
+              "type": "AddedLine",
+            },
+            {
+              "content": "})",
+              "lineAfter": 54,
+              "type": "AddedLine",
+            },
+          ],
+          "context": undefined,
+          "fromFileRange": {
+            "lines": 190,
+            "start": 1,
+          },
+          "toFileRange": {
+            "lines": 54,
+            "start": 1,
+          },
+          "type": "Chunk",
+        },
+      ],
+      "path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.tsx",
+      "type": "ChangedFile",
+    },
+  ],
+  "type": "GitDiff",
+}
+`;
diff --git a/src/__tests__/__snapshots__/31.test.ts.snap b/src/__tests__/__snapshots__/31.test.ts.snap
index 0df8961..074b94b 100644
--- a/src/__tests__/__snapshots__/31.test.ts.snap
+++ b/src/__tests__/__snapshots__/31.test.ts.snap
@@ -3,6 +3,11 @@
 exports[`issue 31 parse \`31\` 1`] = `
 {
   "files": [
+    {
+      "chunks": [],
+      "path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/04.01.solution/7h2jowvfi2q/index.test.tsx",
+      "type": "AddedFile",
+    },
     {
       "chunks": [
         {
@@ -406,7 +411,7 @@ exports[`issue 31 parse \`31\` 1`] = `
         },
       ],
       "path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/04.01.solution/7h2jowvfi2q/index.tsx",
-      "type": "AddedFile",
+      "type": "ChangedFile",
     },
   ],
   "type": "GitDiff",
diff --git a/src/__tests__/__snapshots__/added-empty-file.test.ts.snap b/src/__tests__/__snapshots__/added-empty-file.test.ts.snap
new file mode 100644
index 0000000..44f1615
--- /dev/null
+++ b/src/__tests__/__snapshots__/added-empty-file.test.ts.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`added-empty-file parse \`added-empty-file\` 1`] = `
+{
+  "files": [
+    {
+      "chunks": [],
+      "path": "src/empty",
+      "type": "AddedFile",
+    },
+  ],
+  "type": "GitDiff",
+}
+`;
diff --git a/src/__tests__/__snapshots__/chunk-context.test.ts.snap b/src/__tests__/__snapshots__/chunk-context.test.ts.snap
index f2fa843..85ef29e 100644
--- a/src/__tests__/__snapshots__/chunk-context.test.ts.snap
+++ b/src/__tests__/__snapshots__/chunk-context.test.ts.snap
@@ -14,14 +14,14 @@ exports[`chunk-context parse \`chunk-context\` 1`] = `
               "type": "UnchangedLine",
             },
             {
-              "content": "  console.log(\"hello world\");",
+              "content": "  console.log("hello world");",
               "lineAfter": 5,
               "type": "AddedLine",
             },
           ],
           "context": "function hello() {",
           "fromFileRange": {
-            "lines": 4,
+            "lines": 1,
             "start": 4,
           },
           "toFileRange": {
diff --git a/src/__tests__/__snapshots__/consecutive-empty-files.test.ts.snap b/src/__tests__/__snapshots__/consecutive-empty-files.test.ts.snap
new file mode 100644
index 0000000..c58a934
--- /dev/null
+++ b/src/__tests__/__snapshots__/consecutive-empty-files.test.ts.snap
@@ -0,0 +1,48 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`consecutive-empty-files parse \`consecutive-empty-files\` 1`] = `
+{
+  "files": [
+    {
+      "chunks": [
+        {
+          "changes": [
+            {
+              "content": "content",
+              "lineAfter": 1,
+              "type": "AddedLine",
+            },
+            {
+              "content": "No newline at end of file",
+              "type": "MessageLine",
+            },
+          ],
+          "context": undefined,
+          "fromFileRange": {
+            "lines": 0,
+            "start": 0,
+          },
+          "toFileRange": {
+            "lines": 1,
+            "start": 1,
+          },
+          "type": "Chunk",
+        },
+      ],
+      "path": "content",
+      "type": "AddedFile",
+    },
+    {
+      "chunks": [],
+      "path": "empty",
+      "type": "AddedFile",
+    },
+    {
+      "chunks": [],
+      "path": "empty2",
+      "type": "AddedFile",
+    },
+  ],
+  "type": "GitDiff",
+}
+`;
diff --git a/src/__tests__/__snapshots__/deleted-empty-file.test.ts.snap b/src/__tests__/__snapshots__/deleted-empty-file.test.ts.snap
new file mode 100644
index 0000000..59b07a9
--- /dev/null
+++ b/src/__tests__/__snapshots__/deleted-empty-file.test.ts.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`deleted-empty-file parse \`deleted-empty-file\` 1`] = `
+{
+  "files": [
+    {
+      "chunks": [],
+      "path": "src/empty",
+      "type": "DeletedFile",
+    },
+  ],
+  "type": "GitDiff",
+}
+`;
diff --git a/src/__tests__/__snapshots__/renamed-empty.test.ts.snap b/src/__tests__/__snapshots__/renamed-empty.test.ts.snap
new file mode 100644
index 0000000..3a32eef
--- /dev/null
+++ b/src/__tests__/__snapshots__/renamed-empty.test.ts.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renamed-empty parse \`renamed-empty\` 1`] = `
+{
+  "files": [
+    {
+      "chunks": [],
+      "pathAfter": "src/renamed-empty",
+      "pathBefore": "src/empty",
+      "type": "RenamedFile",
+    },
+  ],
+  "type": "GitDiff",
+}
+`;
diff --git a/src/__tests__/__snapshots__/unified.test.ts.snap b/src/__tests__/__snapshots__/unified.test.ts.snap
new file mode 100644
index 0000000..ffe28b1
--- /dev/null
+++ b/src/__tests__/__snapshots__/unified.test.ts.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`unified parse \`unified 1`] = `
+{
+  "files": [
+    {
+      "chunks": [
+        {
+          "changes": [
+            {
+              "content": "ddd",
+              "lineAfter": 3,
+              "type": "AddedLine",
+            },
+          ],
+          "context": "bbb",
+          "fromFileRange": {
+            "lines": 0,
+            "start": 2,
+          },
+          "toFileRange": {
+            "lines": 1,
+            "start": 3,
+          },
+          "type": "Chunk",
+        },
+      ],
+      "path": "test.txt",
+      "type": "ChangedFile",
+    },
+  ],
+  "type": "GitDiff",
+}
+`;
diff --git a/src/__tests__/added-empty-file.test.ts b/src/__tests__/added-empty-file.test.ts
new file mode 100644
index 0000000..a4d3dd0
--- /dev/null
+++ b/src/__tests__/added-empty-file.test.ts
@@ -0,0 +1,10 @@
+import { getFixture } from './test-utils';
+import parseGitDiff from '../parse-git-diff';
+
+describe('added-empty-file', () => {
+  const fixture = getFixture('added-empty-file');
+
+  it('parse `added-empty-file`', () => {
+    expect(parseGitDiff(fixture)).toMatchSnapshot();
+  });
+});
diff --git a/src/__tests__/consecutive-empty-files.test.ts b/src/__tests__/consecutive-empty-files.test.ts
new file mode 100644
index 0000000..b862d96
--- /dev/null
+++ b/src/__tests__/consecutive-empty-files.test.ts
@@ -0,0 +1,10 @@
+import { getFixture } from './test-utils';
+import parseGitDiff from '../parse-git-diff';
+
+describe('consecutive-empty-files', () => {
+  const fixture = getFixture('consecutive-empty-files');
+
+  it('parse `consecutive-empty-files`', () => {
+    expect(parseGitDiff(fixture)).toMatchSnapshot();
+  });
+});
diff --git a/src/__tests__/deleted-empty-file.test.ts b/src/__tests__/deleted-empty-file.test.ts
new file mode 100644
index 0000000..d313986
--- /dev/null
+++ b/src/__tests__/deleted-empty-file.test.ts
@@ -0,0 +1,10 @@
+import { getFixture } from './test-utils';
+import parseGitDiff from '../parse-git-diff';
+
+describe('deleted-empty-file', () => {
+  const fixture = getFixture('deleted-empty-file');
+
+  it('parse `deleted-empty-file`', () => {
+    expect(parseGitDiff(fixture)).toMatchSnapshot();
+  });
+});
diff --git a/src/__tests__/renamed-empty.test.ts b/src/__tests__/renamed-empty.test.ts
new file mode 100644
index 0000000..9700b3a
--- /dev/null
+++ b/src/__tests__/renamed-empty.test.ts
@@ -0,0 +1,10 @@
+import { getFixture } from './test-utils';
+import parseGitDiff from '../parse-git-diff';
+
+describe('renamed-empty', () => {
+  const fixture = getFixture('renamed-empty');
+
+  it('parse `renamed-empty`', () => {
+    expect(parseGitDiff(fixture)).toMatchSnapshot();
+  });
+});
diff --git a/src/__tests__/unified.test.ts b/src/__tests__/unified.test.ts
new file mode 100644
index 0000000..87e5c54
--- /dev/null
+++ b/src/__tests__/unified.test.ts
@@ -0,0 +1,10 @@
+import { getFixture } from './test-utils';
+import parseGitDiff from '../parse-git-diff';
+
+describe('unified', () => {
+  const fixture = getFixture('unified');
+
+  it('parse `unified', () => {
+    expect(parseGitDiff(fixture)).toMatchSnapshot();
+  });
+});
diff --git a/src/parse-git-diff.ts b/src/parse-git-diff.ts
index 4c74fce..31e6849 100644
--- a/src/parse-git-diff.ts
+++ b/src/parse-git-diff.ts
@@ -3,11 +3,8 @@ import type {
   GitDiff,
   AnyFileChange,
   AnyLineChange,
-  Chunk,
   ChunkRange,
-  CombinedChunk,
   AnyChunk,
-  FilledGitDiffOptions,
   GitDiffOptions,
 } from './types.js';
 import {
@@ -46,8 +43,7 @@ function parseFileChange(ctx: Context): AnyFileChange | undefined {
   if (!isComparisonInputLine(ctx.getCurLine())) {
     return;
   }
-  ctx.nextLine();
-
+  const comparisonLineParsed = parseComparisonInputLine(ctx);
   let isDeleted = false;
   let isNew = false;
   let isRename = false;
@@ -58,8 +54,14 @@ function parseFileChange(ctx: Context): AnyFileChange | undefined {
     if (!extHeader) {
       break;
     }
-    if (extHeader.type === ExtendedHeader.Deleted) isDeleted = true;
-    if (extHeader.type === ExtendedHeader.NewFile) isNew = true;
+    if (extHeader.type === ExtendedHeader.Deleted) {
+      isDeleted = true;
+      pathBefore = comparisonLineParsed?.from || '';
+    }
+    if (extHeader.type === ExtendedHeader.NewFile) {
+      isNew = true;
+      pathAfter = comparisonLineParsed?.to || '';
+    }
     if (extHeader.type === ExtendedHeader.RenameFrom) {
       isRename = true;
       pathBefore = extHeader.path as string;
@@ -73,33 +75,30 @@ function parseFileChange(ctx: Context): AnyFileChange | undefined {
   const changeMarkers = parseChangeMarkers(ctx);
   const chunks = parseChunks(ctx);
 
-  if (isDeleted && changeMarkers) {
+  if (isDeleted && chunks.length && chunks[0].type === 'BinaryFilesChunk') {
     return {
       type: FileType.Deleted,
       chunks,
-      path: changeMarkers.deleted,
+      path: chunks[0].pathBefore,
     };
-  } else if (
-    isDeleted &&
-    chunks.length &&
-    chunks[0].type === 'BinaryFilesChunk'
-  ) {
+  }
+  if (isDeleted) {
     return {
       type: FileType.Deleted,
       chunks,
-      path: chunks[0].pathBefore,
+      path: changeMarkers?.deleted || pathBefore,
     };
-  } else if (isNew && changeMarkers) {
+  } else if (isNew && chunks.length && chunks[0].type === 'BinaryFilesChunk') {
     return {
       type: FileType.Added,
       chunks,
-      path: changeMarkers.added,
+      path: chunks[0].pathAfter,
     };
-  } else if (isNew && chunks.length && chunks[0].type === 'BinaryFilesChunk') {
+  } else if (isNew) {
     return {
       type: FileType.Added,
       chunks,
-      path: chunks[0].pathAfter,
+      path: changeMarkers?.added || pathAfter,
     };
   } else if (isRename) {
     return {
@@ -125,7 +124,6 @@ function parseFileChange(ctx: Context): AnyFileChange | undefined {
       path: chunks[0].pathAfter,
     };
   }
-
   return;
 }
 
@@ -133,6 +131,21 @@ function isComparisonInputLine(line: string): boolean {
   return line.indexOf('diff') === 0;
 }
 
+function parseComparisonInputLine(
+  ctx: Context
+): { from: string; to: string } | null {
+  const line = ctx.getCurLine();
+  const [to, from] = line.split(' ').reverse();
+  ctx.nextLine();
+  if (to && from) {
+    return {
+      from: getFilePath(ctx, from, 'src'),
+      to: getFilePath(ctx, to, 'dst'),
+    };
+  }
+  return null;
+}
+
 function parseChunks(context: Context): AnyChunk[] {
   const chunks: AnyChunk[] = [];
 
@@ -151,7 +164,6 @@ function parseChunk(context: Context): AnyChunk | undefined {
   if (!chunkHeader) {
     return;
   }
-
   if (chunkHeader.type === 'Normal') {
     const changes = parseChanges(
       context,
@@ -195,7 +207,7 @@ function parseChunk(context: Context): AnyChunk | undefined {
 
 function parseExtendedHeader(ctx: Context) {
   if (isComparisonInputLine(ctx.getCurLine())) {
-    ctx.nextLine();
+    return null;
   }
   const line = ctx.getCurLine();
   const type = ExtendedHeaderValues.find((v) => line.startsWith(v));
@@ -264,7 +276,6 @@ function parseChunkHeader(ctx: Context) {
       toFileRange: getRange(addStart, addLines),
     } as const;
   }
-
   const [all, delStart, delLines, addStart, addLines, context] =
     normalChunkExec;
   ctx.nextLine();
@@ -280,7 +291,7 @@ function getRange(start: string, lines?: string) {
   const startNum = parseInt(start, 10);
   return {
     start: startNum,
-    lines: lines === undefined ? startNum : parseInt(lines, 10),
+    lines: lines === undefined ? 1 : parseInt(lines, 10),
   };
 }
 
@@ -386,4 +397,5 @@ function getFilePath(ctx: Context, input: string, type: 'src' | 'dst') {
   }
   if (type === 'src') return input.replace(/^a\//, '');
   if (type === 'dst') return input.replace(/^b\//, '');
+  throw new Error('Unexpected unreachable code');
 }