-
Notifications
You must be signed in to change notification settings - Fork 0
feat(git): add hunk revert #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5098,15 +5098,19 @@ export function App({ initialAppSettings = null }: { initialAppSettings?: AppSet | |
| ); | ||
| }, [runRepoOperation]); | ||
|
|
||
| const applySelectedHunk = useCallback(async (patch: string): Promise<void> => { | ||
| const applySelectedHunk = useCallback(async (patch: string, discard = false): Promise<void> => { | ||
| const current = stateRef.current; | ||
| const selection = current.selection; | ||
| if (!selection || current.diffChanged) { | ||
| if (!selection || current.diffChanged || (discard && selection.side !== "unstaged")) { | ||
| return; | ||
| } | ||
| const repoPath = current.repoPath; | ||
|
|
||
| const result = selection.side === "unstaged" | ||
| const result = discard | ||
| ? await runRepoOperation("Reverting hunk", selection, (operationId) => | ||
| window.githead.discardHunk({ repoPath, path: selection.path, side: selection.side, patch, operationId }) | ||
| ) | ||
| : selection.side === "unstaged" | ||
| ? await runRepoOperation("Staging hunk", selection, (operationId) => | ||
| window.githead.stageHunk({ | ||
| repoPath, | ||
|
|
@@ -10489,7 +10493,7 @@ function StatusView({ | |
| onUnstageFiles: (paths: string[], selection?: FileSelection) => void; | ||
| onRefreshDiff: () => void; | ||
| onDownloadImage: () => void; | ||
| onApplyHunk: (patch: string) => void; | ||
| onApplyHunk: (patch: string, discard?: boolean) => void; | ||
| onContextAction: (file: GitStatusFile, side: GitDiffSide, kind: ContextActionKind, paths?: string[]) => void; | ||
| onUpdateSubmodules: (path?: string) => void; | ||
| onSyncSubmodules: () => void; | ||
|
|
@@ -10523,7 +10527,8 @@ function StatusView({ | |
| ? { | ||
| side: selectedSide, | ||
| disabled: disabled || diffChanged, | ||
| onApply: onApplyHunk | ||
| onApply: onApplyHunk, | ||
| onDiscard: selectedSide === "unstaged" ? (patch) => onApplyHunk(patch, true) : undefined | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an initialized submodule is checked out at a different commit, its unstaged diff is a text hunk and this unconditionally exposes Useful? React with 👍 / 👎. |
||
| } | ||
| : undefined | ||
| ), [canApplyHunks, diffChanged, disabled, onApplyHunk, selectedSide]); | ||
|
|
@@ -11168,6 +11173,7 @@ function DiffPanel({ | |
| } | ||
|
|
||
| interface DiffHunkAction { | ||
| onDiscard?: ((patch: string) => void) | undefined; | ||
| side: GitDiffSide; | ||
| disabled: boolean; | ||
| onApply: (patch: string) => void; | ||
|
|
@@ -11184,6 +11190,8 @@ const DiffRows = memo(function DiffRows({ | |
| truncated: boolean; | ||
| hunkAction?: DiffHunkAction | undefined; | ||
| }): ReactNode { | ||
| const [discardTarget, setDiscardTarget] = useState<{ patch: string; filePath: string; text: string } | null>(null); | ||
| const discardTargetCurrent = discardTarget?.filePath === filePath && discardTarget.text === text; | ||
| const sessionRef = useRef<ReturnType<typeof createDiffProcessingSession> | null>(null); | ||
| const { | ||
| value: processedValue, | ||
|
|
@@ -11233,6 +11241,21 @@ const DiffRows = memo(function DiffRows({ | |
| return ( | ||
| <div className="diff-rows" ref={rootRef} aria-busy={!processed} onPointerDownCapture={onPointerDownCapture}> | ||
| {!processed ? <LoadingState label="Processing diff" className="min-h-32" /> : null} | ||
| <Dialog open={Boolean(discardTarget && discardTargetCurrent && hunkAction?.onDiscard)} onOpenChange={(open) => { if (!open) setDiscardTarget(null); }}> | ||
| <DialogContent> | ||
| <DialogHeader> | ||
| <DialogTitle>Revert this hunk?</DialogTitle> | ||
| <DialogDescription>The changes in this hunk will be reverted. Other hunks and staged changes will be kept.</DialogDescription> | ||
| </DialogHeader> | ||
| <DialogFooter> | ||
| <Button type="button" variant="outline" onClick={() => setDiscardTarget(null)}>Cancel</Button> | ||
| <Button type="button" variant="destructive" disabled={hunkAction?.disabled || !discardTargetCurrent} onClick={() => { | ||
| if (discardTarget && discardTargetCurrent && !hunkAction?.disabled) hunkAction?.onDiscard?.(discardTarget.patch); | ||
| setDiscardTarget(null); | ||
| }}>Revert changes</Button> | ||
| </DialogFooter> | ||
| </DialogContent> | ||
| </Dialog> | ||
| {groups.map((group, groupIndex) => { | ||
| const groupKey = `${groupIndex}:${group.kind}:${group.rows[0]?.text ?? ""}`; | ||
| const rowViews = group.rows.flatMap((row, rowIndex) => { | ||
|
|
@@ -11259,6 +11282,18 @@ const DiffRows = memo(function DiffRows({ | |
| <span aria-hidden="true" /> | ||
| <span className="diff-hunk-title">{formatHunkTitle(group.rows, hunkNumber)}</span> | ||
| <span className="diff-hunk-actions"> | ||
| {hunkAction?.onDiscard && group.patch ? ( | ||
| <Button | ||
| type="button" | ||
| variant="outline" | ||
| size="xs" | ||
| className="diff-hunk-action" | ||
| disabled={hunkAction.disabled} | ||
| onClick={() => setDiscardTarget({ patch: group.patch!, filePath, text })} | ||
| > | ||
| Revert Hunk | ||
| </Button> | ||
| ) : null} | ||
| {hunkAction && group.patch ? ( | ||
| <TooltipTarget content={hunkActionLabel}> | ||
| <Button | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a file has both an unstaged mode change (such as
chmod +x) and content edits, the selected hunk patch still contains the file-levelold mode/new modeheaders collected bygroupDiffRowsByHunk. Passing that patch directly togit apply --reversetherefore reverts the executable-bit change along with the selected content hunk, silently discarding an unrelated working-tree edit. Remove file-mode metadata from the patch used for this operation so that only the chosen hunk is reversed.Useful? React with 👍 / 👎.