Skip to content

fix: set git config + misc daemon improvements #677

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

Merged
merged 8 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/codegen/cli/commands/run/run_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def run_daemon(session: CodegenSession, function, diff_preview: int | None = Non
limited_diff = "\n".join(diff_lines[:diff_preview])

if truncated:
limited_diff += "\n\n...\n\n[yellow]diff truncated to {diff_preview} lines, view the full change set on your local file system after using run with `--apply-local`[/yellow]"
limited_diff += f"\n\n...\n\n[yellow]diff truncated to {diff_preview} lines[/yellow]"

panel = Panel(limited_diff, title="[bold]Diff Preview[/bold]", border_style="blue", padding=(1, 2), expand=False)
rich.print(panel)
Expand Down
50 changes: 37 additions & 13 deletions src/codegen/cli/commands/start/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import platform as py_platform
import subprocess
from importlib.metadata import version
from pathlib import Path
Expand All @@ -18,12 +19,11 @@


@click.command(name="start")
@click.option("--platform", "-t", type=click.Choice(["linux/amd64", "linux/arm64", "linux/amd64,linux/arm64"]), default="linux/amd64,linux/arm64", help="Target platform(s) for the Docker image")
@click.option("--port", "-p", type=int, default=None, help="Port to run the server on")
@click.option("--detached", "-d", is_flag=True, help="Run the server in detached mode")
@click.option("--skip-build", is_flag=True, help="Skip building the Docker image")
@click.option("--force", "-f", is_flag=True, help="Force start the server even if it is already running")
def start_command(port: int | None, platform: str, detached: bool = False, skip_build: bool = False, force: bool = False) -> None:
def start_command(port: int | None, detached: bool = False, skip_build: bool = False, force: bool = False) -> None:
"""Starts a local codegen server"""
repo_path = Path.cwd().resolve()
repo_config = RepoConfig.from_repo_path(str(repo_path))
Expand All @@ -42,15 +42,10 @@ def start_command(port: int | None, platform: str, detached: bool = False, skip_

try:
if not skip_build:
rich.print("[bold blue]Building Docker image...[/bold blue]")
_build_docker_image(codegen_root, platform)
rich.print("[bold blue]Starting Docker container...[/bold blue]")
_build_docker_image(codegen_root)
_run_docker_container(repo_config, port, detached)
rich.print(Panel(f"[green]Server started successfully![/green]\nAccess the server at: [bold]http://{_default_host}:{port}[/bold]", box=ROUNDED, title="Codegen Server"))
# TODO: memory snapshot here
except subprocess.CalledProcessError as e:
rich.print(f"[bold red]Error:[/bold red] Failed to {e.cmd[0]} Docker container")
raise click.Abort()
except Exception as e:
rich.print(f"[bold red]Error:[/bold red] {e!s}")
raise click.Abort()
Expand All @@ -75,7 +70,8 @@ def _handle_existing_container(repo_config: RepoConfig, container: DockerContain
click.Abort()


def _build_docker_image(codegen_root: Path, platform: str) -> None:
def _build_docker_image(codegen_root: Path) -> None:
platform = _get_platform()
build_cmd = [
"docker",
"buildx",
Expand All @@ -89,11 +85,31 @@ def _build_docker_image(codegen_root: Path, platform: str) -> None:
"--load",
str(codegen_root),
]
rich.print(f"build_cmd: {str.join(' ', build_cmd)}")
rich.print(
Panel(
f"{str.join(' ', build_cmd)}",
box=ROUNDED,
title="Running Build Command",
style="blue",
padding=(1, 1),
)
)
subprocess.run(build_cmd, check=True)


def _get_platform() -> str:
machine = py_platform.machine().lower()
if machine in ("x86_64", "amd64"):
return "linux/amd64"
elif machine in ("arm64", "aarch64"):
return "linux/arm64"
else:
rich.print(f"[yellow]Warning: Unknown architecture {machine}, defaulting to linux/amd64[/yellow]")
return "linux/amd64"


def _run_docker_container(repo_config: RepoConfig, port: int, detached: bool) -> None:
rich.print("[bold blue]Starting Docker container...[/bold blue]")
container_repo_path = f"/app/git/{repo_config.name}"
name_args = ["--name", f"{repo_config.name}"]
envvars = {
Expand All @@ -108,9 +124,17 @@ def _run_docker_container(repo_config: RepoConfig, port: int, detached: bool) ->
entry_point = f"uv run --frozen uvicorn codegen.runner.servers.local_daemon:app --host {_default_host} --port {port}"
port_args = ["-p", f"{port}:{port}"]
detached_args = ["-d"] if detached else []
run_cmd = ["docker", "run", *detached_args, *port_args, *name_args, *mount_args, *envvars_args, CODEGEN_RUNNER_IMAGE, entry_point]

rich.print(f"run_cmd: {str.join(' ', run_cmd)}")
run_cmd = ["docker", "run", "--rm", *detached_args, *port_args, *name_args, *mount_args, *envvars_args, CODEGEN_RUNNER_IMAGE, entry_point]

rich.print(
Panel(
f"{str.join(' ', run_cmd)}",
box=ROUNDED,
title="Running Run Command",
style="blue",
padding=(1, 1),
)
)
subprocess.run(run_cmd, check=True)

if detached:
Expand Down
36 changes: 15 additions & 21 deletions src/codegen/git/repo_operator/repo_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,31 +145,25 @@ def git_cli(self) -> GitCLI:
for level in levels:
with git_cli.config_reader(level) as reader:
if reader.has_option("user", "name") and not username:
username = reader.get("user", "name")
user_level = level
username = username or reader.get("user", "name")
user_level = user_level or level
if reader.has_option("user", "email") and not email:
email = reader.get("user", "email")
email_level = level
if self.bot_commit:
self._set_bot_email(git_cli)
email = email or reader.get("user", "email")
email_level = email_level or level

# We need a username and email to commit, so if they're not set, set them to the bot's
if not username or self.bot_commit:
self._set_bot_username(git_cli)
else:
# we need a username and email to commit, so if they're not set, set them to the bot's
# Case 1: username is not set: set it to the bot's
if not username:
self._set_bot_username(git_cli)
# Case 2: username is set to the bot's at the repo level, but something else is set at the user level: unset it
elif username != CODEGEN_BOT_NAME and user_level != "repository":
self._unset_bot_username(git_cli)
# 3: Caseusername is only set at the repo level: do nothing
else:
pass # no-op to make the logic clearer
# Repeat for email
if not email:
self._set_bot_email(git_cli)
if not email or self.bot_commit:
self._set_bot_email(git_cli)

elif email != CODEGEN_BOT_EMAIL and email_level != "repository":
# If user config is set at a level above the repo level: unset it
if not self.bot_commit:
if username and username != CODEGEN_BOT_NAME and user_level != "repository":
self._unset_bot_username(git_cli)
if email and email != CODEGEN_BOT_EMAIL and email_level != "repository":
self._unset_bot_email(git_cli)

return git_cli

@property
Expand Down
4 changes: 2 additions & 2 deletions src/codegen/runner/sandbox/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ class SandboxRunner:
codebase: CodebaseType
executor: SandboxExecutor

def __init__(self, repo_config: RepoConfig) -> None:
def __init__(self, repo_config: RepoConfig, op: RepoOperator | None = None) -> None:
self.repo = repo_config
self.op = RepoOperator(repo_config=self.repo, setup_option=SetupOption.PULL_OR_CLONE, bot_commit=True)
self.op = op or RepoOperator(repo_config=self.repo, setup_option=SetupOption.PULL_OR_CLONE, bot_commit=True)
self.commit = self.op.git_cli.head.commit

async def warmup(self) -> None:
Expand Down
10 changes: 7 additions & 3 deletions src/codegen/runner/servers/local_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from fastapi import FastAPI

from codegen.git.configs.constants import CODEGEN_BOT_EMAIL, CODEGEN_BOT_NAME
from codegen.git.repo_operator.repo_operator import RepoOperator
from codegen.git.schemas.enums import SetupOption
from codegen.git.schemas.repo_config import RepoConfig
from codegen.runner.enums.warmup_state import WarmupState
from codegen.runner.models.apis import (
Expand All @@ -14,14 +16,15 @@
)
from codegen.runner.models.codemod import Codemod, CodemodRunResult
from codegen.runner.sandbox.runner import SandboxRunner
from codegen.shared.logging.get_logger import get_logger

# Configure logging at module level
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
force=True,
)
logger = logging.getLogger(__name__)
logger = get_logger(__name__)

server_info: ServerInfo
runner: SandboxRunner
Expand All @@ -37,13 +40,14 @@ async def lifespan(server: FastAPI):
server_info = ServerInfo(repo_name=repo_config.full_name or repo_config.name)

# Set the bot email and username
op = RepoOperator(repo_config=repo_config, setup_option=SetupOption.SKIP, bot_commit=True)
runner = SandboxRunner(repo_config=repo_config, op=op)
logger.info(f"Configuring git user config to {CODEGEN_BOT_EMAIL} and {CODEGEN_BOT_NAME}")
runner = SandboxRunner(repo_config=repo_config)
runner.op.git_cli.git.config("user.email", CODEGEN_BOT_EMAIL)
runner.op.git_cli.git.config("user.name", CODEGEN_BOT_NAME)

# Parse the codebase
logger.info(f"Starting up sandbox fastapi server for repo_name={repo_config.name}")
logger.info(f"Starting up fastapi server for repo_name={repo_config.name}")
server_info.warmup_state = WarmupState.PENDING
await runner.warmup()
server_info.synced_commit = runner.commit.hexsha
Expand Down
5 changes: 2 additions & 3 deletions src/codegen/sdk/codebase/codebase_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from codegen.sdk.typescript.external.ts_declassify.ts_declassify import TSDeclassify
from codegen.shared.enums.programming_language import ProgrammingLanguage
from codegen.shared.exceptions.control_flow import StopCodemodException
from codegen.shared.logging.get_logger import get_logger
from codegen.shared.performance.stopwatch_utils import stopwatch, stopwatch_with_sentry

if TYPE_CHECKING:
Expand All @@ -51,9 +52,7 @@
from codegen.sdk.core.node_id_factory import NodeId
from codegen.sdk.core.parser import Parser

import logging

logger = logging.getLogger(__name__)
logger = get_logger(__name__)


# src/vs/platform/contextview/browser/contextMenuService.ts is ignored as there is a parsing error with tree-sitter
Expand Down
22 changes: 15 additions & 7 deletions src/codegen/shared/logging/get_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import colorlog

handler = colorlog.StreamHandler()
handler.setFormatter(
colorlog.ColoredFormatter(

def get_logger(name: str, level: int = logging.INFO) -> logging.Logger:
# Force configure the root logger with a NullHandler to prevent duplicate logs
logging.basicConfig(handlers=[logging.NullHandler()], force=True)

formatter = colorlog.ColoredFormatter(
"%(white)s%(asctime)s - %(name)s - %(log_color)s%(levelname)s%(reset)s%(white)s - %(message_log_color)s%(message)s",
log_colors={
"DEBUG": "cyan",
Expand All @@ -23,11 +26,16 @@
}
},
)
)


def get_logger(name: str, level: int = logging.INFO) -> logging.Logger:
logger = logging.getLogger(name)
if logger.hasHandlers():
for h in logger.handlers:
logger.removeHandler(h)

handler = colorlog.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
# Ensure the logger propagates to the root logger
logger.propagate = False
# Set the level on the logger itself
logger.setLevel(level)
return logger
15 changes: 15 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading