11import { Effect , Layer , ServiceMap , Stream } from "effect"
2+ import { ChildProcess , ChildProcessSpawner } from "effect/unstable/process"
23import { Bus } from "@/bus"
34import { BusEvent } from "@/bus/bus-event"
5+ import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
46import { InstanceState } from "@/effect/instance-state"
57import { makeRuntime } from "@/effect/run-service"
68import { FileWatcher } from "@/file/watcher"
79import { Log } from "@/util/log"
8- import { git } from "@/util/git"
9- import { Instance } from "./instance"
1010import z from "zod"
1111
1212export namespace Vcs {
@@ -41,36 +41,49 @@ export namespace Vcs {
4141
4242 export class Service extends ServiceMap . Service < Service , Interface > ( ) ( "@opencode/Vcs" ) { }
4343
44- export const layer : Layer . Layer < Service , never , Bus . Service > = Layer . effect (
44+ export const layer : Layer . Layer < Service , never , Bus . Service | ChildProcessSpawner . ChildProcessSpawner > = Layer . effect (
4545 Service ,
4646 Effect . gen ( function * ( ) {
4747 const bus = yield * Bus . Service
48+ const spawner = yield * ChildProcessSpawner . ChildProcessSpawner
49+
50+ const git = Effect . fnUntraced (
51+ function * ( args : string [ ] , opts : { cwd : string } ) {
52+ const handle = yield * spawner . spawn (
53+ ChildProcess . make ( "git" , args , { cwd : opts . cwd , extendEnv : true , stdin : "ignore" } ) ,
54+ )
55+ const text = yield * Stream . mkString ( Stream . decodeText ( handle . stdout ) )
56+ const code = yield * handle . exitCode
57+ return { code, text }
58+ } ,
59+ Effect . scoped ,
60+ Effect . catch ( ( ) => Effect . succeed ( { code : ChildProcessSpawner . ExitCode ( 1 ) , text : "" } ) ) ,
61+ )
62+
4863 const state = yield * InstanceState . make < State > (
4964 Effect . fn ( "Vcs.state" ) ( ( ctx ) =>
5065 Effect . gen ( function * ( ) {
5166 if ( ctx . project . vcs !== "git" ) {
5267 return { current : undefined }
5368 }
5469
55- const get = async ( ) => {
56- const result = await git ( [ "rev-parse" , "--abbrev-ref" , "HEAD" ] , {
57- cwd : ctx . worktree ,
58- } )
59- if ( result . exitCode !== 0 ) return undefined
60- const text = result . text ( ) . trim ( )
70+ const getBranch = Effect . fnUntraced ( function * ( ) {
71+ const result = yield * git ( [ "rev-parse" , "--abbrev-ref" , "HEAD" ] , { cwd : ctx . worktree } )
72+ if ( result . code !== 0 ) return undefined
73+ const text = result . text . trim ( )
6174 return text || undefined
62- }
75+ } )
6376
6477 const value = {
65- current : yield * Effect . promise ( ( ) => get ( ) ) ,
78+ current : yield * getBranch ( ) ,
6679 }
6780 log . info ( "initialized" , { branch : value . current } )
6881
6982 yield * bus . subscribe ( FileWatcher . Event . Updated ) . pipe (
7083 Stream . filter ( ( evt ) => evt . properties . file . endsWith ( "HEAD" ) ) ,
7184 Stream . runForEach ( ( ) =>
7285 Effect . gen ( function * ( ) {
73- const next = yield * Effect . promise ( ( ) => get ( ) )
86+ const next = yield * getBranch ( )
7487 if ( next !== value . current ) {
7588 log . info ( "branch changed" , { from : value . current , to : next } )
7689 value . current = next
@@ -97,7 +110,7 @@ export namespace Vcs {
97110 } ) ,
98111 )
99112
100- export const defaultLayer = layer . pipe ( Layer . provide ( Bus . layer ) )
113+ export const defaultLayer = layer . pipe ( Layer . provide ( Bus . layer ) , Layer . provide ( CrossSpawnSpawner . defaultLayer ) )
101114
102115 const { runPromise } = makeRuntime ( Service , defaultLayer )
103116
0 commit comments