Skip to content

Commit 47cb02d

Browse files
committed
Implement Git integration for checkpoints, storing uncommitted changes as diffs and resetting/applying diffs during restore operations
1 parent a1a6b90 commit 47cb02d

11 files changed

Lines changed: 419 additions & 58 deletions

File tree

‎packages/vscode/src/commands/checkpoints-command/actions/create-checkpoint.ts‎

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@ import * as vscode from 'vscode'
22
import { CHECKPOINTS_STATE_KEY } from '../../../constants/state-keys'
33
import { WorkspaceProvider } from '../../../context/providers/workspace-provider'
44
import type { Checkpoint } from '../types'
5-
import { copy_workspace_to_dir } from './copy-workspace-to-dir'
65
import { get_checkpoints } from './get-checkpoints'
7-
import { get_checkpoint_path, get_incremented_description } from '../utils'
6+
import {
7+
copy_optimised_recursively,
8+
get_checkpoint_path,
9+
get_incremented_description
10+
} from '../utils'
11+
import {
12+
get_git_diff,
13+
get_git_info,
14+
is_git_repository
15+
} from '../utils/git-utils'
16+
import * as path from 'path'
817

918
export const create_checkpoint = async (
1019
workspace_provider: WorkspaceProvider,
@@ -26,7 +35,60 @@ export const create_checkpoint = async (
2635
const checkpoint_dir_uri = vscode.Uri.file(checkpoint_dir_path)
2736
await vscode.workspace.fs.createDirectory(checkpoint_dir_uri)
2837

29-
await copy_workspace_to_dir(checkpoint_dir_uri, workspace_provider)
38+
const workspace_folders = vscode.workspace.workspaceFolders!
39+
const git_data: Record<string, any> = {}
40+
let uses_git = false
41+
42+
for (const folder of workspace_folders) {
43+
const has_git = await is_git_repository(folder)
44+
45+
if (has_git) {
46+
uses_git = true
47+
const git_info = await get_git_info(folder)
48+
const diff = await get_git_diff(folder)
49+
50+
if (git_info && diff !== null) {
51+
const folder_name = folder.name
52+
git_data[folder_name] = {
53+
branch: git_info.branch,
54+
commit_hash: git_info.commit_hash,
55+
folder_name
56+
}
57+
58+
const diff_file_path = path.join(
59+
checkpoint_dir_path,
60+
`${folder_name}.diff`
61+
)
62+
await vscode.workspace.fs.writeFile(
63+
vscode.Uri.file(diff_file_path),
64+
Buffer.from(diff, 'utf8')
65+
)
66+
} else {
67+
throw new Error(
68+
`Failed to get git information for repository in ${folder.name}.`
69+
)
70+
}
71+
} else {
72+
const dest_folder_path =
73+
workspace_folders.length > 1
74+
? path.join(checkpoint_dir_path, folder.name)
75+
: checkpoint_dir_path
76+
const dest_folder_uri = vscode.Uri.file(dest_folder_path)
77+
if (workspace_folders.length > 1) {
78+
await vscode.workspace.fs.createDirectory(dest_folder_uri)
79+
}
80+
81+
const entries = await vscode.workspace.fs.readDirectory(folder.uri)
82+
for (const [name] of entries) {
83+
await copy_optimised_recursively(
84+
vscode.Uri.joinPath(folder.uri, name),
85+
vscode.Uri.joinPath(dest_folder_uri, name),
86+
folder.uri.fsPath,
87+
workspace_provider
88+
)
89+
}
90+
}
91+
}
3092

3193
const checkpoints = await get_checkpoints(context)
3294

@@ -39,7 +101,9 @@ export const create_checkpoint = async (
39101
const new_checkpoint: Checkpoint = {
40102
title,
41103
timestamp,
42-
description: final_description
104+
description: final_description,
105+
uses_git,
106+
git_data: Object.keys(git_data).length > 0 ? git_data : undefined
43107
}
44108

45109
checkpoints.unshift(new_checkpoint)
Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import * as vscode from 'vscode'
22
import { WorkspaceProvider } from '../../../context/providers/workspace-provider'
33
import type { Checkpoint } from '../types'
4-
import { copy_workspace_to_dir } from './copy-workspace-to-dir'
5-
import { get_checkpoint_path } from '../utils'
4+
import { copy_optimised_recursively, get_checkpoint_path } from '../utils'
5+
import {
6+
get_git_diff,
7+
get_git_info,
8+
is_git_repository
9+
} from '../utils/git-utils'
10+
import * as path from 'path'
611

712
export const create_temporary_checkpoint = async (
813
workspace_provider: WorkspaceProvider
@@ -13,12 +18,67 @@ export const create_temporary_checkpoint = async (
1318
const checkpoint_dir_uri = vscode.Uri.file(checkpoint_dir_path)
1419
await vscode.workspace.fs.createDirectory(checkpoint_dir_uri)
1520

16-
await copy_workspace_to_dir(checkpoint_dir_uri, workspace_provider)
21+
const workspace_folders = vscode.workspace.workspaceFolders!
22+
const git_data: Record<string, any> = {}
23+
let uses_git = false
24+
25+
for (const folder of workspace_folders) {
26+
const has_git = await is_git_repository(folder)
27+
28+
if (has_git) {
29+
uses_git = true
30+
const git_info = await get_git_info(folder)
31+
const diff = await get_git_diff(folder)
32+
33+
if (git_info && diff !== null) {
34+
const folder_name = folder.name
35+
git_data[folder_name] = {
36+
branch: git_info.branch,
37+
commit_hash: git_info.commit_hash,
38+
folder_name
39+
}
40+
41+
const diff_file_path = path.join(
42+
checkpoint_dir_path,
43+
`${folder_name}.diff`
44+
)
45+
await vscode.workspace.fs.writeFile(
46+
vscode.Uri.file(diff_file_path),
47+
Buffer.from(diff, 'utf8')
48+
)
49+
} else {
50+
throw new Error(
51+
`Failed to get git information for repository in ${folder.name}.`
52+
)
53+
}
54+
} else {
55+
const dest_folder_path =
56+
workspace_folders.length > 1
57+
? path.join(checkpoint_dir_path, folder.name)
58+
: checkpoint_dir_path
59+
const dest_folder_uri = vscode.Uri.file(dest_folder_path)
60+
if (workspace_folders.length > 1) {
61+
await vscode.workspace.fs.createDirectory(dest_folder_uri)
62+
}
63+
64+
const entries = await vscode.workspace.fs.readDirectory(folder.uri)
65+
for (const [name] of entries) {
66+
await copy_optimised_recursively(
67+
vscode.Uri.joinPath(folder.uri, name),
68+
vscode.Uri.joinPath(dest_folder_uri, name),
69+
folder.uri.fsPath,
70+
workspace_provider
71+
)
72+
}
73+
}
74+
}
1775

1876
const new_checkpoint: Checkpoint = {
1977
timestamp,
2078
title: '',
21-
is_temporary: true
79+
is_temporary: true,
80+
uses_git,
81+
git_data: Object.keys(git_data).length > 0 ? git_data : undefined
2282
}
2383
return new_checkpoint
2484
}

‎packages/vscode/src/commands/checkpoints-command/actions/delete-checkpoint.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export const delete_checkpoint = async (params: {
77
context: vscode.ExtensionContext
88
checkpoint_to_delete: Checkpoint
99
}) => {
10-
// Remove from state
1110
const checkpoints =
1211
params.context.workspaceState.get<Checkpoint[]>(
1312
CHECKPOINTS_STATE_KEY,
@@ -21,7 +20,6 @@ export const delete_checkpoint = async (params: {
2120
updated_checkpoints
2221
)
2322

24-
// Delete files
2523
try {
2624
const checkpoint_path = get_checkpoint_path(
2725
params.checkpoint_to_delete.timestamp

‎packages/vscode/src/commands/checkpoints-command/actions/restore-checkpoint.ts‎

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import * as vscode from 'vscode'
2-
import { TEMPORARY_CHECKPOINT_TIMESTAMP_STATE_KEY } from '../../../constants/state-keys'
2+
import { TEMPORARY_CHECKPOINT_STATE_KEY } from '../../../constants/state-keys'
33
import { WorkspaceProvider } from '../../../context/providers/workspace-provider'
44
import type { Checkpoint } from '../types'
55
import { create_temporary_checkpoint } from './create-temporary-checkpoint'
66
import { delete_checkpoint } from './delete-checkpoint'
77
import { sync_workspace_from_dir } from './sync-workspace-from-dir'
88
import { get_checkpoint_path } from '../utils'
9+
import { apply_git_diff } from '../utils/git-utils'
10+
import * as path from 'path'
911

1012
export const restore_checkpoint = async (params: {
1113
checkpoint: Checkpoint
@@ -16,12 +18,14 @@ export const restore_checkpoint = async (params: {
1618
let temp_checkpoint: Checkpoint | undefined
1719
try {
1820
if (!params.options?.skip_confirmation) {
19-
const old_temp_timestamp = params.context.workspaceState.get<number>(
20-
TEMPORARY_CHECKPOINT_TIMESTAMP_STATE_KEY
21+
const old_temp_checkpoint = params.context.workspaceState.get<Checkpoint>(
22+
TEMPORARY_CHECKPOINT_STATE_KEY
2123
)
22-
if (old_temp_timestamp) {
24+
if (old_temp_checkpoint) {
2325
try {
24-
const checkpoint_path = get_checkpoint_path(old_temp_timestamp)
26+
const checkpoint_path = get_checkpoint_path(
27+
old_temp_checkpoint.timestamp
28+
)
2529
await vscode.workspace.fs.delete(vscode.Uri.file(checkpoint_path), {
2630
recursive: true
2731
})
@@ -36,19 +40,19 @@ export const restore_checkpoint = async (params: {
3640
params.workspace_provider
3741
)
3842
await params.context.workspaceState.update(
39-
TEMPORARY_CHECKPOINT_TIMESTAMP_STATE_KEY,
40-
temp_checkpoint.timestamp
43+
TEMPORARY_CHECKPOINT_STATE_KEY,
44+
temp_checkpoint
4145
)
4246
}
4347
} catch (err: any) {
4448
vscode.window.showErrorMessage(
4549
`Failed to create temporary checkpoint for revert: ${err.message}`
4650
)
4751
await params.context.workspaceState.update(
48-
TEMPORARY_CHECKPOINT_TIMESTAMP_STATE_KEY,
52+
TEMPORARY_CHECKPOINT_STATE_KEY,
4953
undefined
5054
)
51-
return // Don't proceed
55+
return
5256
}
5357

5458
try {
@@ -65,10 +69,52 @@ export const restore_checkpoint = async (params: {
6569
params.checkpoint.timestamp
6670
)
6771
const checkpoint_dir_uri = vscode.Uri.file(checkpoint_dir_path)
68-
await sync_workspace_from_dir({
69-
source_dir_uri: checkpoint_dir_uri,
70-
workspace_provider: params.workspace_provider
71-
})
72+
73+
if (params.checkpoint.uses_git && params.checkpoint.git_data) {
74+
const workspace_folders = vscode.workspace.workspaceFolders!
75+
76+
for (const folder of workspace_folders) {
77+
const folder_name = folder.name
78+
const git_info = params.checkpoint.git_data[folder_name]
79+
80+
if (git_info) {
81+
const diff_file_path = path.join(
82+
checkpoint_dir_path,
83+
`${folder_name}.diff`
84+
)
85+
try {
86+
const diff_content = await vscode.workspace.fs.readFile(
87+
vscode.Uri.file(diff_file_path)
88+
)
89+
const diff = Buffer.from(diff_content).toString('utf8')
90+
91+
const success = await apply_git_diff(
92+
folder,
93+
diff,
94+
git_info.commit_hash,
95+
git_info.branch
96+
)
97+
98+
if (!success) {
99+
throw new Error(`Failed to apply git diff for ${folder_name}`)
100+
}
101+
} catch (err) {
102+
console.error(`Error restoring git checkpoint: ${err}`)
103+
throw err
104+
}
105+
} else {
106+
await sync_workspace_from_dir({
107+
source_dir_uri: checkpoint_dir_uri,
108+
workspace_provider: params.workspace_provider
109+
})
110+
}
111+
}
112+
} else {
113+
await sync_workspace_from_dir({
114+
source_dir_uri: checkpoint_dir_uri,
115+
workspace_provider: params.workspace_provider
116+
})
117+
}
72118
}
73119
)
74120

@@ -93,7 +139,7 @@ export const restore_checkpoint = async (params: {
93139
checkpoint_to_delete: temp_checkpoint
94140
})
95141
await params.context.workspaceState.update(
96-
TEMPORARY_CHECKPOINT_TIMESTAMP_STATE_KEY,
142+
TEMPORARY_CHECKPOINT_STATE_KEY,
97143
undefined
98144
)
99145
}
@@ -110,7 +156,7 @@ export const restore_checkpoint = async (params: {
110156
checkpoint_to_delete: temp_checkpoint
111157
})
112158
await params.context.workspaceState.update(
113-
TEMPORARY_CHECKPOINT_TIMESTAMP_STATE_KEY,
159+
TEMPORARY_CHECKPOINT_STATE_KEY,
114160
undefined
115161
)
116162
}

0 commit comments

Comments
 (0)