repos / git-pr

a self-hosted git collaboration server
git clone https://github.com/picosh/git-pr.git

commit
9a45dd6
parent
69a42ed
author
Eric Bower
date
2024-05-20 09:37:30 -0400 EDT
web view
6 files changed,  +322, -97
M cfg.go
M cli.go
M go.mod
M go.sum
M pr.go
M web.go
M cfg.go
+14, -0
 1@@ -2,13 +2,27 @@ package git
 2 
 3 import "github.com/charmbracelet/ssh"
 4 
 5+type Repo struct {
 6+	ID        string
 7+	Desc      string
 8+	CloneAddr string
 9+}
10+
11 type GitCfg struct {
12 	DataPath string
13 	Admins   []ssh.PublicKey
14+	Repos    []Repo
15 }
16 
17 func NewGitCfg() *GitCfg {
18 	return &GitCfg{
19 		DataPath: "./ssh_data",
20+		Repos: []Repo{
21+			{
22+				ID:        "test",
23+				Desc:      "A test repo to play around with Patch Requests",
24+				CloneAddr: "git@github.com:picosh/test",
25+			},
26+		},
27 	}
28 }
M cli.go
+2, -2
 1@@ -81,8 +81,8 @@ Here's how it works:
 2 						fmt.Fprintf(
 3 							writer,
 4 							"%s\t%s\n",
 5-							utils.SanitizeRepo(repo),
 6-							filepath.Join(be.ReposDir(), repo),
 7+							utils.SanitizeRepo(repo.ID),
 8+							filepath.Join(be.ReposDir(), repo.ID),
 9 						)
10 					}
11 					writer.Flush()
M go.mod
+7, -12
 1@@ -3,19 +3,21 @@ module github.com/picosh/pico-git
 2 go 1.21.9
 3 
 4 require (
 5+	github.com/alecthomas/chroma/v2 v2.13.0
 6+	github.com/bluekeyes/go-gitdiff v0.7.2
 7 	github.com/charmbracelet/soft-serve v0.7.4
 8 	github.com/charmbracelet/ssh v0.0.0-20240301204039-e79ff702f5b3
 9 	github.com/charmbracelet/wish v1.3.2
10-	github.com/picosh/send v0.0.0-20240217194807-77b972121e63
11+	github.com/jmoiron/sqlx v1.3.5
12+	github.com/urfave/cli/v2 v2.27.2
13+	golang.org/x/crypto v0.21.0
14+	modernc.org/sqlite v1.27.0
15 )
16 
17 require (
18-	github.com/DavidGamba/go-getoptions v0.29.0 // indirect
19 	github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
20-	github.com/antoniomika/go-rsync-receiver v0.0.0-20231110145728-c94949e1ab7d // indirect
21 	github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2 // indirect
22 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
23-	github.com/bluekeyes/go-gitdiff v0.7.2 // indirect
24 	github.com/caarlos0/env/v10 v10.0.0 // indirect
25 	github.com/charmbracelet/bubbletea v0.25.0 // indirect
26 	github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20231027181609-f7ff6baf2ed0 // indirect
27@@ -27,6 +29,7 @@ require (
28 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
29 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
30 	github.com/creack/pty v1.1.21 // indirect
31+	github.com/dlclark/regexp2 v1.11.0 // indirect
32 	github.com/dustin/go-humanize v1.0.1 // indirect
33 	github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
34 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
35@@ -35,32 +38,25 @@ require (
36 	github.com/go-logfmt/logfmt v0.6.0 // indirect
37 	github.com/gobwas/glob v0.2.3 // indirect
38 	github.com/golang-jwt/jwt/v5 v5.1.0 // indirect
39-	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
40 	github.com/google/uuid v1.4.0 // indirect
41-	github.com/jmoiron/sqlx v1.3.5 // indirect
42 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
43-	github.com/kr/fs v0.1.0 // indirect
44 	github.com/lib/pq v1.10.9 // indirect
45 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
46 	github.com/mattn/go-isatty v0.0.20 // indirect
47 	github.com/mattn/go-localereader v0.0.1 // indirect
48 	github.com/mattn/go-runewidth v0.0.15 // indirect
49 	github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 // indirect
50-	github.com/mmcloughlin/md4 v0.1.2 // indirect
51 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
52 	github.com/muesli/cancelreader v0.2.2 // indirect
53 	github.com/muesli/reflow v0.3.0 // indirect
54 	github.com/muesli/termenv v0.15.2 // indirect
55-	github.com/pkg/sftp v1.13.6 // indirect
56 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
57 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
58 	github.com/rivo/uniseg v0.4.7 // indirect
59 	github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 // indirect
60 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
61 	github.com/sergi/go-diff v1.1.0 // indirect
62-	github.com/urfave/cli/v2 v2.27.2 // indirect
63 	github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
64-	golang.org/x/crypto v0.21.0 // indirect
65 	golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
66 	golang.org/x/mod v0.14.0 // indirect
67 	golang.org/x/sync v0.6.0 // indirect
68@@ -77,7 +73,6 @@ require (
69 	modernc.org/mathutil v1.6.0 // indirect
70 	modernc.org/memory v1.7.2 // indirect
71 	modernc.org/opt v0.1.3 // indirect
72-	modernc.org/sqlite v1.27.0 // indirect
73 	modernc.org/strutil v1.1.3 // indirect
74 	modernc.org/token v1.0.1 // indirect
75 )
M go.sum
+10, -38
  1@@ -1,9 +1,11 @@
  2-github.com/DavidGamba/go-getoptions v0.29.0 h1:cU8MjOyfAyPZke4hrgEuiGBJHS9PFYPAHve2fhDhdDk=
  3-github.com/DavidGamba/go-getoptions v0.29.0/go.mod h1:zE97E3PR9P3BI/HKyNYgdMlYxodcuiC6W68KIgeYT84=
  4+github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
  5+github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
  6+github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
  7+github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
  8+github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
  9+github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 10 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 11 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 12-github.com/antoniomika/go-rsync-receiver v0.0.0-20231110145728-c94949e1ab7d h1:NyzUTxebDLLdtNu1gY5hn/amdAEnKG9DOawz82LwNTY=
 13-github.com/antoniomika/go-rsync-receiver v0.0.0-20231110145728-c94949e1ab7d/go.mod h1:zmqePVIo1hp+WEKxERLLGHJBDOr8/z/T4eFqXgWIw1w=
 14 github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2 h1:3w5KT+shE3hzWhORGiu2liVjEoaCEXm9uZP47+Gw4So=
 15 github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2/go.mod h1:d4gQ7/3/S2sPq4NnKdtAgUOVr6XtLpWFtxyVV5/+76U=
 16 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
 17@@ -41,6 +43,8 @@ github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
 18 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 19 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 20 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 21+github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
 22+github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 23 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 24 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 25 github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
 26@@ -64,16 +68,14 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 27 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 28 github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
 29 github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
 30-github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 31-github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 32 github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
 33 github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 34+github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
 35+github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
 36 github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
 37 github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
 38 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 39 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 40-github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 41-github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 42 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 43 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 44 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 45@@ -100,8 +102,6 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp
 46 github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 47 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk=
 48 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
 49-github.com/mmcloughlin/md4 v0.1.2 h1:kGYl+iNbxhyz4u76ka9a+0TXP9KWt/LmnM0QhZwhcBo=
 50-github.com/mmcloughlin/md4 v0.1.2/go.mod h1:AAxFX59fddW0IguqNzWlf1lazh1+rXeIt/Bj49cqDTQ=
 51 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
 52 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
 53 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
 54@@ -110,12 +110,8 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
 55 github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
 56 github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
 57 github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
 58-github.com/picosh/send v0.0.0-20240217194807-77b972121e63 h1:VSSbAejFzj2KBThfVnMcNXQwzHmwjPUridgi29LxihU=
 59-github.com/picosh/send v0.0.0-20240217194807-77b972121e63/go.mod h1:1JCq0NVOdTDenQ0/Kd8e4rP80lu06UHJJ+6dQxhcpew=
 60 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 61 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 62-github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
 63-github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
 64 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 65 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 66 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 67@@ -147,55 +143,31 @@ github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
 68 github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
 69 github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
 70 github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
 71-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 72 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 73 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 74-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 75-golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
 76 golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
 77 golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 78 golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
 79 golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
 80-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 81 golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
 82 golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 83 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 84-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 85-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 86-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 87-golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 88-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 89-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 90 golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 91 golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
 92 golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 93 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 94 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 95-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 96-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 97-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 98-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 99 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
100 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
101 golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
102 golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
103-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
104-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
105-golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
106 golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
107 golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
108 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
109-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
110-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
111-golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
112 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
113 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
114-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
115-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
116-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
117 golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
118 golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
119-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
120 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
121 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
122 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
M pr.go
+14, -26
 1@@ -4,20 +4,18 @@ import (
 2 	"bytes"
 3 	"fmt"
 4 	"io"
 5-	"os"
 6-	"path/filepath"
 7 	"time"
 8 
 9 	"github.com/bluekeyes/go-gitdiff/gitdiff"
10-	"github.com/charmbracelet/soft-serve/pkg/git"
11 )
12 
13 type GitPatchRequest interface {
14-	GetRepos() ([]string, error)
15+	GetRepos() ([]Repo, error)
16 	SubmitPatchRequest(pubkey string, repoID string, patches io.Reader) (*PatchRequest, error)
17 	SubmitPatch(pubkey string, prID int64, review bool, patch io.Reader) (*Patch, error)
18 	GetPatchRequestByID(prID int64) (*PatchRequest, error)
19 	GetPatchRequests() ([]*PatchRequest, error)
20+	GetPatchRequestsByRepoID(repoID string) ([]*PatchRequest, error)
21 	GetPatchesByPrID(prID int64) ([]*Patch, error)
22 	UpdatePatchRequest(prID int64, status string) error
23 }
24@@ -29,18 +27,8 @@ type PrCmd struct {
25 var _ GitPatchRequest = PrCmd{}
26 var _ GitPatchRequest = (*PrCmd)(nil)
27 
28-func (pr PrCmd) GetRepos() ([]string, error) {
29-	repos := []string{}
30-	entries, err := os.ReadDir(pr.Backend.ReposDir())
31-	if err != nil {
32-		return repos, err
33-	}
34-	for _, entry := range entries {
35-		if entry.IsDir() {
36-			repos = append(repos, entry.Name())
37-		}
38-	}
39-	return repos, nil
40+func (pr PrCmd) GetRepos() ([]Repo, error) {
41+	return pr.Backend.Cfg.Repos, nil
42 }
43 
44 func (pr PrCmd) GetPatchesByPrID(prID int64) ([]*Patch, error) {
45@@ -68,6 +56,16 @@ func (cmd PrCmd) GetPatchRequests() ([]*PatchRequest, error) {
46 	return prs, err
47 }
48 
49+func (cmd PrCmd) GetPatchRequestsByRepoID(repoID string) ([]*PatchRequest, error) {
50+	prs := []*PatchRequest{}
51+	err := cmd.Backend.DB.Select(
52+		&prs,
53+		"SELECT * FROM patch_requests WHERE repo_id=?",
54+		repoID,
55+	)
56+	return prs, err
57+}
58+
59 func (cmd PrCmd) GetPatchRequestByID(prID int64) (*PatchRequest, error) {
60 	pr := PatchRequest{}
61 	err := cmd.Backend.DB.Get(
62@@ -132,16 +130,6 @@ func (cmd PrCmd) SubmitPatch(pubkey string, prID int64, review bool, patch io.Re
63 }
64 
65 func (cmd PrCmd) SubmitPatchRequest(pubkey string, repoID string, patches io.Reader) (*PatchRequest, error) {
66-	err := git.EnsureWithin(cmd.Backend.ReposDir(), repoID)
67-	if err != nil {
68-		return nil, err
69-	}
70-	loc := filepath.Join(cmd.Backend.ReposDir(), repoID)
71-	_, err = os.Stat(loc)
72-	if os.IsNotExist(err) {
73-		return nil, fmt.Errorf("repo does not exist: %s", loc)
74-	}
75-
76 	// need to read io.Reader from session twice
77 	var buf bytes.Buffer
78 	tee := io.TeeReader(patches, &buf)
M web.go
+275, -19
  1@@ -1,25 +1,57 @@
  2 package git
  3 
  4 import (
  5+	"bytes"
  6 	"context"
  7 	"fmt"
  8+	"html/template"
  9 	"log/slog"
 10 	"net/http"
 11 	"os"
 12 	"path/filepath"
 13+	"strconv"
 14+	"time"
 15+
 16+	"github.com/alecthomas/chroma/v2"
 17+	formatterHtml "github.com/alecthomas/chroma/v2/formatters/html"
 18+	"github.com/alecthomas/chroma/v2/lexers"
 19+	"github.com/alecthomas/chroma/v2/styles"
 20 )
 21 
 22-type ctxPr struct{}
 23+type WebCtx struct {
 24+	Pr        *PrCmd
 25+	Backend   *Backend
 26+	Formatter *formatterHtml.Formatter
 27+	Logger    *slog.Logger
 28+	Theme     *chroma.Style
 29+}
 30+
 31+type ctxWeb struct{}
 32 
 33-func getPrCtx(r *http.Request) (*PrCmd, error) {
 34-	pr, ok := r.Context().Value(ctxPr{}).(*PrCmd)
 35-	if pr == nil || !ok {
 36-		return pr, fmt.Errorf("pr not set on `r.Context()` for connection")
 37+func getWebCtx(r *http.Request) (*WebCtx, error) {
 38+	data, ok := r.Context().Value(ctxWeb{}).(*WebCtx)
 39+	if data == nil || !ok {
 40+		return data, fmt.Errorf("webCtx not set on `r.Context()` for connection")
 41 	}
 42-	return pr, nil
 43+	return data, nil
 44 }
 45-func setPrCtx(ctx context.Context, pr *PrCmd) context.Context {
 46-	return context.WithValue(ctx, ctxPr{}, pr)
 47+func setWebCtx(ctx context.Context, web *WebCtx) context.Context {
 48+	return context.WithValue(ctx, ctxWeb{}, web)
 49+}
 50+
 51+// converts contents of files in git tree to pretty formatted code
 52+func parseText(formatter *formatterHtml.Formatter, theme *chroma.Style, text string) (string, error) {
 53+	lexer := lexers.Get("diff")
 54+	iterator, err := lexer.Tokenise(nil, text)
 55+	if err != nil {
 56+		return text, err
 57+	}
 58+	var buf bytes.Buffer
 59+	err = formatter.Format(&buf, theme, iterator)
 60+	if err != nil {
 61+		return text, err
 62+	}
 63+	return buf.String(), nil
 64 }
 65 
 66 func ctxMdw(ctx context.Context, handler http.HandlerFunc) http.HandlerFunc {
 67@@ -28,26 +60,232 @@ func ctxMdw(ctx context.Context, handler http.HandlerFunc) http.HandlerFunc {
 68 	}
 69 }
 70 
 71-func prHandler(w http.ResponseWriter, r *http.Request) {
 72-	pr, err := getPrCtx(r)
 73+type TemplateData struct {
 74+	Title string
 75+	Body  template.HTML
 76+}
 77+
 78+func getTemplate() *template.Template {
 79+	str := `<!doctype html>
 80+<html lang="en">
 81+	<head>
 82+		<title>{{.Title}}</title>
 83+		<link rel="stylesheet" href="https://pico.sh/smol.css" />
 84+		<link rel="stylesheet" href="/syntax.css" />
 85+	</head>
 86+	<body class="container">
 87+		{{.Body}}
 88+	</body>
 89+</html>`
 90+	tmpl := template.Must(template.New("main").Parse(str))
 91+	return tmpl
 92+}
 93+
 94+func repoListHandler(w http.ResponseWriter, r *http.Request) {
 95+	web, err := getWebCtx(r)
 96 	if err != nil {
 97 		fmt.Println(err)
 98 		w.WriteHeader(http.StatusInternalServerError)
 99 		return
100 	}
101 
102-	str := "Patch Requests\n"
103-	prs, err := pr.GetPatchRequests()
104+	str := `<h1 class="text-2xl">Repos</h1>`
105+	repos, err := web.Pr.GetRepos()
106 	if err != nil {
107-		pr.Backend.Logger.Error("cannot get prs", "err", err)
108+		web.Pr.Backend.Logger.Error("cannot get repos", "err", err)
109+		w.WriteHeader(http.StatusInternalServerError)
110+		return
111+	}
112+
113+	str += "<ul>"
114+	for _, repo := range repos {
115+		str += fmt.Sprintf(
116+			`<li><a href="%s">%s</a></li>`,
117+			template.URL("/repos/"+repo.ID),
118+			repo.ID,
119+		)
120+	}
121+	str += "</ul>"
122+
123+	w.Header().Set("content-type", "text/html")
124+	tmpl := getTemplate()
125+	err = tmpl.Execute(w, TemplateData{
126+		Title: "Repos",
127+		Body:  template.HTML(str),
128+	})
129+	if err != nil {
130+		fmt.Println(err)
131+	}
132+}
133+
134+func prListHandler(w http.ResponseWriter, r *http.Request) {
135+	repoID := r.PathValue("id")
136+
137+	web, err := getWebCtx(r)
138+	if err != nil {
139+		fmt.Println(err)
140+		w.WriteHeader(http.StatusInternalServerError)
141+		return
142+	}
143+
144+	str := `<h1 class="text-2xl">Patch Requests</h1>`
145+	prs, err := web.Pr.GetPatchRequestsByRepoID(repoID)
146+	if err != nil {
147+		web.Pr.Backend.Logger.Error("cannot get prs", "err", err)
148 		w.WriteHeader(http.StatusInternalServerError)
149 		return
150 	}
151 
152 	for _, curpr := range prs {
153-		str += fmt.Sprintf("%d\t%s\t%s\t%s\n", curpr.ID, curpr.RepoID, curpr.Name, curpr.Pubkey)
154+		row := `
155+<div class="group-h">
156+	<div>%d</div>
157+	<div><a href="%s">%s</a></div>
158+	<div>%s</div>
159+</div>`
160+		str += fmt.Sprintf(
161+			row,
162+			curpr.ID,
163+			template.URL(fmt.Sprintf("/prs/%d", curpr.ID)),
164+			curpr.Name,
165+			curpr.Pubkey,
166+		)
167+	}
168+
169+	w.Header().Set("content-type", "text/html")
170+	tmpl := getTemplate()
171+	err = tmpl.Execute(w, TemplateData{
172+		Title: "Patch Requests",
173+		Body:  template.HTML(str),
174+	})
175+	if err != nil {
176+		fmt.Println(err)
177+	}
178+}
179+
180+func header(pr *PatchRequest, page string) string {
181+	str := fmt.Sprintf(`<h1 class="text-2xl">%s</h1>`, pr.Name)
182+	str += fmt.Sprintf("<div>[%s] %s %s</div>", pr.Status, pr.CreatedAt.Format(time.DateTime), pr.Pubkey)
183+	if page == "pr" {
184+		str += fmt.Sprintf(`<div><strong>summary</strong> &middot; <a href="/prs/%d/patches">patches</a></div>`, pr.ID)
185+	} else {
186+		str += fmt.Sprintf(`<div><a href="/prs/%d">summary</a> &middot; <strong>patches</strong></div>`, pr.ID)
187+	}
188+	return str
189+}
190+
191+func prHandler(w http.ResponseWriter, r *http.Request) {
192+	id := r.PathValue("id")
193+	prID, err := strconv.Atoi(id)
194+	if err != nil {
195+		fmt.Println(err)
196+		w.WriteHeader(http.StatusUnprocessableEntity)
197+		return
198+	}
199+
200+	web, err := getWebCtx(r)
201+	if err != nil {
202+		fmt.Println(err)
203+		w.WriteHeader(http.StatusInternalServerError)
204+		return
205+	}
206+
207+	pr, err := web.Pr.GetPatchRequestByID(int64(prID))
208+	if err != nil {
209+		web.Pr.Backend.Logger.Error("cannot get prs", "err", err)
210+		w.WriteHeader(http.StatusInternalServerError)
211+		return
212+	}
213+
214+	str := header(pr, "pr")
215+	str += fmt.Sprintf("<p>%s</p>", pr.Text)
216+
217+	w.Header().Set("content-type", "text/html")
218+	tmpl := getTemplate()
219+	err = tmpl.Execute(w, TemplateData{
220+		Title: fmt.Sprintf("%s (%s)", pr.Name, pr.Status),
221+		Body:  template.HTML(str),
222+	})
223+	if err != nil {
224+		fmt.Println(err)
225+	}
226+}
227+
228+func prPatchesHandler(w http.ResponseWriter, r *http.Request) {
229+	id := r.PathValue("id")
230+	prID, err := strconv.Atoi(id)
231+	if err != nil {
232+		fmt.Println(err)
233+		w.WriteHeader(http.StatusUnprocessableEntity)
234+		return
235+	}
236+
237+	web, err := getWebCtx(r)
238+	if err != nil {
239+		fmt.Println(err)
240+		w.WriteHeader(http.StatusInternalServerError)
241+		return
242+	}
243+
244+	pr, err := web.Pr.GetPatchRequestByID(int64(prID))
245+	if err != nil {
246+		web.Pr.Backend.Logger.Error("cannot get prs", "err", err)
247+		w.WriteHeader(http.StatusInternalServerError)
248+		return
249+	}
250+
251+	str := header(pr, "patches")
252+
253+	patches, err := web.Pr.GetPatchesByPrID(int64(prID))
254+	if err != nil {
255+		web.Pr.Backend.Logger.Error("cannot get patches", "err", err)
256+		w.WriteHeader(http.StatusInternalServerError)
257+		return
258+	}
259+
260+	for _, patch := range patches {
261+		rev := ""
262+		if patch.Review {
263+			rev = "[review]"
264+		}
265+		diffStr, err := parseText(web.Formatter, web.Theme, patch.RawText)
266+		if err != nil {
267+			w.WriteHeader(http.StatusUnprocessableEntity)
268+			return
269+		}
270+
271+		row := `
272+<h2 class="text-xl">%s %s</h2>
273+<div>%s</div>`
274+		str += fmt.Sprintf(
275+			row,
276+			patch.Title, rev,
277+			diffStr,
278+		)
279+	}
280+
281+	w.Header().Set("content-type", "text/html")
282+	tmpl := getTemplate()
283+	err = tmpl.Execute(w, TemplateData{
284+		Title: fmt.Sprintf("patches - %s", pr.Name),
285+		Body:  template.HTML(str),
286+	})
287+	if err != nil {
288+		fmt.Println(err)
289+	}
290+}
291+
292+func chromaStyleHandler(w http.ResponseWriter, r *http.Request) {
293+	web, err := getWebCtx(r)
294+	if err != nil {
295+		w.WriteHeader(http.StatusUnprocessableEntity)
296+		return
297+	}
298+	w.Header().Add("content-type", "text/css")
299+	err = web.Formatter.WriteCSS(w, web.Theme)
300+	if err != nil {
301+		fmt.Println(err)
302 	}
303-	fmt.Fprintf(w, str)
304 }
305 
306 func StartWebServer() {
307@@ -72,18 +310,36 @@ func StartWebServer() {
308 	be := &Backend{
309 		DB:     dbh,
310 		Logger: logger,
311+		Cfg:    cfg,
312 	}
313 	prCmd := &PrCmd{
314 		Backend: be,
315 	}
316+	formatter := formatterHtml.New(
317+		formatterHtml.WithLineNumbers(true),
318+		formatterHtml.WithLinkableLineNumbers(true, ""),
319+		formatterHtml.WithClasses(true),
320+	)
321+	web := &WebCtx{
322+		Pr:        prCmd,
323+		Backend:   be,
324+		Logger:    logger,
325+		Formatter: formatter,
326+		Theme:     styles.Get("dracula"),
327+	}
328 	ctx := context.Background()
329-	ctx = setPrCtx(ctx, prCmd)
330+	ctx = setWebCtx(ctx, web)
331 
332-	mux := http.NewServeMux()
333-	mux.HandleFunc("/", ctxMdw(ctx, prHandler))
334+	// ensure legacy router is disabled
335+	// GODEBUG=httpmuxgo121=0
336+	http.HandleFunc("GET /prs/{id}/patches", ctxMdw(ctx, prPatchesHandler))
337+	http.HandleFunc("GET /prs/{id}", ctxMdw(ctx, prHandler))
338+	http.HandleFunc("GET /repos/{id}", ctxMdw(ctx, prListHandler))
339+	http.HandleFunc("GET /", ctxMdw(ctx, repoListHandler))
340+	http.HandleFunc("GET /syntax.css", ctxMdw(ctx, chromaStyleHandler))
341 
342 	logger.Info("starting web server", "addr", addr)
343-	err = http.ListenAndServe(addr, mux)
344+	err = http.ListenAndServe(addr, nil)
345 	if err != nil {
346 		logger.Error("listen", "err", err)
347 	}