From e1ab58efb048d1ca7262a637f2c445ac885222ea Mon Sep 17 00:00:00 2001
From: simon987 <me@simon987.net>
Date: Sun, 5 Jul 2020 10:37:17 -0400
Subject: [PATCH] Initial version

---
 .gitignore   |   4 +-
 go.mod       |   3 +-
 go.sum       |  78 ++++++++++++++++---
 main.go      | 193 +++++++++++++++++++++++++++++++++++++++++++---
 workspace.go | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 466 insertions(+), 22 deletions(-)
 create mode 100644 workspace.go

diff --git a/.gitignore b/.gitignore
index d0a1472..49a8ed7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
 .idea
 *.iml
-task_tracker_drone_go
\ No newline at end of file
+task_tracker_drone_go
+workdir
+*.json
diff --git a/go.mod b/go.mod
index 051b7c5..878b7b3 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,8 @@ module task_tracker_drone_go
 go 1.14
 
 require (
-	github.com/simon987/task_tracker v0.0.0-20200626015847-ed8b153d3b78
+	github.com/go-git/go-git/v5 v5.1.0
+	github.com/simon987/task_tracker v0.0.0-20200705141550-62f9ca4d4b5d
 	github.com/urfave/cli/v2 v2.2.0
 	go.uber.org/zap v1.15.0
 )
diff --git a/go.sum b/go.sum
index ca70d74..fa55338 100644
--- a/go.sum
+++ b/go.sum
@@ -2,9 +2,15 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
@@ -19,6 +25,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -26,11 +33,25 @@ github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4=
 github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 github.com/fasthttp/session v1.1.4 h1:K2VkYIjHvXKattN5l2rTocK+kkyXP5Uyv/SvIByygOQ=
 github.com/fasthttp/session v1.1.4/go.mod h1:U7iWjLnnK6RYBcLXRQzkxWOcoFXJfyXIwB2RFkRRJeo=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
+github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
+github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
+github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
+github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
+github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -46,9 +67,12 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -56,10 +80,18 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
+github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kataras/go-sessions v3.0.0+incompatible/go.mod h1:wBiyJj8R+1sfu/n2EO2HERC/0XsIsJiwx3PlYz/iy0A=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
@@ -68,6 +100,7 @@ github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/klauspost/cpuid v1.2.2/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -75,6 +108,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
 github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -82,9 +117,13 @@ github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzR
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
@@ -116,24 +155,28 @@ github.com/savsgio/dictpool v0.0.0-20191028211042-a886cee3358a h1:HHo8bk/5tOTL+U
 github.com/savsgio/dictpool v0.0.0-20191028211042-a886cee3358a/go.mod h1:hnGRFeigcU3gTEMTWW8OjfoSYztn2GPcriOY9iIzCrA=
 github.com/savsgio/gotils v0.0.0-20190925070755-524bc4f47500 h1:9Pi10H7E8E79/x2HSe1FmMGd7BJ1WAqDKzwjpv+ojFg=
 github.com/savsgio/gotils v0.0.0-20190925070755-524bc4f47500/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
+github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/simon987/task_tracker v0.0.0-20200625222400-24c6701b01c8 h1:EQOfgYP2T8VDJHDxkBSTuiIAEWu0uw6VW9TUFUdh6Es=
-github.com/simon987/task_tracker v0.0.0-20200625222400-24c6701b01c8/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
-github.com/simon987/task_tracker v0.0.0-20200626014303-f80af654f199 h1:S9IcI+PgXeTa+naiUzNFDZllHd7jrckbKjkzQgMfeJc=
-github.com/simon987/task_tracker v0.0.0-20200626014303-f80af654f199/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
-github.com/simon987/task_tracker v0.0.0-20200626015206-7772195f8527 h1:5XMQzagUM9u2Lzb04z1JjvkuxB8whJ7VgLf4lU4reUA=
-github.com/simon987/task_tracker v0.0.0-20200626015206-7772195f8527/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
-github.com/simon987/task_tracker v0.0.0-20200626015457-68cc3b7c81f5 h1:TxRFUs1c+c30h1bU5toptt0bmlG/y5uB2g3yi/M9MhE=
-github.com/simon987/task_tracker v0.0.0-20200626015457-68cc3b7c81f5/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
-github.com/simon987/task_tracker v0.0.0-20200626015657-6ef34587baf8 h1:EpK3oJzcbJuCkglfE2okG/RXvYwbz8N0vrCJJ3dr7Bo=
-github.com/simon987/task_tracker v0.0.0-20200626015657-6ef34587baf8/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
 github.com/simon987/task_tracker v0.0.0-20200626015847-ed8b153d3b78 h1:K/tGG89EXvGv4eD5uCF3S7l0Vkjnz2rr4NONvIYhjDw=
 github.com/simon987/task_tracker v0.0.0-20200626015847-ed8b153d3b78/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
+github.com/simon987/task_tracker v0.0.0-20200628154235-961d692c6117 h1:yx6xqYDtZkdKg1/GpBypXWdxzFKEP+ZpnkHlU1ZPkTQ=
+github.com/simon987/task_tracker v0.0.0-20200628154235-961d692c6117/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
+github.com/simon987/task_tracker v0.0.0-20200628155834-26a3552b7b49 h1:x3RQLNRKnSP7JiB6IEm4wgxz1ZtQlTpWIaO84nEoB/g=
+github.com/simon987/task_tracker v0.0.0-20200628155834-26a3552b7b49/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
+github.com/simon987/task_tracker v0.0.0-20200629213836-9cb8891024ed h1:DKMM/xTp1Oz6Rp22+cdyQuycppgudIf+4jXOYRH3qPY=
+github.com/simon987/task_tracker v0.0.0-20200629213836-9cb8891024ed/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
+github.com/simon987/task_tracker v0.0.0-20200705140353-093be5d1f09c h1:8bgxJoBKL5FUB9Fvr2vH0iSpS6kz81v1qp5kDFOdgq0=
+github.com/simon987/task_tracker v0.0.0-20200705140353-093be5d1f09c/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
+github.com/simon987/task_tracker v0.0.0-20200705141550-62f9ca4d4b5d h1:ycaWe4RALbGGZlVJ5AObObMcAVKcXLXCM8DKZHCxRtk=
+github.com/simon987/task_tracker v0.0.0-20200705141550-62f9ca4d4b5d/go.mod h1:1RTTfh5jpryLPkS/fnJTbHter2IwuTU34TJeoZK01Kg=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -171,6 +214,8 @@ github.com/valyala/fasthttp v1.7.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBn
 github.com/valyala/fasthttp v1.7.1 h1:UHtt5/7O70RSUZTR/hSu0PNWMAfWx5AtsPp9Jk+g17M=
 github.com/valyala/fasthttp v1.7.1/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
+github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -186,8 +231,11 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
 go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
@@ -201,6 +249,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -211,10 +261,13 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7 h1:/W9OPMnnpmFXHYkcp2rQsbFUbRlRzfECQjmAFiOyHE8=
 golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -239,11 +292,16 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
diff --git a/main.go b/main.go
index 4c5c32e..8480d1a 100644
--- a/main.go
+++ b/main.go
@@ -1,32 +1,191 @@
 package main
 
 import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/simon987/task_tracker/api"
 	tt "github.com/simon987/task_tracker/client"
+	"github.com/simon987/task_tracker/storage"
 	"github.com/urfave/cli/v2"
 	"go.uber.org/zap"
+	"io/ioutil"
 	"log"
 	"os"
+	"time"
 )
 
+type DroneContext struct {
+	WorkdirPath string
+	client      *tt.TaskTrackerClient
+	Projects    []storage.Project
+	Secrets     map[int64]string
+}
+
+func (ctx *DroneContext) FetchProjects() error {
+
+	logger.Debug("fetching project list")
+	projects, err := ctx.client.GetProjectList()
+	if err != nil {
+		return err
+	}
+
+	for _, p := range projects {
+		logger.Debug("got project", zap.String("name", p.Name), zap.String("version", p.Version))
+
+		secret, err := ctx.client.GetProjectSecret(int(p.Id))
+		if err != nil {
+			return err
+		}
+
+		ctx.Secrets[p.Id] = secret
+	}
+
+	ctx.Projects = projects
+	return nil
+}
+
 var logger *zap.Logger
 
+func (ctx *DroneContext) taskRunner(name string) {
+
+	logger.Info("Starting task runner goroutine", zap.String("name", name))
+
+	for {
+		for _, p := range ctx.Projects {
+			task, err := ctx.client.FetchTask(int(p.Id))
+
+			if err != nil {
+				logger.Error("error fetching task", zap.Error(err))
+				continue
+			}
+
+			if task.Ok == false {
+				if task.Message != "No task available" {
+					logger.Error("couldn't fetch task", zap.String("message", task.Message))
+					continue
+				}
+
+				time.Sleep(time.Second * 1)
+				continue
+			}
+
+			w := Workspace{
+				Name:    name,
+				Project: &p,
+				ctx:     ctx,
+			}
+			err = w.Execute(&task.Content.Task)
+			if err != nil {
+				logger.Error("error executing task", zap.Error(err))
+				continue
+			}
+		}
+	}
+}
+
+func (ctx *DroneContext) updateProjects() {
+
+	requestedAccess := make(map[int64]bool)
+
+	for {
+		err := ctx.FetchProjects()
+		if err != nil {
+			logger.Error("error while fetching projects", zap.Error(err))
+		}
+
+		for _, p := range ctx.Projects {
+
+			ok, _ := requestedAccess[p.Id]
+			if !ok {
+				logger.Info("requesting access to project", zap.String("name", p.Name))
+				_, err := ctx.client.RequestAccess(api.CreateWorkerAccessRequest{
+					Assign:  true,
+					Submit:  false,
+					Project: p.Id,
+				})
+
+				if err != nil {
+					logger.Error("error requesting access", zap.Error(err))
+				} else {
+					requestedAccess[p.Id] = true
+				}
+			}
+		}
+
+		time.Sleep(time.Second * 60)
+	}
+}
+
+func makeWorker(client *tt.TaskTrackerClient, alias string) (*tt.Worker, error) {
+	var worker *tt.Worker
+
+	path := fmt.Sprintf("worker_%s.json", alias)
+
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		worker, err = client.MakeWorker(alias)
+		if err != nil {
+			logger.Error("Could not create client", zap.Error(err))
+			return nil, err
+		}
+		saveWorker(worker)
+		return worker, nil
+	}
+
+	fp, _ := os.OpenFile(path, os.O_RDONLY, 0600)
+	workerJsonData, _ := ioutil.ReadAll(fp)
+	err := json.Unmarshal(workerJsonData, &worker)
+	if err != nil {
+		return nil, err
+	}
+
+	logger.Info("loaded worker from file", zap.String("alias", alias))
+
+	return worker, nil
+}
+
+func saveWorker(w *tt.Worker) {
+	workerJsonData, _ := json.Marshal(&w)
+
+	path := fmt.Sprintf("worker_%s.json", w.Alias)
+
+	fp, _ := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
+	_, _ = fp.Write(workerJsonData)
+}
+
 func drone(c *cli.Context) error {
 
+	err := os.MkdirAll(c.String("workdir"), 0755)
+	if err != nil {
+		return err
+	}
+
 	client := tt.New(c.String("api-url"))
 
-	worker, err := client.MakeWorker(c.String("alias"))
+	worker, err := makeWorker(client, c.String("alias"))
 	if err != nil {
-		logger.Error("Could not create client", zap.Error(err))
+		return err
 	}
 
+	ctx := &DroneContext{WorkdirPath: "workdir", Secrets: make(map[int64]string)}
 	client.SetWorker(worker)
+	ctx.client = client
 
-	projects, err := client.GetProjectList()
-	for _, p := range projects {
-		logger.Info(p.Name)
+	err = ctx.FetchProjects()
+	if err != nil {
+		logger.Error("error while fetching projects", zap.Error(err))
+		return errors.New("could not bootstrap task runner")
 	}
 
-	return nil
+	go ctx.updateProjects()
+	for i := 0; i < c.Int("concurrency"); i++ {
+		runnerName := fmt.Sprintf("%s-%d", c.String("alias"), i)
+		go ctx.taskRunner(runnerName)
+	}
+
+	for {
+		time.Sleep(time.Second)
+	}
 }
 
 func main() {
@@ -42,14 +201,28 @@ func main() {
 		},
 		Flags: []cli.Flag{
 			&cli.StringFlag{
-				Name: "api-url",
+				Name:     "api-url",
 				Required: true,
-				Usage: "task_tracker api url",
+				Usage:    "task_tracker api url",
+				EnvVars:  []string{"TT_API_URL"},
 			},
 			&cli.StringFlag{
-				Name: "alias",
+				Name:     "alias",
 				Required: true,
-				Usage: "task_tracker worker alias",
+				Usage:    "task_tracker worker alias",
+				EnvVars:  []string{"TT_ALIAS"},
+			},
+			&cli.StringFlag{
+				Name:    "workdir",
+				Value:   "workdir",
+				Usage:   "Work directory name",
+				EnvVars: []string{"TT_WORKDIR"},
+			},
+			&cli.IntFlag{
+				Name:    "concurrency",
+				Value:   20,
+				Usage:   "Number of tasks to execute at the same time",
+				EnvVars: []string{"TT_CONCURRENCY"},
 			},
 		},
 	}
diff --git a/workspace.go b/workspace.go
new file mode 100644
index 0000000..d04854f
--- /dev/null
+++ b/workspace.go
@@ -0,0 +1,210 @@
+package main
+
+import (
+	"bytes"
+	"errors"
+	"github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/simon987/task_tracker/api"
+	"github.com/simon987/task_tracker/storage"
+	"go.uber.org/zap"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"time"
+)
+
+type Workspace struct {
+	Name    string
+	Project *storage.Project
+	ctx     *DroneContext
+	repo    *git.Repository
+}
+
+func (w *Workspace) Cleanup() error {
+	logger.Info("Cleaning up workspace", zap.String("name", w.Name))
+
+	clonePath := filepath.Join(w.ctx.WorkdirPath, w.Project.Name, w.Name)
+	return os.RemoveAll(clonePath)
+}
+
+func (w *Workspace) clonePath() string {
+	ret, _ := filepath.Abs(filepath.Join(w.ctx.WorkdirPath, w.Name, w.Project.Name+"_"+w.Project.Version))
+	return ret
+}
+
+func (w *Workspace) Deploy() error {
+
+	if w.repo != nil {
+		return errors.New("project is already deployed")
+	}
+
+	clonePath := w.clonePath()
+	if _, err := os.Stat(clonePath); err == nil {
+		w.repo, err = git.PlainOpen(clonePath)
+		if err == nil {
+			return nil
+		}
+		err = os.RemoveAll(clonePath)
+		if err != nil {
+			return err
+		}
+	}
+
+	logger.Info("Deploying project", zap.String("name", w.Project.Name))
+
+	var err error
+	w.repo, err = git.PlainClone(clonePath, false, &git.CloneOptions{
+		URL:               w.Project.CloneUrl,
+		SingleBranch:      true,
+		RecurseSubmodules: 10,
+		Progress:          os.Stdout,
+		Depth:             1,
+	})
+	if err != nil {
+		return err
+	}
+
+	return w.checkout(w.Project.Version)
+}
+
+func (w *Workspace) checkout(hash string) error {
+	logger.Info("Checking out commit hash", zap.String("version", w.Project.Version))
+	worktree, err := w.repo.Worktree()
+	if err != nil {
+		return err
+	}
+
+	err = worktree.Checkout(&git.CheckoutOptions{
+		Hash: plumbing.NewHash(hash),
+	})
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (w *Workspace) reset() error {
+	logger.Debug("Reset workspace")
+	worktree, err := w.repo.Worktree()
+	if err != nil {
+		return err
+	}
+
+	err = worktree.Reset(&git.ResetOptions{
+		Mode: git.HardReset,
+	})
+
+	if err != nil {
+		logger.Error("error during reset", zap.Error(err))
+	}
+	return err
+}
+
+func (w *Workspace) Update() error {
+
+	if w.repo == nil {
+		err := w.Deploy()
+		if err != nil {
+			return err
+		}
+	}
+
+	ref, err := w.repo.Head()
+	if err != nil {
+		return err
+	}
+
+	if ref.Hash().String() != w.Project.Version {
+		err = w.repo.Fetch(&git.FetchOptions{})
+		if err != nil {
+			return err
+		}
+		return w.checkout(w.Project.Version)
+	}
+
+	return nil
+}
+
+func (w *Workspace) Execute(task *storage.Task) error {
+
+	if task.Project.CloneUrl == "" {
+		logger.Warn("project does not have clone URL, skipping task")
+		_, err := w.ctx.client.ReleaseTask(api.ReleaseTaskRequest{
+			TaskId: task.Id,
+			Result: storage.TR_SKIP,
+		})
+		if err != nil {
+			return nil
+		}
+	}
+
+	err := w.Update()
+	if err != nil {
+		return err
+	}
+
+	defer func() { _ = w.reset() }()
+
+	clonePath := w.clonePath()
+	runFilePath := filepath.Join(clonePath, "run")
+
+	if _, err := os.Stat(runFilePath); os.IsNotExist(err) {
+		time.Sleep(time.Second * 5)
+		return errors.New("/run does not exist")
+	}
+
+	var stderr bytes.Buffer
+	var stdout bytes.Buffer
+
+	cmd := exec.Command(runFilePath)
+	cmd.Dir = clonePath
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	cmd.Env = append(cmd.Env, "PROJECT_NAME="+task.Project.Name)
+	cmd.Env = append(cmd.Env, "PROJECT_SECRET="+w.ctx.Secrets[task.Project.Id])
+	cmd.Env = append(cmd.Env, "TASK_RECIPE="+task.Recipe)
+
+	err = cmd.Run()
+
+	if len(stderr.Bytes()) > 0 {
+		err := w.ctx.client.Log(storage.ERROR, stderr.String())
+		if err != nil {
+			return err
+		}
+	}
+
+	if len(stdout.Bytes()) > 0 {
+		err := w.ctx.client.Log(storage.INFO, stdout.String())
+		if err != nil {
+			return err
+		}
+	}
+
+	var result storage.TaskResult
+	if err != nil || cmd.ProcessState.ExitCode() != 0 {
+		logger.Error(
+			"Failed to execute task",
+			zap.Int("exitCode", cmd.ProcessState.ExitCode()),
+			zap.Error(err),
+		)
+		result = storage.TR_FAIL
+	} else {
+		result = storage.TR_OK
+	}
+
+	resp, err := w.ctx.client.ReleaseTask(api.ReleaseTaskRequest{
+		TaskId:       task.Id,
+		Result:       result,
+		Verification: 0,
+	})
+	if err != nil {
+		return err
+	}
+
+	if !resp.Ok {
+		return errors.New(resp.Message)
+	}
+
+	return nil
+}