Skip to content

Commit 2d82359

Browse files
committed
refac
1 parent ffbb47a commit 2d82359

1 file changed

Lines changed: 48 additions & 12 deletions

File tree

cptr/utils/git.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -232,30 +232,66 @@ async def unstage(root: str, files: list[str]) -> None:
232232

233233

234234
async def discard(root: str, files: list[str]) -> None:
235-
"""Discard changes in files. Tracked files are restored via git checkout;
236-
untracked files are deleted from disk."""
235+
"""Fully discard all changes for files — both staged and unstaged.
236+
237+
Tracked modified/deleted files are unstaged then restored via checkout.
238+
Newly added (staged) files are unstaged then deleted from disk.
239+
Untracked files are deleted from disk.
240+
"""
237241
if not files:
238242
return
239243

240-
# Determine which files are untracked
244+
requested = set(files)
245+
241246
_, st_out, _ = await _run(
242247
"status",
243248
"--porcelain=v2",
244249
"--untracked-files=all",
245250
cwd=root,
246251
check=False,
247252
)
248-
untracked = set()
249-
for line in st_out.splitlines():
250-
if line.startswith("? "):
251-
untracked.add(line[2:])
252253

253-
tracked = [f for f in files if f not in untracked]
254-
to_delete = [f for f in files if f in untracked]
255-
256-
if tracked:
257-
await _run("checkout", "--", *tracked, cwd=root)
254+
to_unstage: list[str] = [] # staged changes that need unstaging first
255+
to_checkout: list[str] = [] # working-tree changes to restore from HEAD
256+
to_delete: list[str] = [] # untracked / newly-added files to remove
258257

258+
for line in st_out.splitlines():
259+
if line.startswith("? "):
260+
path = line[2:]
261+
if path in requested:
262+
to_delete.append(path)
263+
elif line.startswith("1 ") or line.startswith("2 "):
264+
parts = line.split(" ", 8)
265+
xy = parts[1]
266+
path = parts[-1]
267+
if line.startswith("2 "):
268+
path = path.split("\t")[0]
269+
if path not in requested:
270+
continue
271+
staged_code = xy[0]
272+
unstaged_code = xy[1]
273+
if staged_code == "A":
274+
# Newly added file: unstage → becomes untracked → delete
275+
to_unstage.append(path)
276+
to_delete.append(path)
277+
elif staged_code != ".":
278+
# Staged modification/deletion: unstage then checkout
279+
to_unstage.append(path)
280+
to_checkout.append(path)
281+
if unstaged_code != "." and staged_code != "A":
282+
# Working-tree change: checkout (deduplicated)
283+
if path not in to_checkout:
284+
to_checkout.append(path)
285+
286+
# 1. Unstage any staged changes (reverts index to HEAD)
287+
if to_unstage:
288+
await _run("restore", "--staged", "--", *to_unstage, cwd=root)
289+
290+
# 2. Restore working-tree files from HEAD
291+
if to_checkout:
292+
await _run("checkout", "--", *to_checkout, cwd=root)
293+
294+
# 3. Remove untracked / newly-added files
259295
for f in to_delete:
260296
full = os.path.join(root, f)
261297
if os.path.isfile(full):

0 commit comments

Comments
 (0)