Skip to content

Commit 75b1703

Browse files
authored
feat: introduce folio abstraction (#67)
This change also makes bot edits fully concurrent with both the working tree and index. All changes are applied to an independent tree, committed separately from the current HEAD.
1 parent 5a46298 commit 75b1703

24 files changed

+727
-637
lines changed

docs/git-draft.adoc

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ IMPORTANT: `git-draft` is WIP.
2020
[verse]
2121
git draft [options] [--generate] [--accept... | --no-accept] [--bot BOT]
2222
[--edit] [--reset | --no-reset] [TEMPLATE [VARIABLE...]]
23-
git draft [options] --finalize [--delete]
24-
git draft [options] --show-drafts [--json]
25-
git draft [options] --show-prompts [--json] [PROMPT]
23+
git draft [options] --finalize
2624
git draft [options] --show-templates [--json | [--edit] TEMPLATE]
2725

2826

@@ -38,16 +36,11 @@ git draft [options] --show-templates [--json | [--edit] TEMPLATE]
3836
--no-accept::
3937
Check out generated changes automatically.
4038
Can be repeated.
41-
This may fail if you manually edit files that the bot updates during generation.
4239

4340
-b BOT::
4441
--bot=BOT::
4542
Bot name.
4643

47-
-d::
48-
--delete::
49-
Delete finalized branch.
50-
5144
-e::
5245
--edit::
5346
Enable interactive editing of prompts and templates.
@@ -73,23 +66,9 @@ git draft [options] --show-templates [--json | [--edit] TEMPLATE]
7366
--log::
7467
Show log path and exit.
7568

76-
--reset::
77-
--no-reset::
78-
Controls behavior when staged changes are present at the start of a generate command.
79-
If enabled, these changes are automatically reset and combined with other working directory changes.
80-
Otherwise an error is raised.
81-
8269
--root::
8370
Repository search root.
8471

85-
-D::
86-
--show-drafts::
87-
List recently created drafts.
88-
89-
-P::
90-
--show-prompts::
91-
Lists recently used prompts.
92-
9372
-T::
9473
--show-templates::
9574
Lists available templates.
@@ -109,20 +88,75 @@ The workhorse command is `git draft --generate` which leverages AI to edit our c
10988
A prompt can be specified as standard input, for example `echo "Add a test for compute_offset in chart.py" | git draft --generate`.
11089
If no prompt is specified and stdin is a TTY, `$EDITOR` will be opened to enter the prompt.
11190

112-
If not on a draft branch, a new draft branch called `drafts/$parent/$hash` will be created (`$hash` is a random suffix used to guarantee uniqueness of branch names) and checked out.
113-
By default any unstaged changes are then automatically added and committed (`draft! sync`).
114-
This behavior can be disabled by passing in `--stash`, which will instead add them to the stash.
115-
Staged changes are always committed.
116-
117-
The prompt automatically gets augmented with information about the files in the repository, and give the AI access to tools for reading and writing files.
118-
Once the response has been received and changes applied, a commit is created (`draft! prompt: a short summary of the change`).
91+
By default, the prompt gets augmented with information about the files in the repository, and give the AI access to tools for reading and writing files.
92+
Once the response has been received and changes applied, a commit is created in a separate branch.
11993

12094
The `--generate` step can be repeated as many times as needed.
12195
Once you are satisfied with the changes, run `git draft --finalize` to apply them.
12296
This will check out the branch used when creating the draft, adding the final state of the draft to the worktree.
12397
Note that you can come back to an existing draft anytime (by checking its branch out), but you will not be able to apply it if its origin branch has moved since the draft was created.
12498

12599

100+
* Generate: create a new draft to the current folio, or create a new folio if none exists.
101+
* Finalize
102+
** Apply: include changes into origin branch.
103+
** Discard: abandon folio.
104+
** Save: return to original branch. Q: how to load after?
105+
* List templates
106+
107+
108+
o Foo (main)
109+
o Sync (drafts/123/pub)
110+
111+
# After generate without accept
112+
o Foo (main)
113+
o Sync (drafts/123)
114+
o draft! <prompt> (drafts/123+, refs/drafts/123/1)
115+
116+
# After generate with accept
117+
o Foo (main)
118+
o Sync
119+
|\
120+
| o draft! prompt: <prompt> (refs/drafts/123/1)
121+
o | Sync
122+
|/
123+
o Merge (drafts/123/pub)
124+
125+
126+
o Foo (main)
127+
o draft! sync
128+
|\
129+
| o draft! prompt: <prompt> (drafts/123+, refs/drafts/123/1)
130+
o Something
131+
o Also something (drafts/123)
132+
133+
134+
o Foo (main)
135+
o draft! sync
136+
|\
137+
| o draft! prompt: <prompt> (refs/drafts/123/1)
138+
o Something
139+
o Also something (drafts/123/pub)
140+
o draft! prompt: <prompt> (drafts/123+, refs/drafts/123/2)
141+
142+
143+
o Foo (main)
144+
o draft! sync (drafts/123/pub)
145+
|\
146+
| o draft! prompt: <prompt> (refs/drafts/123/1)
147+
\
148+
o draft! prompt: <prompt> (drafts/123+, refs/drafts/123/2)
149+
150+
o Foo (main)
151+
o draft! sync (drafts/123/pub)
152+
|\
153+
| o draft! prompt: <prompt> (refs/drafts/123/1)
154+
|/
155+
o draft! sync
156+
\
157+
o draft! prompt: <prompt> (drafts/123+, refs/drafts/123/2)
158+
159+
126160
== See also
127161

128162
`git(1)`

src/git_draft/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import logging
44

5-
from .bots import Action, Bot, Goal, Toolbox
5+
from .bots import Action, Bot, Goal
6+
from .toolbox import Toolbox
67

78

89
__all__ = [

src/git_draft/__main__.py

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ def callback(
6060

6161
add_command("finalize", help="apply current draft to original branch")
6262
add_command("generate", help="create or update draft from a prompt")
63-
add_command("show-drafts", short="D", help="show draft history")
64-
add_command("show-prompts", short="P", help="show prompt history")
6563
add_command("show-templates", short="T", help="show template information")
6664

6765
parser.add_option(
@@ -76,12 +74,6 @@ def callback(
7674
dest="bot",
7775
help="bot name",
7876
)
79-
parser.add_option(
80-
"-d",
81-
"--delete",
82-
help="delete draft after finalizing",
83-
action="store_true",
84-
)
8577
parser.add_option(
8678
"-e",
8779
"--edit",
@@ -102,18 +94,6 @@ def callback(
10294
action="store_const",
10395
const=0,
10496
)
105-
parser.add_option(
106-
"--no-reset",
107-
help="abort if there are any staged changes",
108-
dest="reset",
109-
action="store_false",
110-
)
111-
parser.add_option(
112-
"--reset",
113-
help="reset index before generating a new draft",
114-
dest="reset",
115-
action="store_true",
116-
)
11797
parser.add_option(
11898
"--timeout",
11999
dest="timeout",
@@ -213,33 +193,26 @@ def main() -> None: # noqa: PLR0912 PLR0915
213193
prompt = sys.stdin.read()
214194

215195
accept = Accept(opts.accept or 0)
216-
draft = drafter.generate_draft(
196+
drafter.generate_draft(
217197
prompt,
218198
bot,
219199
accept=accept,
220200
bot_name=opts.bot,
221201
prompt_transform=open_editor if editable else None,
222202
tool_visitors=[ToolPrinter()],
223-
reset=config.reset if opts.reset is None else opts.reset,
224203
)
225204
match accept:
226205
case Accept.MANUAL:
227-
print(f"Generated change in {draft.branch_name}.")
228-
case Accept.CHECKOUT:
229-
print(f"Applied change in {draft.branch_name}.")
230-
case Accept.FINALIZE | Accept.NO_REGRETS:
231-
print(f"Finalized change via {draft.branch_name}.")
206+
print("Generated draft.")
207+
case Accept.MERGE:
208+
print("Merged draft.")
209+
case Accept.FINALIZE:
210+
print("Finalized draft.")
232211
case _:
233212
raise UnreachableError()
234213
case "finalize":
235-
draft = drafter.finalize_draft(delete=opts.delete)
236-
print(f"Finalized {draft.branch_name}.")
237-
case "show-drafts":
238-
table = drafter.history_table(args[0] if args else None)
239-
if table:
240-
print(table.to_json() if opts.json else table)
241-
case "show-prompts":
242-
raise NotImplementedError() # TODO: Implement
214+
drafter.finalize_folio()
215+
print("Finalized draft folio.")
243216
case "show-templates":
244217
if args:
245218
name = args[0]

src/git_draft/common.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
import logging
99
import os
1010
from pathlib import Path
11-
import random
1211
import sqlite3
13-
import string
1412
import textwrap
1513
import tomllib
1614
from typing import Any, ClassVar, Self
@@ -41,7 +39,6 @@ class Config:
4139

4240
bots: Sequence[BotConfig] = dataclasses.field(default_factory=lambda: [])
4341
log_level: int = logging.INFO
44-
reset: bool = True
4542

4643
@staticmethod
4744
def folder_path() -> Path:
@@ -73,31 +70,11 @@ class BotConfig:
7370
pythonpath: str | None = None
7471

7572

76-
type RepoID = str
77-
78-
79-
@dataclasses.dataclass(frozen=True)
80-
class RepoConfig: # TODO: Use
81-
"""Repository-specific config"""
82-
83-
repo_id: str
84-
bot_name: str | None = None
85-
86-
8773
def config_string(arg: str) -> str:
8874
"""Dereferences environment value if the input starts with `$`"""
8975
return os.environ[arg[1:]] if arg and arg.startswith("$") else arg
9076

9177

92-
_random = random.Random()
93-
_alphabet = string.ascii_lowercase + string.digits
94-
95-
96-
def random_id(n: int) -> str:
97-
"""Generates a random length n string of lowercase letters and digits"""
98-
return "".join(_random.choices(_alphabet, k=n))
99-
100-
10178
class UnreachableError(RuntimeError):
10279
"""Indicates unreachable code was unexpectedly executed"""
10380

0 commit comments

Comments
 (0)