Skip to content

Commit f5e4bcf

Browse files
authored
Merge pull request #50 from artmoskvin/45-apply-diffs
Update files with diffs
2 parents ee35d8b + 931bf15 commit f5e4bcf

19 files changed

Lines changed: 1503 additions & 523 deletions

cmd/hide/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"path/filepath"
1111

1212
"github.com/artmoskvin/hide/pkg/devcontainer"
13-
"github.com/artmoskvin/hide/pkg/filemanager"
13+
"github.com/artmoskvin/hide/pkg/files"
1414
"github.com/artmoskvin/hide/pkg/handlers"
1515
"github.com/artmoskvin/hide/pkg/project"
1616
"github.com/artmoskvin/hide/pkg/util"
@@ -66,7 +66,7 @@ func main() {
6666
projectsDir := filepath.Join(home, ProjectsDir)
6767

6868
projectManager := project.NewProjectManager(containerRunner, projectStore, projectsDir)
69-
fileManager := filemanager.NewFileManager()
69+
fileManager := files.NewFileManager()
7070
createProjectHandler := handlers.CreateProjectHandler{Manager: projectManager}
7171
deleteProjectHandler := handlers.DeleteProjectHandler{Manager: projectManager}
7272
createTaskHandler := handlers.CreateTaskHandler{Manager: projectManager}

docs/quickstart.md

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ To interact with the Hide server, we need to create a client. We can do this by
1313
```python
1414
import hide
1515
from hide.devcontainer.model import ImageDevContainer
16-
from hide.model import Repository
16+
from hide.model import FileUpdateType, UdiffUpdate, Repository
1717
from hide.toolkit import Toolkit
1818

1919
hide_client = hide.Client()
@@ -70,7 +70,11 @@ Creating a project can take some time. Under the hood, Hide clones the repositor
7070
Having created a project, we can now start working with it. You could notice that the devcontainer [configuration](https://github.com/artmoskvin/tiny-math-service/blob/main/.devcontainer.json) for the [Tiny Math Service](https://github.com/artmoskvin/tiny-math-service) contains a `customizations` section that defines a custom task called `test`. We can use this task to run tests for our project:
7171

7272
```python
73-
result = hide_client.run_task(project.id, alias="test")
73+
result = hide_client.run_task(
74+
project_id=project.id,
75+
alias="test"
76+
)
77+
7478
print(result.stdOut)
7579
```
7680

@@ -79,7 +83,11 @@ The print statement will output the test results.
7983
Aliases are convenient when referring to frequently used commands. Running tasks is powered by Task API which also allows us to run arbitrary shell commands by providing the `command` parameter:
8084

8185
```python
82-
result = hide_client.run_task(project.id, command="pwd")
86+
result = hide_client.run_task(
87+
project_id=project.id,
88+
command="pwd"
89+
)
90+
8391
print(result.stdOut)
8492
```
8593

@@ -90,36 +98,84 @@ The tasks are executed from the project root so the print statement will output
9098
We can also read and update files in the project. For example, we can read the `maths.py` file and add a new endpoint in it. First, let's read the file:
9199

92100
```python
93-
result = hide_client.get_file(project.id, path="my_tiny_service/api/routers/maths.py")
101+
result = hide_client.get_file(
102+
project_id=project.id,
103+
path="my_tiny_service/api/routers/maths.py"
104+
)
105+
94106
print(result.content)
95107
```
96108

97109
This will print the content of the `maths.py` file.
98110

99-
Coding agents often update files by generating diffs. Therefore it can be important to include line numbers when reading files. With Hide, we can include line numbers by setting the `show_line_numbers` parameter to `True`:
111+
Coding agents often update files by adding/replacing lines or by applying unified diffs. Therefore it can be important to include line numbers when reading files. With Hide, we can include line numbers when reading files by setting the `show_line_numbers` parameter to `True`:
100112

101113
```python
102114
result = hide_client.get_file(
103-
project.id, path="my_tiny_service/api/routers/maths.py", show_line_numbers=True
115+
project_id=project.id,
116+
path="my_tiny_service/api/routers/maths.py",
117+
show_line_numbers=True
104118
)
105119

106120
print(result.content)
107121
```
108122

109123
This will print the content of the `maths.py` file with line numbers.
110124

111-
By default, Hide will show the first 100 lines of the file. We can change this by setting the `start_line` and `num_lines` parameters:
125+
By default, Hide returns the first 100 lines of the file. We can change this by setting the `start_line` and `num_lines` parameters:
112126

113127
```python
114128
result = hide_client.get_file(
115-
project.id,
129+
project_id=project.id,
116130
path="my_tiny_service/api/routers/maths.py",
117131
show_line_numbers=True,
118132
start_line=10,
119-
num_lines=20,
133+
num_lines=200,
134+
)
135+
136+
print(result.content)
137+
```
138+
139+
This will print the content of the `maths.py` file with 200 lines starting from line 10.
140+
141+
Updating files can be done in three ways: by replacing the entire file, by updating lines, or by applying unified diffs. For this quickstart we will use the unified diff option:
142+
143+
```python
144+
145+
patch = """\
146+
--- a/my_tiny_service/api/routers/maths.py
147+
+++ b/my_tiny_service/api/routers/maths.py
148+
@@ -113,3 +113,17 @@
149+
status_code=starlette.status.HTTP_400_BAD_REQUEST,
150+
detail="Division by zero is not allowed",
151+
) from e
152+
+
153+
+
154+
+@router.post(
155+
+ "/exp",
156+
+ summary="Calculate the exponent of two numbers",
157+
+ response_model=MathsResult,
158+
+)
159+
+def exp(maths_input: MathsIn) -> MathsResult:
160+
+ \"\"\"Calculates the exponent of two whole numbers.\"\"\"
161+
+ return MathsResult(
162+
+ **maths_input.dict(),
163+
+ operation="exp",
164+
+ result=maths_input.number1 ** maths_input.number2,
165+
+ )
166+
"""
167+
168+
result = hide_client.update_file(
169+
project_id=project.id,
170+
path='my_tiny_service/api/routers/maths.py',
171+
type=FileUpdateType.UDIFF,
172+
update=UdiffUpdate(patch=patch)
120173
)
121174

122175
print(result.content)
123176
```
124177

125-
This will print the content of the `maths.py` file with 20 lines starting from line 10.
178+
This will apply the unified diff to the file and return the updated content.
179+
180+
For more information on all the available update types and their parameters, see the [Files API](usage/files.md#updating-a-file) documentation.
181+

docs/usage/files.md

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,125 @@ To specify a range of lines, set the `startLine` and `numLines` parameters:
9898

9999
### Updating a File
100100

101-
To update the contents of an existing file:
101+
Updating files can be done in three ways: by replacing the entire file, by updating lines, or by applying unified diffs. We will look at each of these in the next sections.
102+
103+
#### Replacing the entire file
104+
105+
To replace the entire file, we can use the update type `overwrite` and provide the new content as the `content` parameter:
106+
107+
=== "python"
108+
109+
```python
110+
from hide.model import FileUpdateType, OverwriteUpdate
111+
112+
result = hide_client.update_file(
113+
project_id="my-project",
114+
path="path/to/file.py",
115+
type=FileUpdateType.OVERWRITE,
116+
update=OverwriteUpdate(
117+
content="def hello_world():\n print('Hello, World!')\n"
118+
)
119+
)
120+
```
102121

103122
=== "curl"
104123

105124
```bash
106-
curl -X PUT http://localhost:8080/projects/{project_id}/files/example.txt \
125+
curl -X PUT http://localhost:8080/projects/my-project/files/path/to/file.py \
107126
-H "Content-Type: application/json" \
108-
-d '{"content": "Updated content!"}'
127+
-d '{
128+
"type": "overwrite",
129+
"overwrite": {
130+
"content": "def hello_world():\n print('Hello, World!')\n"
131+
}
132+
}'
109133
```
110134

135+
This will replace the entire file with the new content.
136+
137+
#### Updating lines
138+
139+
To update lines, we can use the update type `linediff` and provide the line diff as the `lineDiff` parameter:
140+
111141
=== "python"
112142

113143
```python
114-
# Coming soon
144+
from hide.model import FileUpdateType, LineDiffUpdate
145+
146+
result = hide_client.update_file(
147+
project_id="my-project",
148+
path="path/to/file.py",
149+
type=FileUpdateType.LINEDIFF,
150+
update=LineDiffUpdate(
151+
start_line=1,
152+
end_line=2,
153+
content="def hello_world():\n print('Hello, World!')\n"
154+
),
155+
)
156+
```
157+
158+
=== "curl"
159+
160+
```bash
161+
curl -X PUT http://localhost:8080/projects/my-project/files/path/to/file.py \
162+
-H "Content-Type: application/json" \
163+
-d '{
164+
"type": "linediff",
165+
"lineDiff": {
166+
"startLine": 1,
167+
"endLine": 2,
168+
"content": "def hello_world():\n print('Hello, World!')\n"
169+
}
170+
}'
115171
```
116172

117-
This will update the contents of the file `example.txt` in your project's root directory to `Updated content!`. This API call will effectively overwrite the existing file.
173+
This will update the lines from line 1 to 3 with the new content.
174+
175+
#### Applying unified diffs
176+
177+
To apply unified diffs, we can use the update type `udiff` and provide the patch as the `patch` parameter:
178+
179+
=== "python"
180+
181+
```python
182+
from hide.model import FileUpdateType, UdiffUpdate
183+
184+
result = hide_client.update_file(
185+
project_id="my-project",
186+
path="path/to/file.py",
187+
type=FileUpdateType.UDIFF,
188+
udiff=UdiffUpdate(
189+
patch="""--- path/to/file.py
190+
+++ path/to/file.py
191+
@@ -1,2 +1,2 @@
192+
def hello_world():
193+
- print('Hello, World!')
194+
+ print('Hello, World!!!')
195+
"""
196+
)
197+
)
198+
```
199+
200+
=== "curl"
201+
202+
```bash
203+
curl -X PUT http://localhost:8080/projects/my-project/files/path/to/file.py \
204+
-H "Content-Type: application/json" \
205+
-d '{
206+
"type": "udiff",
207+
"udiff": {
208+
"patch": """--- path/to/file.py
209+
+++ path/to/file.py
210+
@@ -1,2 +1,2 @@
211+
def hello_world():
212+
- print('Hello, World!')
213+
+ print('Hello, World!!!')
214+
"""
215+
}
216+
}'
217+
```
218+
219+
This will apply the unified diff to the file and update the lines accordingly.
118220

119221
### Deleting a File
120222

@@ -134,10 +236,6 @@ To delete a specific file:
134236

135237
This will delete the file `example.txt` in your project's root directory.
136238

137-
### :construction: Applying Diffs
138-
139-
Coming soon
140-
141239
## Error Handling
142240

143241
The API uses standard HTTP status codes to indicate the success or failure of requests:

go.mod

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ module github.com/artmoskvin/hide
22

33
go 1.22.1
44

5-
require github.com/docker/docker v26.1.3+incompatible
5+
require (
6+
github.com/docker/docker v26.1.3+incompatible
7+
github.com/docker/go-connections v0.5.0
8+
)
69

710
require (
811
github.com/Microsoft/go-winio v0.6.2 // indirect
912
github.com/Microsoft/hcsshim v0.12.3 // indirect
13+
github.com/bluekeyes/go-gitdiff v0.7.4 // indirect
1014
github.com/containerd/containerd v1.7.17 // indirect
1115
github.com/containerd/log v0.1.0 // indirect
1216
github.com/distribution/reference v0.6.0 // indirect
13-
github.com/docker/go-connections v0.5.0 // indirect
1417
github.com/docker/go-units v0.5.0 // indirect
1518
github.com/felixge/httpsnoop v1.0.4 // indirect
1619
github.com/go-logr/logr v1.4.1 // indirect
@@ -27,13 +30,15 @@ require (
2730
github.com/opencontainers/image-spec v1.1.0 // indirect
2831
github.com/pkg/errors v0.9.1 // indirect
2932
github.com/sirupsen/logrus v1.9.3 // indirect
33+
github.com/spf13/afero v1.11.0 // indirect
3034
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
3135
go.opentelemetry.io/otel v1.26.0 // indirect
3236
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect
3337
go.opentelemetry.io/otel/metric v1.26.0 // indirect
3438
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
3539
go.opentelemetry.io/otel/trace v1.26.0 // indirect
3640
golang.org/x/sys v0.19.0 // indirect
41+
golang.org/x/text v0.14.0 // indirect
3742
golang.org/x/time v0.5.0 // indirect
3843
gotest.tools/v3 v3.5.1 // indirect
3944
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
66
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
77
github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0=
88
github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
9+
github.com/bluekeyes/go-gitdiff v0.7.4 h1:pKFVC/HCQckkBTqhQPc5osCXdDylYwjOGHnV0FUi43g=
10+
github.com/bluekeyes/go-gitdiff v0.7.4/go.mod h1:QpfYYO1E0fTVHVZAZKiRjtSGY9823iCdvGXBcEzHGbM=
911
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
1012
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
1113
github.com/containerd/containerd v1.7.17 h1:KjNnn0+tAVQHAoaWRjmdak9WlvnFR/8rU1CHHy8Rm2A=
@@ -62,6 +64,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
6264
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6365
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
6466
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
67+
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
68+
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
6569
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6670
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
6771
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=

0 commit comments

Comments
 (0)