From ecf1215ac92d96648f7fd5cc3990e81c61da43f8 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 1 Dec 2020 16:10:44 +0000 Subject: [PATCH 1/9] Add RepoStorage interface (for draft PR) --- go.mod | 2 ++ go.sum | 20 ++------------------ repository/repo.go | 13 +++++++++++++ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index a6496b4526162d765a131e2d0943b2f219a689f7..000f84f06bd1cb32e4422e815d64cfba4636b37a 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,8 @@ require ( github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect github.com/fatih/color v1.10.0 + github.com/go-git/go-billy v4.2.0+incompatible + github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-git/v5 v5.2.0 github.com/golang/protobuf v1.4.3 // indirect github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index d22c5f3ae0b7110879b8697ed7b477d7b105d2a8..fe78e7ea766dfd8299884736b805aa37e10670a1 100644 --- a/go.sum +++ b/go.sum @@ -50,15 +50,10 @@ github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhIN github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -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 h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -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/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 h1:c4mLfegoDw6OhSJXTd2jUEQgZUQuJWtocudb97Qn9EM= github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= @@ -66,14 +61,12 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -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/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986 h1:QvIfX96O11qjX1Zr3hKkG0dI12JBRBGABWffyZ1GI60= github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA= github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0= github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= @@ -178,12 +171,10 @@ github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+ne github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -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/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= @@ -194,9 +185,10 @@ github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 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 v4.2.0+incompatible h1:Z6QtVXd5tjxUtcODLugkJg4WaZnGg13CD8qB9pr+7q0= +github.com/go-git/go-billy v4.2.0+incompatible/go.mod h1:hedUGslB3n31bx5SW9KMjV/t0CUKnrapjVG9fT7xKX4= 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.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI= github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= @@ -237,7 +229,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= @@ -286,7 +277,6 @@ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= @@ -317,7 +307,6 @@ github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGzny github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 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= @@ -333,7 +322,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 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/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= 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= @@ -343,7 +331,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -382,7 +369,6 @@ github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= -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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -727,7 +713,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -785,7 +770,6 @@ 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/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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= diff --git a/repository/repo.go b/repository/repo.go index 4b45a1c5c77c47b57dce2a3c7c2d8417a6f0fea7..93069d61537f97fc1fe2704209630c5b3610f2eb 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/MichaelMure/git-bug/util/lamport" + "github.com/go-git/go-billy/v5" ) var ( @@ -20,6 +21,7 @@ type Repo interface { RepoKeyring RepoCommon RepoData + RepoStorage } // ClockedRepo is a Repo that also has Lamport clocks @@ -64,6 +66,11 @@ type RepoCommon interface { GetRemotes() (map[string]string, error) } +type RepoStorage interface { + // Storage returns a billy.Filesystem giving access to $RepoPath/.git/git-bug + Storage() billy.Filesystem +} + // RepoData give access to the git data storage type RepoData interface { // FetchRefs fetch git refs from a remote @@ -145,4 +152,10 @@ type TestedRepo interface { type repoTest interface { // AddRemote add a new remote to the repository AddRemote(name string, url string) error + + // GetLocalRemote return the URL to use to add this repo as a local remote + GetLocalRemote() string + + // EraseFromDisk delete this repository entirely from the disk + EraseFromDisk() error } From 28adf41af6a5c0f4b19875912d056b03ea6710f8 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 1 Dec 2020 16:34:02 +0000 Subject: [PATCH 2/9] Move new RepoStorage interface to GoGitRepo, revert repoTest --- repository/gogit.go | 7 +++++++ repository/repo.go | 13 ------------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/repository/gogit.go b/repository/gogit.go index c59409d493214d7a6e6379edbe48769cef03dd99..45906ea4f9aa4bf095bb8aad1ecb9d022dfe59ab 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -13,6 +13,7 @@ import ( "sync" "time" + "github.com/go-git/go-billy" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" @@ -32,6 +33,12 @@ type GoGitRepo struct { clocks map[string]lamport.Clock keyring Keyring + RepoStorage +} + +type RepoStorage interface { + // Storage returns a billy.Filesystem giving access to $RepoPath/.git/git-bug + Storage() billy.Filesystem } func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { diff --git a/repository/repo.go b/repository/repo.go index 93069d61537f97fc1fe2704209630c5b3610f2eb..4b45a1c5c77c47b57dce2a3c7c2d8417a6f0fea7 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -5,7 +5,6 @@ import ( "errors" "github.com/MichaelMure/git-bug/util/lamport" - "github.com/go-git/go-billy/v5" ) var ( @@ -21,7 +20,6 @@ type Repo interface { RepoKeyring RepoCommon RepoData - RepoStorage } // ClockedRepo is a Repo that also has Lamport clocks @@ -66,11 +64,6 @@ type RepoCommon interface { GetRemotes() (map[string]string, error) } -type RepoStorage interface { - // Storage returns a billy.Filesystem giving access to $RepoPath/.git/git-bug - Storage() billy.Filesystem -} - // RepoData give access to the git data storage type RepoData interface { // FetchRefs fetch git refs from a remote @@ -152,10 +145,4 @@ type TestedRepo interface { type repoTest interface { // AddRemote add a new remote to the repository AddRemote(name string, url string) error - - // GetLocalRemote return the URL to use to add this repo as a local remote - GetLocalRemote() string - - // EraseFromDisk delete this repository entirely from the disk - EraseFromDisk() error } From bca9ae82745ffd619fd321f4200016c184849f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 1 Dec 2020 21:19:23 +0100 Subject: [PATCH 3/9] repo: more work towards RepoStorage --- go.mod | 1 - repository/git.go | 23 +++++++++++++++++++++++ repository/git_testing.go | 6 ++---- repository/gogit.go | 33 +++++++++++++++++++++------------ repository/gogit_test.go | 2 +- repository/gogit_testing.go | 6 ++---- repository/mock_repo.go | 32 +++++++++++++++++++++++++++----- repository/repo.go | 17 ++++++++++++++--- repository/repo_testing.go | 28 +++++++--------------------- 9 files changed, 97 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index 000f84f06bd1cb32e4422e815d64cfba4636b37a..4fcbcbab14791e2df5b49f4a3be8f9bb9fc9bdb0 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,6 @@ require ( github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect github.com/fatih/color v1.10.0 - github.com/go-git/go-billy v4.2.0+incompatible github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-git/v5 v5.2.0 github.com/golang/protobuf v1.4.3 // indirect diff --git a/repository/git.go b/repository/git.go index 504cdd8903a0e2cca639fb30a657e70453a0b6d2..8c319285e0a2ad023548dd17d82a0a7129c402aa 100644 --- a/repository/git.go +++ b/repository/git.go @@ -4,10 +4,15 @@ package repository import ( "bytes" "fmt" + "os" "path" + "path/filepath" "strings" "sync" + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/osfs" + "github.com/MichaelMure/git-bug/util/lamport" ) @@ -174,6 +179,11 @@ func (repo *GitRepo) GetRemotes() (map[string]string, error) { return remotes, nil } +// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug +func (repo *GitRepo) LocalStorage() billy.Filesystem { + return osfs.New(repo.path) +} + // FetchRefs fetch git refs from a remote func (repo *GitRepo) FetchRefs(remote, refSpec string) (string, error) { stdout, err := repo.runGitCommand("fetch", remote, refSpec) @@ -408,3 +418,16 @@ func (repo *GitRepo) AddRemote(name string, url string) error { return err } + +// GetLocalRemote return the URL to use to add this repo as a local remote +func (repo *GitRepo) GetLocalRemote() string { + return repo.path +} + +// EraseFromDisk delete this repository entirely from the disk +func (repo *GitRepo) EraseFromDisk() error { + path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git")) + + // fmt.Println("Cleaning repo:", path) + return os.RemoveAll(path) +} diff --git a/repository/git_testing.go b/repository/git_testing.go index 874cc86ceabd59fe0fbc977e6a13cd88bcd219d3..2168d53e41df8e9cd838cda2960dd6d591611dee 100644 --- a/repository/git_testing.go +++ b/repository/git_testing.go @@ -48,14 +48,12 @@ func SetupReposAndRemote() (repoA, repoB, remote TestedRepo) { repoB = CreateGoGitTestRepo(false) remote = CreateGoGitTestRepo(true) - remoteAddr := "file://" + remote.GetPath() - - err := repoA.AddRemote("origin", remoteAddr) + err := repoA.AddRemote("origin", remote.GetLocalRemote()) if err != nil { log.Fatal(err) } - err = repoB.AddRemote("origin", remoteAddr) + err = repoB.AddRemote("origin", remote.GetLocalRemote()) if err != nil { log.Fatal(err) } diff --git a/repository/gogit.go b/repository/gogit.go index 45906ea4f9aa4bf095bb8aad1ecb9d022dfe59ab..874885dba2721d0260126442e099123ad4094437 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -13,7 +13,8 @@ import ( "sync" "time" - "github.com/go-git/go-billy" + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/osfs" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" @@ -24,6 +25,7 @@ import ( ) var _ ClockedRepo = &GoGitRepo{} +var _ TestedRepo = &GoGitRepo{} type GoGitRepo struct { r *gogit.Repository @@ -33,12 +35,6 @@ type GoGitRepo struct { clocks map[string]lamport.Clock keyring Keyring - RepoStorage -} - -type RepoStorage interface { - // Storage returns a billy.Filesystem giving access to $RepoPath/.git/git-bug - Storage() billy.Filesystem } func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { @@ -202,11 +198,6 @@ func (repo *GoGitRepo) Keyring() Keyring { return repo.keyring } -// GetPath returns the path to the repo. -func (repo *GoGitRepo) GetPath() string { - return repo.path -} - // GetUserName returns the name the the user has used to configure git func (repo *GoGitRepo) GetUserName() (string, error) { return repo.AnyConfig().ReadString("user.name") @@ -277,6 +268,11 @@ func (repo *GoGitRepo) GetRemotes() (map[string]string, error) { return result, nil } +// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug +func (repo *GoGitRepo) LocalStorage() billy.Filesystem { + return osfs.New(repo.path) +} + // FetchRefs fetch git refs from a remote func (repo *GoGitRepo) FetchRefs(remote string, refSpec string) (string, error) { buf := bytes.NewBuffer(nil) @@ -660,3 +656,16 @@ func (repo *GoGitRepo) AddRemote(name string, url string) error { return err } + +// GetLocalRemote return the URL to use to add this repo as a local remote +func (repo *GoGitRepo) GetLocalRemote() string { + return repo.path +} + +// EraseFromDisk delete this repository entirely from the disk +func (repo *GoGitRepo) EraseFromDisk() error { + path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git")) + + // fmt.Println("Cleaning repo:", path) + return os.RemoveAll(path) +} diff --git a/repository/gogit_test.go b/repository/gogit_test.go index fba990d3c4c8259d0c9b1188210c008b045cd899..a1f67664c053afde03439e3bdfbb1383a7c3a75f 100644 --- a/repository/gogit_test.go +++ b/repository/gogit_test.go @@ -58,7 +58,7 @@ func TestNewGoGitRepo(t *testing.T) { require.Error(t, err, i) } else { require.NoError(t, err, i) - assert.Equal(t, filepath.ToSlash(tc.outPath), filepath.ToSlash(r.GetPath()), i) + assert.Equal(t, filepath.ToSlash(tc.outPath), filepath.ToSlash(r.path), i) } } } diff --git a/repository/gogit_testing.go b/repository/gogit_testing.go index f20ff6be3734cb14315f80d0e99097a6dc14fd6f..a8bff41ebd17f5d859175c415b558a02d325d905 100644 --- a/repository/gogit_testing.go +++ b/repository/gogit_testing.go @@ -42,14 +42,12 @@ func SetupGoGitReposAndRemote() (repoA, repoB, remote TestedRepo) { repoB = CreateGoGitTestRepo(false) remote = CreateGoGitTestRepo(true) - remoteAddr := "file://" + remote.GetPath() - - err := repoA.AddRemote("origin", remoteAddr) + err := repoA.AddRemote("origin", remote.GetLocalRemote()) if err != nil { log.Fatal(err) } - err = repoB.AddRemote("origin", remoteAddr) + err = repoB.AddRemote("origin", remote.GetLocalRemote()) if err != nil { log.Fatal(err) } diff --git a/repository/mock_repo.go b/repository/mock_repo.go index 628939aab9975997999c47843815cff0839135c0..74d4cae7ae635c44c84958319adb54b49d8d6d33 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -6,6 +6,8 @@ import ( "strings" "github.com/99designs/keyring" + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" "github.com/MichaelMure/git-bug/util/lamport" ) @@ -18,6 +20,7 @@ type mockRepoForTest struct { *mockRepoConfig *mockRepoKeyring *mockRepoCommon + *mockRepoStorage *mockRepoData *mockRepoClock } @@ -27,6 +30,7 @@ func NewMockRepoForTest() *mockRepoForTest { mockRepoConfig: NewMockRepoConfig(), mockRepoKeyring: NewMockRepoKeyring(), mockRepoCommon: NewMockRepoCommon(), + mockRepoStorage: NewMockRepoStorage(), mockRepoData: NewMockRepoData(), mockRepoClock: NewMockRepoClock(), } @@ -86,11 +90,6 @@ func NewMockRepoCommon() *mockRepoCommon { return &mockRepoCommon{} } -// GetPath returns the path to the repo. -func (r *mockRepoCommon) GetPath() string { - return "~/mockRepo/" -} - func (r *mockRepoCommon) GetUserName() (string, error) { return "René Descartes", nil } @@ -112,6 +111,20 @@ func (r *mockRepoCommon) GetRemotes() (map[string]string, error) { }, nil } +var _ RepoStorage = &mockRepoStorage{} + +type mockRepoStorage struct { + localFs billy.Filesystem +} + +func NewMockRepoStorage() *mockRepoStorage { + return &mockRepoStorage{localFs: memfs.New()} +} + +func (m *mockRepoStorage) LocalStorage() billy.Filesystem { + return m.localFs +} + var _ RepoData = &mockRepoData{} type commit struct { @@ -314,6 +327,15 @@ func (r *mockRepoData) AddRemote(name string, url string) error { panic("implement me") } +func (m mockRepoForTest) GetLocalRemote() string { + panic("implement me") +} + +func (m mockRepoForTest) EraseFromDisk() error { + // nothing to do + return nil +} + type mockRepoClock struct { clocks map[string]lamport.Clock } diff --git a/repository/repo.go b/repository/repo.go index 4b45a1c5c77c47b57dce2a3c7c2d8417a6f0fea7..d34995f9b5cef741fec514bc005c81009f02ef9e 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -4,6 +4,8 @@ package repository import ( "errors" + "github.com/go-git/go-billy/v5" + "github.com/MichaelMure/git-bug/util/lamport" ) @@ -20,6 +22,7 @@ type Repo interface { RepoKeyring RepoCommon RepoData + RepoStorage } // ClockedRepo is a Repo that also has Lamport clocks @@ -48,9 +51,6 @@ type RepoKeyring interface { // RepoCommon represent the common function the we want all the repo to implement type RepoCommon interface { - // GetPath returns the path to the repo. - GetPath() string - // GetUserName returns the name the the user has used to configure git GetUserName() (string, error) @@ -64,6 +64,11 @@ type RepoCommon interface { GetRemotes() (map[string]string, error) } +type RepoStorage interface { + // LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug + LocalStorage() billy.Filesystem +} + // RepoData give access to the git data storage type RepoData interface { // FetchRefs fetch git refs from a remote @@ -145,4 +150,10 @@ type TestedRepo interface { type repoTest interface { // AddRemote add a new remote to the repository AddRemote(name string, url string) error + + // GetLocalRemote return the URL to use to add this repo as a local remote + GetLocalRemote() string + + // EraseFromDisk delete this repository entirely from the disk + EraseFromDisk() error } diff --git a/repository/repo_testing.go b/repository/repo_testing.go index 41b3609ef9a3e2e8d34aaf56630b640bde174015..c0e1fa7979c239153536df28a67a5d216378fa83 100644 --- a/repository/repo_testing.go +++ b/repository/repo_testing.go @@ -3,8 +3,6 @@ package repository import ( "log" "math/rand" - "os" - "strings" "testing" "github.com/stretchr/testify/require" @@ -15,25 +13,13 @@ import ( func CleanupTestRepos(repos ...Repo) { var firstErr error for _, repo := range repos { - path := repo.GetPath() - if strings.HasSuffix(path, "/.git") { - // for a normal repository (not --bare), we want to remove everything - // including the parent directory where files are checked out - path = strings.TrimSuffix(path, "/.git") - - // Testing non-bare repo should also check path is - // only .git (i.e. ./.git), but doing so, we should - // try to remove the current directory and hav some - // trouble. In the present case, this case should not - // occur. - // TODO consider warning or error when path == ".git" - } - // fmt.Println("Cleaning repo:", path) - err := os.RemoveAll(path) - if err != nil { - log.Println(err) - if firstErr == nil { - firstErr = err + if repo, ok := repo.(TestedRepo); ok { + err := repo.EraseFromDisk() + if err != nil { + log.Println(err) + if firstErr == nil { + firstErr = err + } } } } From 4ef2c1104079032336da9f2a7879f2432c2609ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Sat, 5 Dec 2020 03:08:54 +0100 Subject: [PATCH 4/9] repo: finish RepoStorage move --- bug/bug_test.go | 4 +- cache/repo_cache.go | 31 ++---- cache/repo_cache_bug.go | 10 +- cache/repo_cache_common.go | 24 +++-- cache/repo_cache_identity.go | 11 +- cache/repo_cache_test.go | 4 +- commands/env.go | 2 +- commands/select/select.go | 22 +--- doc/gen_docs.go | 5 +- go.sum | 1 + input/input.go | 36 ++++--- misc/gen_completion.go | 14 +-- misc/random_bugs/cmd/main.go | 2 +- repository/git.go | 9 +- repository/gogit.go | 152 ++++++++++++++++----------- repository/gogit_config.go | 6 +- repository/gogit_test.go | 4 +- repository/keyring.go | 4 +- repository/repo.go | 5 + util/lamport/persisted_clock.go | 29 ++--- util/lamport/persisted_clock_test.go | 8 +- 21 files changed, 191 insertions(+), 192 deletions(-) diff --git a/bug/bug_test.go b/bug/bug_test.go index 05f6e617e9497e27e5d19425d305f2bf04457f5c..6363f4e92db5bf2da5cc6620fea38f8cace203c5 100644 --- a/bug/bug_test.go +++ b/bug/bug_test.go @@ -130,10 +130,10 @@ func TestBugRemove(t *testing.T) { remoteB := repository.CreateGoGitTestRepo(true) defer repository.CleanupTestRepos(repo, remoteA, remoteB) - err := repo.AddRemote("remoteA", "file://"+remoteA.GetPath()) + err := repo.AddRemote("remoteA", remoteA.GetLocalRemote()) require.NoError(t, err) - err = repo.AddRemote("remoteB", "file://"+remoteB.GetPath()) + err = repo.AddRemote("remoteB", remoteB.GetLocalRemote()) require.NoError(t, err) // generate a bunch of bugs diff --git a/cache/repo_cache.go b/cache/repo_cache.go index 8bce3d8c2d97cd384bd4b99316c3799fbbb16be8..a08744cc94ff23ab6e97ff3d52abf49fac10f620 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -5,8 +5,6 @@ import ( "io" "io/ioutil" "os" - "path" - "path/filepath" "strconv" "sync" @@ -133,25 +131,18 @@ func (c *RepoCache) write() error { } func (c *RepoCache) lock() error { - lockPath := repoLockFilePath(c.repo) - err := repoIsAvailable(c.repo) if err != nil { return err } - err = os.MkdirAll(filepath.Dir(lockPath), 0777) - if err != nil { - return err - } - - f, err := os.Create(lockPath) + f, err := c.repo.LocalStorage().Create(lockfile) if err != nil { return err } pid := fmt.Sprintf("%d", os.Getpid()) - _, err = f.WriteString(pid) + _, err = f.Write([]byte(pid)) if err != nil { return err } @@ -175,11 +166,12 @@ func (c *RepoCache) Close() error { c.searchCache = nil } - lockPath := repoLockFilePath(c.repo) - return os.Remove(lockPath) + return c.repo.LocalStorage().Remove(lockfile) } func (c *RepoCache) buildCache() error { + // TODO: make that parallel + c.muBug.Lock() defer c.muBug.Unlock() c.muIdentity.Lock() @@ -230,17 +222,11 @@ func (c *RepoCache) buildCache() error { return nil } -func repoLockFilePath(repo repository.Repo) string { - return path.Join(repo.GetPath(), "git-bug", lockfile) -} - // repoIsAvailable check is the given repository is locked by a Cache. // Note: this is a smart function that will cleanup the lock file if the // corresponding process is not there anymore. // If no error is returned, the repo is free to edit. -func repoIsAvailable(repo repository.Repo) error { - lockPath := repoLockFilePath(repo) - +func repoIsAvailable(repo repository.RepoStorage) error { // Todo: this leave way for a racey access to the repo between the test // if the file exist and the actual write. It's probably not a problem in // practice because using a repository will be done from user interaction @@ -252,8 +238,7 @@ func repoIsAvailable(repo repository.Repo) error { // computer. Should add a configuration that prevent the cleaning of the // lock file - f, err := os.Open(lockPath) - + f, err := repo.LocalStorage().Open(lockfile) if err != nil && !os.IsNotExist(err) { return err } @@ -285,7 +270,7 @@ func repoIsAvailable(repo repository.Repo) error { return err } - err = os.Remove(lockPath) + err = repo.LocalStorage().Remove(lockfile) if err != nil { return err } diff --git a/cache/repo_cache_bug.go b/cache/repo_cache_bug.go index d57e1bce3fc96469d9ca352c9ae0e2ffabac70fe..f540e51b9452b0a17a942c2e8f5c62de08fc4ff2 100644 --- a/cache/repo_cache_bug.go +++ b/cache/repo_cache_bug.go @@ -5,8 +5,6 @@ import ( "encoding/gob" "errors" "fmt" - "os" - "path" "sort" "strings" "time" @@ -25,10 +23,6 @@ const ( var errBugNotInCache = errors.New("bug missing from cache") -func bugCacheFilePath(repo repository.Repo) string { - return path.Join(repo.GetPath(), "git-bug", bugCacheFile) -} - func searchCacheDirPath(repo repository.Repo) string { return path.Join(repo.GetPath(), "git-bug", searchCacheDir) } @@ -65,7 +59,7 @@ func (c *RepoCache) loadBugCache() error { c.muBug.Lock() defer c.muBug.Unlock() - f, err := os.Open(bugCacheFilePath(c.repo)) + f, err := c.repo.LocalStorage().Open(bugCacheFile) if err != nil { return err } @@ -148,7 +142,7 @@ func (c *RepoCache) writeBugCache() error { return err } - f, err := os.Create(bugCacheFilePath(c.repo)) + f, err := c.repo.LocalStorage().Create(bugCacheFile) if err != nil { return err } diff --git a/cache/repo_cache_common.go b/cache/repo_cache_common.go index 95e2f7bbfc2e71cb22fcecfb42390a2d8d6a2f09..5dc19d22ed83082d8dd0445aacd7e06c9721b511 100644 --- a/cache/repo_cache_common.go +++ b/cache/repo_cache_common.go @@ -3,6 +3,7 @@ package cache import ( "fmt" + "github.com/go-git/go-billy/v5" "github.com/pkg/errors" "github.com/MichaelMure/git-bug/bug" @@ -30,13 +31,19 @@ func (c *RepoCache) AnyConfig() repository.ConfigRead { return c.repo.AnyConfig() } +// Keyring give access to a user-wide storage for secrets func (c *RepoCache) Keyring() repository.Keyring { return c.repo.Keyring() } -// GetPath returns the path to the repo. -func (c *RepoCache) GetPath() string { - return c.repo.GetPath() +// GetUserName returns the name the the user has used to configure git +func (c *RepoCache) GetUserName() (string, error) { + return c.repo.GetUserName() +} + +// GetUserEmail returns the email address that the user has used to configure git. +func (c *RepoCache) GetUserEmail() (string, error) { + return c.repo.GetUserEmail() } // GetCoreEditor returns the name of the editor that the user has used to configure git. @@ -49,14 +56,9 @@ func (c *RepoCache) GetRemotes() (map[string]string, error) { return c.repo.GetRemotes() } -// GetUserName returns the name the the user has used to configure git -func (c *RepoCache) GetUserName() (string, error) { - return c.repo.GetUserName() -} - -// GetUserEmail returns the email address that the user has used to configure git. -func (c *RepoCache) GetUserEmail() (string, error) { - return c.repo.GetUserEmail() +// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug +func (c *RepoCache) LocalStorage() billy.Filesystem { + return c.repo.LocalStorage() } // ReadData will attempt to read arbitrary data from the given hash diff --git a/cache/repo_cache_identity.go b/cache/repo_cache_identity.go index 8957d4aabf1fec55b66416705abdbe98749ca340..8df5b810b29c90546767f3dad0cc42be76345b40 100644 --- a/cache/repo_cache_identity.go +++ b/cache/repo_cache_identity.go @@ -4,20 +4,13 @@ import ( "bytes" "encoding/gob" "fmt" - "os" - "path" "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/repository" ) const identityCacheFile = "identity-cache" -func identityCacheFilePath(repo repository.Repo) string { - return path.Join(repo.GetPath(), "git-bug", identityCacheFile) -} - // identityUpdated is a callback to trigger when the excerpt of an identity // changed, that is each time an identity is updated func (c *RepoCache) identityUpdated(id entity.Id) error { @@ -41,7 +34,7 @@ func (c *RepoCache) loadIdentityCache() error { c.muIdentity.Lock() defer c.muIdentity.Unlock() - f, err := os.Open(identityCacheFilePath(c.repo)) + f, err := c.repo.LocalStorage().Open(identityCacheFile) if err != nil { return err } @@ -88,7 +81,7 @@ func (c *RepoCache) writeIdentityCache() error { return err } - f, err := os.Create(identityCacheFilePath(c.repo)) + f, err := c.repo.LocalStorage().Create(identityCacheFile) if err != nil { return err } diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go index 0037c7bbaf619140005d4c4fda4149aa74757059..1c5c41d217787ae86cedaedb360026bb3e1ab3eb 100644 --- a/cache/repo_cache_test.go +++ b/cache/repo_cache_test.go @@ -167,10 +167,10 @@ func TestRemove(t *testing.T) { remoteB := repository.CreateGoGitTestRepo(true) defer repository.CleanupTestRepos(repo, remoteA, remoteB) - err := repo.AddRemote("remoteA", "file://"+remoteA.GetPath()) + err := repo.AddRemote("remoteA", remoteA.GetLocalRemote()) require.NoError(t, err) - err = repo.AddRemote("remoteB", "file://"+remoteB.GetPath()) + err = repo.AddRemote("remoteB", remoteB.GetLocalRemote()) require.NoError(t, err) repoCache, err := NewRepoCache(repo) diff --git a/commands/env.go b/commands/env.go index 5658342d2efdd806b03feb0dbe000d270337b402..8d12c5bfa0847a314ac86811589de92ff4533b1a 100644 --- a/commands/env.go +++ b/commands/env.go @@ -54,7 +54,7 @@ func loadRepo(env *Env) func(*cobra.Command, []string) error { return fmt.Errorf("unable to get the current working directory: %q", err) } - env.repo, err = repository.NewGoGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader}) + env.repo, err = repository.OpenGoGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader}) if err == repository.ErrNotARepo { return fmt.Errorf("%s must be run from within a git repo", rootCommandName) } diff --git a/commands/select/select.go b/commands/select/select.go index cf861fcc885ad5b9eb2a28d502af8b9976c98be5..3df743874a86cdfbddba80ec43619273c5fdc527 100644 --- a/commands/select/select.go +++ b/commands/select/select.go @@ -5,14 +5,12 @@ import ( "io" "io/ioutil" "os" - "path" "github.com/pkg/errors" "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/entity" - "github.com/MichaelMure/git-bug/repository" ) const selectFile = "select" @@ -71,14 +69,12 @@ func ResolveBug(repo *cache.RepoCache, args []string) (*cache.BugCache, []string // Select will select a bug for future use func Select(repo *cache.RepoCache, id entity.Id) error { - selectPath := selectFilePath(repo) - - f, err := os.OpenFile(selectPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + f, err := repo.LocalStorage().OpenFile(selectFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { return err } - _, err = f.WriteString(id.String()) + _, err = f.Write([]byte(id.String())) if err != nil { return err } @@ -88,15 +84,11 @@ func Select(repo *cache.RepoCache, id entity.Id) error { // Clear will clear the selected bug, if any func Clear(repo *cache.RepoCache) error { - selectPath := selectFilePath(repo) - - return os.Remove(selectPath) + return repo.LocalStorage().Remove(selectFile) } func selected(repo *cache.RepoCache) (*cache.BugCache, error) { - selectPath := selectFilePath(repo) - - f, err := os.Open(selectPath) + f, err := repo.LocalStorage().Open(selectFile) if err != nil { if os.IsNotExist(err) { return nil, nil @@ -115,7 +107,7 @@ func selected(repo *cache.RepoCache) (*cache.BugCache, error) { id := entity.Id(buf) if err := id.Validate(); err != nil { - err = os.Remove(selectPath) + err = repo.LocalStorage().Remove(selectFile) if err != nil { return nil, errors.Wrap(err, "error while removing invalid select file") } @@ -135,7 +127,3 @@ func selected(repo *cache.RepoCache) (*cache.BugCache, error) { return b, nil } - -func selectFilePath(repo repository.RepoCommon) string { - return path.Join(repo.GetPath(), "git-bug", selectFile) -} diff --git a/doc/gen_docs.go b/doc/gen_docs.go index 8bb596fe4b5e037ae03923a811404dead8817b26..482abd8e95162ab83e40c91c3f4f59bf54dc9ba6 100644 --- a/doc/gen_docs.go +++ b/doc/gen_docs.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "path" "path/filepath" "sync" "time" @@ -42,7 +41,7 @@ func main() { func genManPage(root *cobra.Command) error { cwd, _ := os.Getwd() - dir := path.Join(cwd, "doc", "man") + dir := filepath.Join(cwd, "doc", "man") // fixed date to avoid having to commit each month date := time.Date(2019, 4, 1, 12, 0, 0, 0, time.UTC) @@ -69,7 +68,7 @@ func genManPage(root *cobra.Command) error { func genMarkdown(root *cobra.Command) error { cwd, _ := os.Getwd() - dir := path.Join(cwd, "doc", "md") + dir := filepath.Join(cwd, "doc", "md") files, err := filepath.Glob(dir + "/*.md") if err != nil { diff --git a/go.sum b/go.sum index fe78e7ea766dfd8299884736b805aa37e10670a1..fc9e2392e60b9326d1ff26a3f729810dd729b14b 100644 --- a/go.sum +++ b/go.sum @@ -229,6 +229,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= diff --git a/input/input.go b/input/input.go index ca787ceb516654fa4a2560b7051ad9246de44c62..ef76f53ee59b17f4d444bca31f7f4fdbf01d2141 100644 --- a/input/input.go +++ b/input/input.go @@ -11,9 +11,11 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "strings" "github.com/MichaelMure/git-bug/repository" + "github.com/go-git/go-billy/v5/util" "github.com/pkg/errors" ) @@ -35,7 +37,7 @@ const bugTitleCommentTemplate = `%s%s // BugCreateEditorInput will open the default editor in the terminal with a // template for the user to fill. The file is then processed to extract title // and message. -func BugCreateEditorInput(repo repository.RepoCommon, preTitle string, preMessage string) (string, string, error) { +func BugCreateEditorInput(repo repository.RepoCommonStorage, preTitle string, preMessage string) (string, string, error) { if preMessage != "" { preMessage = "\n\n" + preMessage } @@ -101,10 +103,10 @@ const bugCommentTemplate = `%s // BugCommentEditorInput will open the default editor in the terminal with a // template for the user to fill. The file is then processed to extract a comment. -func BugCommentEditorInput(repo repository.RepoCommon, preMessage string) (string, error) { +func BugCommentEditorInput(repo repository.RepoCommonStorage, preMessage string) (string, error) { template := fmt.Sprintf(bugCommentTemplate, preMessage) - raw, err := launchEditorWithTemplate(repo, messageFilename, template) + raw, err := launchEditorWithTemplate(repo, messageFilename, template) if err != nil { return "", err } @@ -152,10 +154,10 @@ const bugTitleTemplate = `%s // BugTitleEditorInput will open the default editor in the terminal with a // template for the user to fill. The file is then processed to extract a title. -func BugTitleEditorInput(repo repository.RepoCommon, preTitle string) (string, error) { +func BugTitleEditorInput(repo repository.RepoCommonStorage, preTitle string) (string, error) { template := fmt.Sprintf(bugTitleTemplate, preTitle) - raw, err := launchEditorWithTemplate(repo, messageFilename, template) + raw, err := launchEditorWithTemplate(repo, messageFilename, template) if err != nil { return "", err } @@ -212,10 +214,10 @@ const queryTemplate = `%s // QueryEditorInput will open the default editor in the terminal with a // template for the user to fill. The file is then processed to extract a query. -func QueryEditorInput(repo repository.RepoCommon, preQuery string) (string, error) { +func QueryEditorInput(repo repository.RepoCommonStorage, preQuery string) (string, error) { template := fmt.Sprintf(queryTemplate, preQuery) - raw, err := launchEditorWithTemplate(repo, messageFilename, template) + raw, err := launchEditorWithTemplate(repo, messageFilename, template) if err != nil { return "", err } @@ -238,11 +240,8 @@ func QueryEditorInput(repo repository.RepoCommon, preQuery string) (string, erro // launchEditorWithTemplate will launch an editor as launchEditor do, but with a // provided template. -func launchEditorWithTemplate(repo repository.RepoCommon, fileName string, template string) (string, error) { - path := fmt.Sprintf("%s/%s", repo.GetPath(), fileName) - - err := ioutil.WriteFile(path, []byte(template), 0644) - +func launchEditorWithTemplate(repo repository.RepoCommonStorage, fileName string, template string) (string, error) { + err := util.WriteFile(repo.LocalStorage(), fileName, []byte(template), 0644) if err != nil { return "", err } @@ -254,20 +253,25 @@ func launchEditorWithTemplate(repo repository.RepoCommon, fileName string, templ // method blocks until the editor command has returned. // // The specified filename should be a temporary file and provided as a relative path -// from the repo (e.g. "FILENAME" will be converted to "[/].git/FILENAME"). This file +// from the repo (e.g. "FILENAME" will be converted to "[/].git/git-bug/FILENAME"). This file // will be deleted after the editor is closed and its contents have been read. // // This method returns the text that was read from the temporary file, or // an error if any step in the process failed. -func launchEditor(repo repository.RepoCommon, fileName string) (string, error) { - path := fmt.Sprintf("%s/%s", repo.GetPath(), fileName) - defer os.Remove(path) +func launchEditor(repo repository.RepoCommonStorage, fileName string) (string, error) { + defer repo.LocalStorage().Remove(fileName) editor, err := repo.GetCoreEditor() if err != nil { return "", fmt.Errorf("Unable to detect default git editor: %v\n", err) } + repo.LocalStorage().Root() + + // bypass the interface but that's ok: we need that because we are communicating + // the absolute path to an external program + path := filepath.Join(repo.LocalStorage().Root(), fileName) + cmd, err := startInlineCommand(editor, path) if err != nil { // Running the editor directly did not work. This might mean that diff --git a/misc/gen_completion.go b/misc/gen_completion.go index af00fa645767af49b6f7978d5dc16302e0f9dd0e..c073e67e8a2536994f07f9ce7b832d0fd9d12b70 100644 --- a/misc/gen_completion.go +++ b/misc/gen_completion.go @@ -3,7 +3,7 @@ package main import ( "fmt" "os" - "path" + "path/filepath" "sync" "github.com/spf13/cobra" @@ -41,24 +41,24 @@ func main() { func genBash(root *cobra.Command) error { cwd, _ := os.Getwd() - dir := path.Join(cwd, "misc", "bash_completion", "git-bug") + dir := filepath.Join(cwd, "misc", "bash_completion", "git-bug") return root.GenBashCompletionFile(dir) } func genFish(root *cobra.Command) error { cwd, _ := os.Getwd() - dir := path.Join(cwd, "misc", "fish_completion", "git-bug") + dir := filepath.Join(cwd, "misc", "fish_completion", "git-bug") return root.GenFishCompletionFile(dir, true) } func genPowerShell(root *cobra.Command) error { cwd, _ := os.Getwd() - filepath := path.Join(cwd, "misc", "powershell_completion", "git-bug") - return root.GenPowerShellCompletionFile(filepath) + path := filepath.Join(cwd, "misc", "powershell_completion", "git-bug") + return root.GenPowerShellCompletionFile(path) } func genZsh(root *cobra.Command) error { cwd, _ := os.Getwd() - filepath := path.Join(cwd, "misc", "zsh_completion", "git-bug") - return root.GenZshCompletionFile(filepath) + path := filepath.Join(cwd, "misc", "zsh_completion", "git-bug") + return root.GenZshCompletionFile(path) } diff --git a/misc/random_bugs/cmd/main.go b/misc/random_bugs/cmd/main.go index 3127b4aaddcc50956a654aeb7de95c2c7465683a..010ae6d1bb4606455c11fb3197eb2448e5004bfd 100644 --- a/misc/random_bugs/cmd/main.go +++ b/misc/random_bugs/cmd/main.go @@ -20,7 +20,7 @@ func main() { bug.ClockLoader, } - repo, err := repository.NewGoGitRepo(dir, loaders) + repo, err := repository.OpenGoGitRepo(dir, loaders) if err != nil { panic(err) } diff --git a/repository/git.go b/repository/git.go index 8c319285e0a2ad023548dd17d82a0a7129c402aa..e67e12f54735dba5fc8091a39fbe900a1f692c20 100644 --- a/repository/git.go +++ b/repository/git.go @@ -5,7 +5,6 @@ import ( "bytes" "fmt" "os" - "path" "path/filepath" "strings" "sync" @@ -379,9 +378,7 @@ func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { repo.clocksMutex.Lock() defer repo.clocksMutex.Unlock() - p := path.Join(repo.path, clockPath, name+"-clock") - - c, err = lamport.NewPersistedClock(p) + c, err = lamport.NewPersistedClock(repo.LocalStorage(), name+"-clock") if err != nil { return nil, err } @@ -398,9 +395,7 @@ func (repo *GitRepo) getClock(name string) (lamport.Clock, error) { return c, nil } - p := path.Join(repo.path, clockPath, name+"-clock") - - c, err := lamport.LoadPersistedClock(p) + c, err := lamport.LoadPersistedClock(repo.LocalStorage(), name+"-clock") if err == nil { repo.clocks[name] = c return c, nil diff --git a/repository/gogit.go b/repository/gogit.go index 874885dba2721d0260126442e099123ad4094437..65c2ab546fba237cda635760d7c126b65d04e05f 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -6,20 +6,22 @@ import ( "io/ioutil" "os" "os/exec" - stdpath "path" "path/filepath" "sort" "strings" "sync" "time" + "github.com/99designs/keyring" "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/osfs" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/storage/memory" "github.com/MichaelMure/git-bug/util/lamport" ) @@ -28,16 +30,18 @@ var _ ClockedRepo = &GoGitRepo{} var _ TestedRepo = &GoGitRepo{} type GoGitRepo struct { - r *gogit.Repository - path string + r *gogit.Repository + path string + isMemory bool clocksMutex sync.Mutex clocks map[string]lamport.Clock - keyring Keyring + keyring Keyring + localStorage billy.Filesystem } -func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { +func OpenGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { path, err := detectGitPath(path) if err != nil { return nil, err @@ -54,10 +58,12 @@ func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { } repo := &GoGitRepo{ - r: r, - path: path, - clocks: make(map[string]lamport.Clock), - keyring: k, + r: r, + path: path, + isMemory: false, + clocks: make(map[string]lamport.Clock), + keyring: k, + localStorage: osfs.New(filepath.Join(path, "git-bug")), } for _, loader := range clockLoaders { @@ -79,6 +85,69 @@ func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { return repo, nil } +// InitGoGitRepo create a new empty git repo at the given path +func InitGoGitRepo(path string) (*GoGitRepo, error) { + r, err := gogit.PlainInit(path, false) + if err != nil { + return nil, err + } + + k, err := defaultKeyring() + if err != nil { + return nil, err + } + + return &GoGitRepo{ + r: r, + path: filepath.Join(path, ".git"), + isMemory: false, + clocks: make(map[string]lamport.Clock), + keyring: k, + localStorage: osfs.New(filepath.Join(path, ".git", "git-bug")), + }, nil +} + +// InitBareGoGitRepo create a new --bare empty git repo at the given path +func InitBareGoGitRepo(path string) (*GoGitRepo, error) { + r, err := gogit.PlainInit(path, true) + if err != nil { + return nil, err + } + + k, err := defaultKeyring() + if err != nil { + return nil, err + } + + return &GoGitRepo{ + r: r, + path: path, + isMemory: false, + clocks: make(map[string]lamport.Clock), + keyring: k, + localStorage: osfs.New(filepath.Join(path, "git-bug")), + }, nil +} + +func InitMemoryGoGitRepo() (*GoGitRepo, error) { + r, err := gogit.Init(memory.NewStorage(), nil) + if err != nil { + return nil, err + } + + k := keyring.NewArrayKeyring(nil) + + repo := &GoGitRepo{ + r: r, + isMemory: true, + clocks: make(map[string]lamport.Clock), + keyring: k, + localStorage: memfs.New(), + } + + return repo, nil +} + func detectGitPath(path string) (string, error) { // normalize the path path, err := filepath.Abs(path) @@ -87,12 +156,12 @@ func detectGitPath(path string) (string, error) { } for { - fi, err := os.Stat(stdpath.Join(path, ".git")) + fi, err := os.Stat(filepath.Join(path, ".git")) if err == nil { if !fi.IsDir() { return "", fmt.Errorf(".git exist but is not a directory") } - return stdpath.Join(path, ".git"), nil + return filepath.Join(path, ".git"), nil } if !os.IsNotExist(err) { // unknown error @@ -120,7 +189,7 @@ func isGitDir(path string) (bool, error) { markers := []string{"HEAD", "objects", "refs"} for _, marker := range markers { - _, err := os.Stat(stdpath.Join(path, marker)) + _, err := os.Stat(filepath.Join(path, marker)) if err == nil { continue } @@ -135,46 +204,6 @@ func isGitDir(path string) (bool, error) { return true, nil } -// InitGoGitRepo create a new empty git repo at the given path -func InitGoGitRepo(path string) (*GoGitRepo, error) { - r, err := gogit.PlainInit(path, false) - if err != nil { - return nil, err - } - - k, err := defaultKeyring() - if err != nil { - return nil, err - } - - return &GoGitRepo{ - r: r, - path: path + "/.git", - clocks: make(map[string]lamport.Clock), - keyring: k, - }, nil -} - -// InitBareGoGitRepo create a new --bare empty git repo at the given path -func InitBareGoGitRepo(path string) (*GoGitRepo, error) { - r, err := gogit.PlainInit(path, true) - if err != nil { - return nil, err - } - - k, err := defaultKeyring() - if err != nil { - return nil, err - } - - return &GoGitRepo{ - r: r, - path: path, - clocks: make(map[string]lamport.Clock), - keyring: k, - }, nil -} - // LocalConfig give access to the repository scoped configuration func (repo *GoGitRepo) LocalConfig() Config { return newGoGitLocalConfig(repo.r) @@ -182,10 +211,7 @@ func (repo *GoGitRepo) LocalConfig() Config { // GlobalConfig give access to the global scoped configuration func (repo *GoGitRepo) GlobalConfig() Config { - // TODO: replace that with go-git native implementation once it's supported - // see: https://github.com/go-git/go-git - // see: https://github.com/src-d/go-git/issues/760 - return newGoGitGlobalConfig(repo.r) + return newGoGitGlobalConfig() } // AnyConfig give access to a merged local/global configuration @@ -270,7 +296,7 @@ func (repo *GoGitRepo) GetRemotes() (map[string]string, error) { // LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug func (repo *GoGitRepo) LocalStorage() billy.Filesystem { - return osfs.New(repo.path) + return repo.localStorage } // FetchRefs fetch git refs from a remote @@ -614,9 +640,7 @@ func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { repo.clocksMutex.Lock() defer repo.clocksMutex.Unlock() - p := stdpath.Join(repo.path, clockPath, name+"-clock") - - c, err = lamport.NewPersistedClock(p) + c, err = lamport.NewPersistedClock(repo.localStorage, name+"-clock") if err != nil { return nil, err } @@ -633,9 +657,7 @@ func (repo *GoGitRepo) getClock(name string) (lamport.Clock, error) { return c, nil } - p := stdpath.Join(repo.path, clockPath, name+"-clock") - - c, err := lamport.LoadPersistedClock(p) + c, err := lamport.LoadPersistedClock(repo.localStorage, name+"-clock") if err == nil { repo.clocks[name] = c return c, nil @@ -664,6 +686,10 @@ func (repo *GoGitRepo) GetLocalRemote() string { // EraseFromDisk delete this repository entirely from the disk func (repo *GoGitRepo) EraseFromDisk() error { + if repo.isMemory { + return nil + } + path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git")) // fmt.Println("Cleaning repo:", path) diff --git a/repository/gogit_config.go b/repository/gogit_config.go index 2f9a4cc34c5357e81c25adad9e637e11ba23fb6b..ba61adcac651a9217e3c36dccc3a9747c1d68d2e 100644 --- a/repository/gogit_config.go +++ b/repository/gogit_config.go @@ -24,7 +24,11 @@ func newGoGitLocalConfig(repo *gogit.Repository) *goGitConfig { } } -func newGoGitGlobalConfig(repo *gogit.Repository) *goGitConfig { +func newGoGitGlobalConfig() *goGitConfig { + // TODO: replace that with go-git native implementation once it's supported + // see: https://github.com/go-git/go-git + // see: https://github.com/src-d/go-git/issues/760 + return &goGitConfig{ ConfigRead: &goGitConfigReader{getConfig: func() (*config.Config, error) { return config.LoadConfig(config.GlobalScope) diff --git a/repository/gogit_test.go b/repository/gogit_test.go index a1f67664c053afde03439e3bdfbb1383a7c3a75f..a2bb49b9c0dcb745551b4b9e7bec3ce0c7e12cf6 100644 --- a/repository/gogit_test.go +++ b/repository/gogit_test.go @@ -19,7 +19,7 @@ func TestNewGoGitRepo(t *testing.T) { _, err = InitGoGitRepo(plainRoot) require.NoError(t, err) - plainGitDir := path.Join(plainRoot, ".git") + plainGitDir := filepath.Join(plainRoot, ".git") // Bare bareRoot, err := ioutil.TempDir("", "") @@ -52,7 +52,7 @@ func TestNewGoGitRepo(t *testing.T) { } for i, tc := range tests { - r, err := NewGoGitRepo(tc.inPath, nil) + r, err := OpenGoGitRepo(tc.inPath, nil) if tc.err { require.Error(t, err, i) diff --git a/repository/keyring.go b/repository/keyring.go index f690b0b31995d1decdb5a0a2ef06fe02d5a08821..4cb3c9ff57079b171db4a4ad5f4ceba5fd1d3ac3 100644 --- a/repository/keyring.go +++ b/repository/keyring.go @@ -2,7 +2,7 @@ package repository import ( "os" - "path" + "path/filepath" "github.com/99designs/keyring" ) @@ -38,7 +38,7 @@ func defaultKeyring() (Keyring, error) { ServiceName: "git-bug", // Fallback encrypted file - FileDir: path.Join(ucd, "git-bug", "keyring"), + FileDir: filepath.Join(ucd, "git-bug", "keyring"), // As we write the file in the user's config directory, this file should already be protected by the OS against // other user's access. We actually don't terribly need to protect it further and a password prompt across all // UI's would be a pain. Therefore we use here a constant password so the file will be unreadable by generic file diff --git a/repository/repo.go b/repository/repo.go index d34995f9b5cef741fec514bc005c81009f02ef9e..d8fe44e63a3772afb379cf2469cf447e87577ef7 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -25,6 +25,11 @@ type Repo interface { RepoStorage } +type RepoCommonStorage interface { + RepoCommon + RepoStorage +} + // ClockedRepo is a Repo that also has Lamport clocks type ClockedRepo interface { Repo diff --git a/util/lamport/persisted_clock.go b/util/lamport/persisted_clock.go index e70b01efd3c92348cb454213e54a462bb7493bf9..b9246f73e0581cf2c647230223707785b757cdc1 100644 --- a/util/lamport/persisted_clock.go +++ b/util/lamport/persisted_clock.go @@ -5,30 +5,28 @@ import ( "fmt" "io/ioutil" "os" - "path/filepath" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/util" ) var ErrClockNotExist = errors.New("clock doesn't exist") type PersistedClock struct { *MemClock + root billy.Filesystem filePath string } // NewPersistedClock create a new persisted Lamport clock -func NewPersistedClock(filePath string) (*PersistedClock, error) { +func NewPersistedClock(root billy.Filesystem, filePath string) (*PersistedClock, error) { clock := &PersistedClock{ MemClock: NewMemClock(), + root: root, filePath: filePath, } - dir := filepath.Dir(filePath) - err := os.MkdirAll(dir, 0777) - if err != nil { - return nil, err - } - - err = clock.Write() + err := clock.Write() if err != nil { return nil, err } @@ -37,8 +35,9 @@ func NewPersistedClock(filePath string) (*PersistedClock, error) { } // LoadPersistedClock load a persisted Lamport clock from a file -func LoadPersistedClock(filePath string) (*PersistedClock, error) { +func LoadPersistedClock(root billy.Filesystem, filePath string) (*PersistedClock, error) { clock := &PersistedClock{ + root: root, filePath: filePath, } @@ -71,13 +70,19 @@ func (pc *PersistedClock) Witness(time Time) error { } func (pc *PersistedClock) read() error { - content, err := ioutil.ReadFile(pc.filePath) + f, err := pc.root.Open(pc.filePath) if os.IsNotExist(err) { return ErrClockNotExist } if err != nil { return err } + defer f.Close() + + content, err := ioutil.ReadAll(f) + if err != nil { + return err + } var value uint64 n, err := fmt.Sscanf(string(content), "%d", &value) @@ -96,5 +101,5 @@ func (pc *PersistedClock) read() error { func (pc *PersistedClock) Write() error { data := []byte(fmt.Sprintf("%d", pc.counter)) - return ioutil.WriteFile(pc.filePath, data, 0644) + return util.WriteFile(pc.root, pc.filePath, data, 0644) } diff --git a/util/lamport/persisted_clock_test.go b/util/lamport/persisted_clock_test.go index aacec3bfed458957b0e60e456a7265610f880f24..9ef690da3922f944b74db8eaff0aa48ef6adaf1b 100644 --- a/util/lamport/persisted_clock_test.go +++ b/util/lamport/persisted_clock_test.go @@ -1,18 +1,16 @@ package lamport import ( - "io/ioutil" - "path" "testing" + "github.com/go-git/go-billy/v5/memfs" "github.com/stretchr/testify/require" ) func TestPersistedClock(t *testing.T) { - dir, err := ioutil.TempDir("", "") - require.NoError(t, err) + root := memfs.New() - c, err := NewPersistedClock(path.Join(dir, "test-clock")) + c, err := NewPersistedClock(root, "test-clock") require.NoError(t, err) testClock(t, c) From be6e653f1566f5812e7c4442927eee02963dfb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Sat, 5 Dec 2020 21:40:48 +0100 Subject: [PATCH 5/9] repo: remove the memory-only repo for now --- repository/gogit.go | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/repository/gogit.go b/repository/gogit.go index 65c2ab546fba237cda635760d7c126b65d04e05f..0d34c00aa5a11ca92a5374e7f88e27a3e8ffab9c 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -12,16 +12,13 @@ import ( "sync" "time" - "github.com/99designs/keyring" "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/osfs" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/object" - "github.com/go-git/go-git/v5/storage/memory" "github.com/MichaelMure/git-bug/util/lamport" ) @@ -30,9 +27,8 @@ var _ ClockedRepo = &GoGitRepo{} var _ TestedRepo = &GoGitRepo{} type GoGitRepo struct { - r *gogit.Repository - path string - isMemory bool + r *gogit.Repository + path string clocksMutex sync.Mutex clocks map[string]lamport.Clock @@ -41,6 +37,7 @@ type GoGitRepo struct { localStorage billy.Filesystem } +// OpenGoGitRepo open an already existing repo at the given path func OpenGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { path, err := detectGitPath(path) if err != nil { @@ -60,7 +57,6 @@ func OpenGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) repo := &GoGitRepo{ r: r, path: path, - isMemory: false, clocks: make(map[string]lamport.Clock), keyring: k, localStorage: osfs.New(filepath.Join(path, "git-bug")), @@ -100,7 +96,6 @@ func InitGoGitRepo(path string) (*GoGitRepo, error) { return &GoGitRepo{ r: r, path: filepath.Join(path, ".git"), - isMemory: false, clocks: make(map[string]lamport.Clock), keyring: k, localStorage: osfs.New(filepath.Join(path, ".git", "git-bug")), @@ -122,32 +117,12 @@ func InitBareGoGitRepo(path string) (*GoGitRepo, error) { return &GoGitRepo{ r: r, path: path, - isMemory: false, clocks: make(map[string]lamport.Clock), keyring: k, localStorage: osfs.New(filepath.Join(path, "git-bug")), }, nil } -func InitMemoryGoGitRepo() (*GoGitRepo, error) { - r, err := gogit.Init(memory.NewStorage(), nil) - if err != nil { - return nil, err - } - - k := keyring.NewArrayKeyring(nil) - - repo := &GoGitRepo{ - r: r, - isMemory: true, - clocks: make(map[string]lamport.Clock), - keyring: k, - localStorage: memfs.New(), - } - - return repo, nil -} - func detectGitPath(path string) (string, error) { // normalize the path path, err := filepath.Abs(path) @@ -686,10 +661,6 @@ func (repo *GoGitRepo) GetLocalRemote() string { // EraseFromDisk delete this repository entirely from the disk func (repo *GoGitRepo) EraseFromDisk() error { - if repo.isMemory { - return nil - } - path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git")) // fmt.Println("Cleaning repo:", path) From 71e1303234ef227851998f08c98c7c69670c9966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 8 Dec 2020 13:15:21 +0100 Subject: [PATCH 6/9] repo: simpler clock mutex locking --- repository/git.go | 9 +++------ repository/gogit.go | 9 +++------ repository/mock_repo.go | 5 +++++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/repository/git.go b/repository/git.go index e67e12f54735dba5fc8091a39fbe900a1f692c20..2348f5d57e9140d2df8b568e2304f154418c4dd3 100644 --- a/repository/git.go +++ b/repository/git.go @@ -367,6 +367,9 @@ func (repo *GitRepo) GetTreeHash(commit Hash) (Hash, error) { // GetOrCreateClock return a Lamport clock stored in the Repo. // If the clock doesn't exist, it's created. func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { + repo.clocksMutex.Lock() + defer repo.clocksMutex.Unlock() + c, err := repo.getClock(name) if err == nil { return c, nil @@ -375,9 +378,6 @@ func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { return nil, err } - repo.clocksMutex.Lock() - defer repo.clocksMutex.Unlock() - c, err = lamport.NewPersistedClock(repo.LocalStorage(), name+"-clock") if err != nil { return nil, err @@ -388,9 +388,6 @@ func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { } func (repo *GitRepo) getClock(name string) (lamport.Clock, error) { - repo.clocksMutex.Lock() - defer repo.clocksMutex.Unlock() - if c, ok := repo.clocks[name]; ok { return c, nil } diff --git a/repository/gogit.go b/repository/gogit.go index 0d34c00aa5a11ca92a5374e7f88e27a3e8ffab9c..741982aaddc91a2a5ddecb927bc33a12c9eaf6f3 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -604,6 +604,9 @@ func (repo *GoGitRepo) ListCommits(ref string) ([]Hash, error) { // GetOrCreateClock return a Lamport clock stored in the Repo. // If the clock doesn't exist, it's created. func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { + repo.clocksMutex.Lock() + defer repo.clocksMutex.Unlock() + c, err := repo.getClock(name) if err == nil { return c, nil @@ -612,9 +615,6 @@ func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { return nil, err } - repo.clocksMutex.Lock() - defer repo.clocksMutex.Unlock() - c, err = lamport.NewPersistedClock(repo.localStorage, name+"-clock") if err != nil { return nil, err @@ -625,9 +625,6 @@ func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { } func (repo *GoGitRepo) getClock(name string) (lamport.Clock, error) { - repo.clocksMutex.Lock() - defer repo.clocksMutex.Unlock() - if c, ok := repo.clocks[name]; ok { return c, nil } diff --git a/repository/mock_repo.go b/repository/mock_repo.go index 74d4cae7ae635c44c84958319adb54b49d8d6d33..02e5010f39a6a25973a7b25a511ba2b32fb516fe 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -4,6 +4,7 @@ import ( "crypto/sha1" "fmt" "strings" + "sync" "github.com/99designs/keyring" "github.com/go-git/go-billy/v5" @@ -337,6 +338,7 @@ func (m mockRepoForTest) EraseFromDisk() error { } type mockRepoClock struct { + mu sync.Mutex clocks map[string]lamport.Clock } @@ -347,6 +349,9 @@ func NewMockRepoClock() *mockRepoClock { } func (r *mockRepoClock) GetOrCreateClock(name string) (lamport.Clock, error) { + r.mu.Lock() + defer r.mu.Unlock() + if c, ok := r.clocks[name]; ok { return c, nil } From c884d557bf5e0ebdbe6e3b20535af24c2e97d29f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 8 Dec 2020 14:42:13 +0100 Subject: [PATCH 7/9] repo: move bleve there --- api/graphql/resolvers/repo.go | 5 +- cache/repo_cache.go | 14 +++--- cache/repo_cache_bug.go | 60 ++++++++++-------------- cache/repo_cache_test.go | 4 +- commands/ls.go | 5 +- identity/identity_test.go | 4 +- repository/git.go | 86 +++++++++++++++++++++++++++++++---- repository/gogit.go | 76 +++++++++++++++++++++++++++++++ repository/mock_repo.go | 47 +++++++++++++++++++ repository/repo.go | 14 ++++++ termui/bug_table.go | 6 ++- 11 files changed, 262 insertions(+), 59 deletions(-) diff --git a/api/graphql/resolvers/repo.go b/api/graphql/resolvers/repo.go index 5d96428e6b86ef56fad6c0943e2d822ce4184da8..c2163cbe88d9a952f347cc3244be78a3a2ba91af 100644 --- a/api/graphql/resolvers/repo.go +++ b/api/graphql/resolvers/repo.go @@ -41,7 +41,10 @@ func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *st } // Simply pass a []string with the ids to the pagination algorithm - source := obj.Repo.QueryBugs(q) + source, err := obj.Repo.QueryBugs(q) + if err != nil { + return nil, err + } // The edger create a custom edge holding just the id edger := func(id entity.Id, offset int) connections.Edge { diff --git a/cache/repo_cache.go b/cache/repo_cache.go index a08744cc94ff23ab6e97ff3d52abf49fac10f620..b5b9ee543992e7077128af0572401d927f31f801 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -13,7 +13,6 @@ import ( "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/process" - "github.com/blevesearch/bleve" ) // 1: original format @@ -55,8 +54,6 @@ type RepoCache struct { muBug sync.RWMutex // excerpt of bugs data for all bugs bugExcerpts map[entity.Id]*BugExcerpt - // searchable cache of all bugs - searchCache bleve.Index // bug loaded in memory bugs map[entity.Id]*BugCache // loadedBugs is an LRU cache that records which bugs the cache has loaded in @@ -161,9 +158,9 @@ func (c *RepoCache) Close() error { c.bugs = make(map[entity.Id]*BugCache) c.bugExcerpts = nil - if c.searchCache != nil { - c.searchCache.Close() - c.searchCache = nil + err := c.repo.Close() + if err != nil { + return err } return c.repo.LocalStorage().Remove(lockfile) @@ -199,9 +196,10 @@ func (c *RepoCache) buildCache() error { allBugs := bug.ReadAllLocal(c.repo) - err := c.createBleveIndex() + // wipe the index just to be sure + err := c.repo.ClearBleveIndex("bug") if err != nil { - return fmt.Errorf("Unable to create search cache. Error: %v", err) + return err } for b := range allBugs { diff --git a/cache/repo_cache_bug.go b/cache/repo_cache_bug.go index f540e51b9452b0a17a942c2e8f5c62de08fc4ff2..1701f66d09fb6ae9c1ded3c0d9ead16bd2a65bf4 100644 --- a/cache/repo_cache_bug.go +++ b/cache/repo_cache_bug.go @@ -23,10 +23,6 @@ const ( var errBugNotInCache = errors.New("bug missing from cache") -func searchCacheDirPath(repo repository.Repo) string { - return path.Join(repo.GetPath(), "git-bug", searchCacheDir) -} - // bugUpdated is a callback to trigger when the excerpt of a bug changed, // that is each time a bug is updated func (c *RepoCache) bugUpdated(id entity.Id) error { @@ -82,14 +78,13 @@ func (c *RepoCache) loadBugCache() error { c.bugExcerpts = aux.Excerpts - blevePath := searchCacheDirPath(c.repo) - searchCache, err := bleve.Open(blevePath) + index, err := c.repo.GetBleveIndex("bug") if err != nil { - return fmt.Errorf("Unable to open search cache. Error: %v", err) + return err } - c.searchCache = searchCache - count, err := c.searchCache.DocCount() + // simple heuristic to detect a mismatch between the index and the bugs + count, err := index.DocCount() if err != nil { return err } @@ -100,26 +95,6 @@ func (c *RepoCache) loadBugCache() error { return nil } -func (c *RepoCache) createBleveIndex() error { - blevePath := searchCacheDirPath(c.repo) - - _ = os.RemoveAll(blevePath) - - mapping := bleve.NewIndexMapping() - mapping.DefaultAnalyzer = "en" - - dir := searchCacheDirPath(c.repo) - - bleveIndex, err := bleve.New(dir, mapping) - if err != nil { - return err - } - - c.searchCache = bleveIndex - - return nil -} - // write will serialize on disk the bug cache file func (c *RepoCache) writeBugCache() error { c.muBug.RLock() @@ -287,12 +262,12 @@ func (c *RepoCache) resolveBugMatcher(f func(*BugExcerpt) bool) (entity.Id, erro } // QueryBugs return the id of all Bug matching the given Query -func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { +func (c *RepoCache) QueryBugs(q *query.Query) ([]entity.Id, error) { c.muBug.RLock() defer c.muBug.RUnlock() if q == nil { - return c.AllBugsIds() + return c.AllBugsIds(), nil } matcher := compileMatcher(q.Filters) @@ -313,9 +288,15 @@ func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { bleveQuery := bleve.NewQueryStringQuery(strings.Join(terms, " ")) bleveSearch := bleve.NewSearchRequest(bleveQuery) - searchResults, err := c.searchCache.Search(bleveSearch) + + index, err := c.repo.GetBleveIndex("bug") + if err != nil { + return nil, err + } + + searchResults, err := index.Search(bleveSearch) if err != nil { - panic("bleve search failed") + return nil, err } for _, hit := range searchResults.Hits { @@ -341,7 +322,7 @@ func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { case query.OrderByEdit: sorter = BugsByEditTime(filtered) default: - panic("missing sort type") + return nil, errors.New("missing sort type") } switch q.OrderDirection { @@ -350,7 +331,7 @@ func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { case query.OrderDescending: sorter = sort.Reverse(sorter) default: - panic("missing sort direction") + return nil, errors.New("missing sort direction") } sort.Sort(sorter) @@ -361,7 +342,7 @@ func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { result[i] = val.Id } - return result + return result, nil } // AllBugsIds return all known bug ids @@ -504,7 +485,12 @@ func (c *RepoCache) addBugToSearchIndex(snap *bug.Snapshot) error { searchableBug.Text = append(searchableBug.Text, snap.Title) - err := c.searchCache.Index(snap.Id().String(), searchableBug) + index, err := c.repo.GetBleveIndex("bug") + if err != nil { + return err + } + + err = index.Index(snap.Id().String(), searchableBug) if err != nil { return err } diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go index 1c5c41d217787ae86cedaedb360026bb3e1ab3eb..bd06e84db518c4ec7fc476fb7398d255d62189d5 100644 --- a/cache/repo_cache_test.go +++ b/cache/repo_cache_test.go @@ -73,7 +73,9 @@ func TestCache(t *testing.T) { // Querying q, err := query.Parse("status:open author:descartes sort:edit-asc") require.NoError(t, err) - require.Len(t, cache.QueryBugs(q), 2) + res, err := cache.QueryBugs(q) + require.NoError(t, err) + require.Len(t, res, 2) // Close require.NoError(t, cache.Close()) diff --git a/commands/ls.go b/commands/ls.go index f6d654b1ba6df2a3d99407bca44a53ceda9f01b1..327fd37f53da0418be2d05338da7b11b6dea5a20 100644 --- a/commands/ls.go +++ b/commands/ls.go @@ -110,7 +110,10 @@ func runLs(env *Env, opts lsOptions, args []string) error { return err } - allIds := env.backend.QueryBugs(q) + allIds, err := env.backend.QueryBugs(q) + if err != nil { + return err + } bugExcerpt := make([]*cache.BugExcerpt, len(allIds)) for i, id := range allIds { diff --git a/identity/identity_test.go b/identity/identity_test.go index baf933c8336df64f677cc636015d50b08d0ffe8c..82e58b01a91eca8f81956a0010ca0a86959ab81b 100644 --- a/identity/identity_test.go +++ b/identity/identity_test.go @@ -273,10 +273,10 @@ func TestIdentityRemove(t *testing.T) { remoteB := repository.CreateGoGitTestRepo(true) defer repository.CleanupTestRepos(repo, remoteA, remoteB) - err := repo.AddRemote("remoteA", "file://"+remoteA.GetPath()) + err := repo.AddRemote("remoteA", remoteA.GetLocalRemote()) require.NoError(t, err) - err = repo.AddRemote("remoteB", "file://"+remoteB.GetPath()) + err = repo.AddRemote("remoteB", remoteB.GetLocalRemote()) require.NoError(t, err) // generate an identity for testing diff --git a/repository/git.go b/repository/git.go index 2348f5d57e9140d2df8b568e2304f154418c4dd3..993e6cc6365e9f5652ec5adee2b4c21a68a01373 100644 --- a/repository/git.go +++ b/repository/git.go @@ -9,6 +9,7 @@ import ( "strings" "sync" + "github.com/blevesearch/bleve" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/osfs" @@ -30,12 +31,15 @@ type GitRepo struct { clocksMutex sync.Mutex clocks map[string]lamport.Clock + indexesMutex sync.Mutex + indexes map[string]bleve.Index + keyring Keyring } -// NewGitRepo determines if the given working directory is inside of a git repository, +// OpenGitRepo determines if the given working directory is inside of a git repository, // and returns the corresponding GitRepo instance if it is. -func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { +func OpenGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { k, err := defaultKeyring() if err != nil { return nil, err @@ -45,6 +49,7 @@ func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { gitCli: gitCli{path: path}, path: path, clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), keyring: k, } @@ -84,9 +89,10 @@ func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { // InitGitRepo create a new empty git repo at the given path func InitGitRepo(path string) (*GitRepo, error) { repo := &GitRepo{ - gitCli: gitCli{path: path}, - path: path + "/.git", - clocks: make(map[string]lamport.Clock), + gitCli: gitCli{path: path}, + path: path + "/.git", + clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), } _, err := repo.runGitCommand("init", path) @@ -100,9 +106,10 @@ func InitGitRepo(path string) (*GitRepo, error) { // InitBareGitRepo create a new --bare empty git repo at the given path func InitBareGitRepo(path string) (*GitRepo, error) { repo := &GitRepo{ - gitCli: gitCli{path: path}, - path: path, - clocks: make(map[string]lamport.Clock), + gitCli: gitCli{path: path}, + path: path, + clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), } _, err := repo.runGitCommand("init", "--bare", path) @@ -113,6 +120,17 @@ func InitBareGitRepo(path string) (*GitRepo, error) { return repo, nil } +func (repo *GitRepo) Close() error { + var firstErr error + for _, index := range repo.indexes { + err := index.Close() + if err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} + // LocalConfig give access to the repository scoped configuration func (repo *GitRepo) LocalConfig() Config { return newGitConfig(repo.gitCli, false) @@ -183,6 +201,58 @@ func (repo *GitRepo) LocalStorage() billy.Filesystem { return osfs.New(repo.path) } +// GetBleveIndex return a bleve.Index that can be used to index documents +func (repo *GitRepo) GetBleveIndex(name string) (bleve.Index, error) { + repo.indexesMutex.Lock() + defer repo.indexesMutex.Unlock() + + if index, ok := repo.indexes[name]; ok { + return index, nil + } + + path := filepath.Join(repo.path, "indexes", name) + + index, err := bleve.Open(path) + if err == nil { + repo.indexes[name] = index + return index, nil + } + + err = os.MkdirAll(path, os.ModeDir) + if err != nil { + return nil, err + } + + mapping := bleve.NewIndexMapping() + mapping.DefaultAnalyzer = "en" + + index, err = bleve.New(path, mapping) + if err != nil { + return nil, err + } + + repo.indexes[name] = index + + return index, nil +} + +// ClearBleveIndex will wipe the given index +func (repo *GitRepo) ClearBleveIndex(name string) error { + repo.indexesMutex.Lock() + defer repo.indexesMutex.Unlock() + + path := filepath.Join(repo.path, "indexes", name) + + err := os.RemoveAll(path) + if err != nil { + return err + } + + delete(repo.indexes, name) + + return nil +} + // FetchRefs fetch git refs from a remote func (repo *GitRepo) FetchRefs(remote, refSpec string) (string, error) { stdout, err := repo.runGitCommand("fetch", remote, refSpec) diff --git a/repository/gogit.go b/repository/gogit.go index 741982aaddc91a2a5ddecb927bc33a12c9eaf6f3..74fe3fc52f729f30af6b5459298e2c4d21330b84 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "github.com/blevesearch/bleve" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/osfs" gogit "github.com/go-git/go-git/v5" @@ -33,6 +34,9 @@ type GoGitRepo struct { clocksMutex sync.Mutex clocks map[string]lamport.Clock + indexesMutex sync.Mutex + indexes map[string]bleve.Index + keyring Keyring localStorage billy.Filesystem } @@ -58,6 +62,7 @@ func OpenGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) r: r, path: path, clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), keyring: k, localStorage: osfs.New(filepath.Join(path, "git-bug")), } @@ -97,6 +102,7 @@ func InitGoGitRepo(path string) (*GoGitRepo, error) { r: r, path: filepath.Join(path, ".git"), clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), keyring: k, localStorage: osfs.New(filepath.Join(path, ".git", "git-bug")), }, nil @@ -118,6 +124,7 @@ func InitBareGoGitRepo(path string) (*GoGitRepo, error) { r: r, path: path, clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), keyring: k, localStorage: osfs.New(filepath.Join(path, "git-bug")), }, nil @@ -179,6 +186,17 @@ func isGitDir(path string) (bool, error) { return true, nil } +func (repo *GoGitRepo) Close() error { + var firstErr error + for _, index := range repo.indexes { + err := index.Close() + if err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} + // LocalConfig give access to the repository scoped configuration func (repo *GoGitRepo) LocalConfig() Config { return newGoGitLocalConfig(repo.r) @@ -274,6 +292,64 @@ func (repo *GoGitRepo) LocalStorage() billy.Filesystem { return repo.localStorage } +// GetBleveIndex return a bleve.Index that can be used to index documents +func (repo *GoGitRepo) GetBleveIndex(name string) (bleve.Index, error) { + repo.indexesMutex.Lock() + defer repo.indexesMutex.Unlock() + + if index, ok := repo.indexes[name]; ok { + return index, nil + } + + path := filepath.Join(repo.path, "git-bug", "indexes", name) + + index, err := bleve.Open(path) + if err == nil { + repo.indexes[name] = index + return index, nil + } + + err = os.MkdirAll(path, os.ModePerm) + if err != nil { + return nil, err + } + + mapping := bleve.NewIndexMapping() + mapping.DefaultAnalyzer = "en" + + index, err = bleve.New(path, mapping) + if err != nil { + return nil, err + } + + repo.indexes[name] = index + + return index, nil +} + +// ClearBleveIndex will wipe the given index +func (repo *GoGitRepo) ClearBleveIndex(name string) error { + repo.indexesMutex.Lock() + defer repo.indexesMutex.Unlock() + + path := filepath.Join(repo.path, "indexes", name) + + err := os.RemoveAll(path) + if err != nil { + return err + } + + if index, ok := repo.indexes[name]; ok { + err = index.Close() + if err != nil { + return err + } + delete(repo.indexes, name) + } + + return nil +} + // FetchRefs fetch git refs from a remote func (repo *GoGitRepo) FetchRefs(remote string, refSpec string) (string, error) { buf := bytes.NewBuffer(nil) diff --git a/repository/mock_repo.go b/repository/mock_repo.go index 02e5010f39a6a25973a7b25a511ba2b32fb516fe..8a1724ef1c9c546a380d3a90fcc78fac39bff876 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/99designs/keyring" + "github.com/blevesearch/bleve" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" @@ -22,16 +23,20 @@ type mockRepoForTest struct { *mockRepoKeyring *mockRepoCommon *mockRepoStorage + *mockRepoBleve *mockRepoData *mockRepoClock } +func (m *mockRepoForTest) Close() error { return nil } + func NewMockRepoForTest() *mockRepoForTest { return &mockRepoForTest{ mockRepoConfig: NewMockRepoConfig(), mockRepoKeyring: NewMockRepoKeyring(), mockRepoCommon: NewMockRepoCommon(), mockRepoStorage: NewMockRepoStorage(), + mockRepoBleve: newMockRepoBleve(), mockRepoData: NewMockRepoData(), mockRepoClock: NewMockRepoClock(), } @@ -126,6 +131,48 @@ func (m *mockRepoStorage) LocalStorage() billy.Filesystem { return m.localFs } +var _ RepoBleve = &mockRepoBleve{} + +type mockRepoBleve struct { + indexesMutex sync.Mutex + indexes map[string]bleve.Index +} + +func newMockRepoBleve() *mockRepoBleve { + return &mockRepoBleve{ + indexes: make(map[string]bleve.Index), + } +} + +func (m *mockRepoBleve) GetBleveIndex(name string) (bleve.Index, error) { + m.indexesMutex.Lock() + defer m.indexesMutex.Unlock() + + if index, ok := m.indexes[name]; ok { + return index, nil + } + + mapping := bleve.NewIndexMapping() + mapping.DefaultAnalyzer = "en" + + index, err := bleve.NewMemOnly(mapping) + if err != nil { + return nil, err + } + + m.indexes[name] = index + + return index, nil +} + +func (m *mockRepoBleve) ClearBleveIndex(name string) error { + m.indexesMutex.Lock() + defer m.indexesMutex.Unlock() + + delete(m.indexes, name) + return nil +} + var _ RepoData = &mockRepoData{} type commit struct { diff --git a/repository/repo.go b/repository/repo.go index d8fe44e63a3772afb379cf2469cf447e87577ef7..eb9296d4829704332960b586a0f54612fd992a3f 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -4,6 +4,7 @@ package repository import ( "errors" + "github.com/blevesearch/bleve" "github.com/go-git/go-billy/v5" "github.com/MichaelMure/git-bug/util/lamport" @@ -23,6 +24,9 @@ type Repo interface { RepoCommon RepoData RepoStorage + RepoBleve + + Close() error } type RepoCommonStorage interface { @@ -69,11 +73,21 @@ type RepoCommon interface { GetRemotes() (map[string]string, error) } +// RepoStorage give access to the filesystem type RepoStorage interface { // LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug LocalStorage() billy.Filesystem } +// RepoBleve give access to Bleve to implement full-text search indexes. +type RepoBleve interface { + // GetBleveIndex return a bleve.Index that can be used to index documents + GetBleveIndex(name string) (bleve.Index, error) + + // ClearBleveIndex will wipe the given index + ClearBleveIndex(name string) error +} + // RepoData give access to the git data storage type RepoData interface { // FetchRefs fetch git refs from a remote diff --git a/termui/bug_table.go b/termui/bug_table.go index d3a187dbefb6476a9283a5c7aeddb24317c09308..94185a2999e8457ac1e43b1f74bdf32557a9594f 100644 --- a/termui/bug_table.go +++ b/termui/bug_table.go @@ -237,7 +237,11 @@ func (bt *bugTable) disable(g *gocui.Gui) error { } func (bt *bugTable) paginate(max int) error { - bt.allIds = bt.repo.QueryBugs(bt.query) + var err error + bt.allIds, err = bt.repo.QueryBugs(bt.query) + if err != nil { + return err + } return bt.doPaginate(max) } From 52ef5e96f543ca242a9e1e8f2917492eb3d3fb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 8 Dec 2020 15:03:56 +0100 Subject: [PATCH 8/9] add github actions --- .github/workflows/go.yml | 41 ++++++++++++++++++++++++++++++++++++ .github/workflows/nodejs.yml | 38 +++++++++++++++++++++++++++++++++ bridge/github/config_test.go | 3 +++ 3 files changed, 82 insertions(+) create mode 100644 .github/workflows/go.yml create mode 100644 .github/workflows/nodejs.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000000000000000000000000000000000000..3ce22f3a0f0ef9cc926c5fdb764aa6cc4bd94145 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,41 @@ +name: Go build and test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + build: + + strategy: + matrix: + go-version: [1.13.x, 1.14.x, 1.15.x] + platform: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.platform }} + + steps: + + - name: Set up Go ${{ matrix.node-version }} + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Check out code + uses: actions/checkout@v2 + + - name: Build + run: make + + - name: Test + run: make test + env: + GITHUB_TEST_USER: ${{ secrets._GITHUB_TEST_USER }} + GITHUB_TOKEN_ADMIN: ${{ secrets._GITHUB_TOKEN_ADMIN }} + GITHUB_TOKEN_PRIVATE: ${{ secrets._GITHUB_TOKEN_PRIVATE }} + GITHUB_TOKEN_PUBLIC: ${{ secrets._GITHUB_TOKEN_PUBLIC }} + GITLAB_API_TOKEN: ${{ secrets.GITLAB_API_TOKEN }} + GITLAB_PROJECT_ID: ${{ secrets.GITLAB_PROJECT_ID }} diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000000000000000000000000000000000000..d7a69c8edaa7029c300dddecf5124a29db5612cc --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,38 @@ +name: Node.js build and test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +defaults: + run: + working-directory: webui + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x] + + steps: + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Check out code + uses: actions/checkout@v2 + + - name: Install + run: make install + + - name: Build + run: make build + + - name: Test + run: make test diff --git a/bridge/github/config_test.go b/bridge/github/config_test.go index eecb1aa8fd2b784a0b8c8735bc888b2eb54ca110..01907435d4aaca08071fef9a308203a0a69a9158 100644 --- a/bridge/github/config_test.go +++ b/bridge/github/config_test.go @@ -103,6 +103,9 @@ func TestValidateUsername(t *testing.T) { if env := os.Getenv("TRAVIS"); env == "true" { t.Skip("Travis environment: avoiding non authenticated requests") } + if _, has := os.LookupEnv("CI"); has { + t.Skip("Github action environment: avoiding non authenticated requests") + } tests := []struct { name string From 8128bb79b0db9023a98c356e4e173d846057c577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 8 Dec 2020 15:09:58 +0100 Subject: [PATCH 9/9] repo: close before deleting --- repository/git.go | 5 +++++ repository/gogit.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/repository/git.go b/repository/git.go index 993e6cc6365e9f5652ec5adee2b4c21a68a01373..bc9d87723691ce4e04f151d227ad59218ea29574 100644 --- a/repository/git.go +++ b/repository/git.go @@ -488,6 +488,11 @@ func (repo *GitRepo) GetLocalRemote() string { // EraseFromDisk delete this repository entirely from the disk func (repo *GitRepo) EraseFromDisk() error { + err := repo.Close() + if err != nil { + return err + } + path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git")) // fmt.Println("Cleaning repo:", path) diff --git a/repository/gogit.go b/repository/gogit.go index 74fe3fc52f729f30af6b5459298e2c4d21330b84..bdac259de85ebda97275ee7fc402c9e9c0023782 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -734,6 +734,11 @@ func (repo *GoGitRepo) GetLocalRemote() string { // EraseFromDisk delete this repository entirely from the disk func (repo *GoGitRepo) EraseFromDisk() error { + err := repo.Close() + if err != nil { + return err + } + path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git")) // fmt.Println("Cleaning repo:", path)