all repos

mugit @ 7a05bd0

馃惍 git server that your cow will love
10 files changed, 748 insertions(+), 6 deletions(-)
git: add all helpers 

git: some renaming
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-01-21 01:49:14 +0200
Authored at: 2026-01-18 19:45:08 +0200
Change ID: qpvvmyqqypuryxsyqlzzrnlpolunqqwq
Parent: b407568
M go.mod
路路路
        2
        2
         

      
        3
        3
         go 1.25.3

      
        4
        4
         

      
        5
        
        -require gopkg.in/yaml.v2 v2.4.0

      
        
        5
        +require (

      
        
        6
        +	github.com/bluekeyes/go-gitdiff v0.8.1

      
        
        7
        +	github.com/go-git/go-git/v5 v5.16.4

      
        
        8
        +	gopkg.in/yaml.v2 v2.4.0

      
        
        9
        +)

      
        
        10
        +

      
        
        11
        +require (

      
        
        12
        +	dario.cat/mergo v1.0.0 // indirect

      
        
        13
        +	github.com/Microsoft/go-winio v0.6.2 // indirect

      
        
        14
        +	github.com/ProtonMail/go-crypto v1.1.6 // indirect

      
        
        15
        +	github.com/cloudflare/circl v1.6.1 // indirect

      
        
        16
        +	github.com/cyphar/filepath-securejoin v0.4.1 // indirect

      
        
        17
        +	github.com/emirpasic/gods v1.18.1 // indirect

      
        
        18
        +	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect

      
        
        19
        +	github.com/go-git/go-billy/v5 v5.6.2 // indirect

      
        
        20
        +	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect

      
        
        21
        +	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect

      
        
        22
        +	github.com/kevinburke/ssh_config v1.2.0 // indirect

      
        
        23
        +	github.com/pjbgf/sha1cd v0.3.2 // indirect

      
        
        24
        +	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect

      
        
        25
        +	github.com/skeema/knownhosts v1.3.1 // indirect

      
        
        26
        +	github.com/xanzy/ssh-agent v0.3.3 // indirect

      
        
        27
        +	golang.org/x/crypto v0.37.0 // indirect

      
        
        28
        +	golang.org/x/net v0.39.0 // indirect

      
        
        29
        +	golang.org/x/sys v0.32.0 // indirect

      
        
        30
        +	gopkg.in/warnings.v0 v0.1.2 // indirect

      
        
        31
        +)

      
M go.sum
路路路
        1
        
        -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

      
        
        1
        +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=

      
        
        2
        +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=

      
        
        3
        +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=

      
        
        4
        +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=

      
        
        5
        +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=

      
        
        6
        +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=

      
        
        7
        +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=

      
        
        8
        +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=

      
        
        9
        +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=

      
        
        10
        +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=

      
        
        11
        +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=

      
        
        12
        +github.com/bluekeyes/go-gitdiff v0.8.1 h1:lL1GofKMywO17c0lgQmJYcKek5+s8X6tXVNOLxy4smI=

      
        
        13
        +github.com/bluekeyes/go-gitdiff v0.8.1/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE=

      
        
        14
        +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=

      
        
        15
        +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=

      
        
        16
        +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=

      
        
        17
        +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=

      
        
        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/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=

      
        
        22
        +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=

      
        
        23
        +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=

      
        
        24
        +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=

      
        
        25
        +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=

      
        
        26
        +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=

      
        
        27
        +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=

      
        
        28
        +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=

      
        
        29
        +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=

      
        
        30
        +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=

      
        
        31
        +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=

      
        
        32
        +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=

      
        
        33
        +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=

      
        
        34
        +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=

      
        
        35
        +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=

      
        
        36
        +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=

      
        
        37
        +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=

      
        
        38
        +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=

      
        
        39
        +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=

      
        
        40
        +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=

      
        
        41
        +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=

      
        
        42
        +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=

      
        
        43
        +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=

      
        
        44
        +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=

      
        
        45
        +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=

      
        
        46
        +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=

      
        
        47
        +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=

      
        
        48
        +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=

      
        
        49
        +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=

      
        
        50
        +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=

      
        
        51
        +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=

      
        
        52
        +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=

      
        
        53
        +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=

      
        
        54
        +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

      
        
        55
        +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

      
        
        56
        +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

      
        
        57
        +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

      
        
        58
        +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=

      
        
        59
        +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=

      
        
        60
        +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=

      
        
        61
        +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=

      
        
        62
        +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=

      
        
        63
        +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=

      
        
        64
        +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=

      
        
        65
        +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

      
        
        66
        +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=

      
        
        67
        +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

      
        
        68
        +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=

      
        
        69
        +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=

      
        
        70
        +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=

      
        
        71
        +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=

      
        
        72
        +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=

      
        
        73
        +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=

      
        
        74
        +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=

      
        
        75
        +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=

      
        
        76
        +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=

      
        
        77
        +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=

      
        
        78
        +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=

      
        
        79
        +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=

      
        
        80
        +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

      
        
        81
        +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

      
        
        82
        +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

      
        
        83
        +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

      
        
        84
        +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

      
        
        85
        +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

      
        
        86
        +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=

      
        
        87
        +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

      
        
        88
        +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

      
        
        89
        +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=

      
        
        90
        +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=

      
        
        91
        +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

      
        
        92
        +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=

      
        
        93
        +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=

      
        
        94
        +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

      
        2
        95
         gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

      
        
        96
        +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

      
        
        97
        +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

      
        
        98
        +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

      
        
        99
        +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=

      
        
        100
        +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=

      
        
        101
        +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

      
        3
        102
         gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

      
        4
        103
         gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

      
        
        104
        +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

      
        
        105
        +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

      
A internal/git/diff.go
路路路
        
        1
        +package git

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"fmt"

      
        
        5
        +	"strings"

      
        
        6
        +

      
        
        7
        +	"github.com/bluekeyes/go-gitdiff/gitdiff"

      
        
        8
        +	"github.com/go-git/go-git/v5/plumbing/object"

      
        
        9
        +)

      
        
        10
        +

      
        
        11
        +type TextFragment struct {

      
        
        12
        +	Header string

      
        
        13
        +	Lines  []gitdiff.Line

      
        
        14
        +}

      
        
        15
        +

      
        
        16
        +type Diff struct {

      
        
        17
        +	Name struct {

      
        
        18
        +		Old string

      
        
        19
        +		New string

      
        
        20
        +	}

      
        
        21
        +	TextFragments []TextFragment

      
        
        22
        +	IsBinary      bool

      
        
        23
        +	IsNew         bool

      
        
        24
        +	IsDelete      bool

      
        
        25
        +}

      
        
        26
        +

      
        
        27
        +type NiceDiff struct {

      
        
        28
        +	Diff   []Diff

      
        
        29
        +	Commit struct {

      
        
        30
        +		Message string

      
        
        31
        +		Author  object.Signature

      
        
        32
        +		This    string

      
        
        33
        +		Parent  string

      
        
        34
        +	}

      
        
        35
        +	Stat struct {

      
        
        36
        +		FilesChanged int

      
        
        37
        +		Insertions   int

      
        
        38
        +		Deletions    int

      
        
        39
        +	}

      
        
        40
        +}

      
        
        41
        +

      
        
        42
        +func (g *Repo) Diff() (*NiceDiff, error) {

      
        
        43
        +	c, err := g.r.CommitObject(g.h)

      
        
        44
        +	if err != nil {

      
        
        45
        +		return nil, fmt.Errorf("commit object: %w", err)

      
        
        46
        +	}

      
        
        47
        +

      
        
        48
        +	patch, parent, err := g.getPatch(c)

      
        
        49
        +	if err != nil {

      
        
        50
        +		return nil, err

      
        
        51
        +	}

      
        
        52
        +

      
        
        53
        +	diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String()))

      
        
        54
        +	if err != nil {

      
        
        55
        +		return nil, fmt.Errorf("parsing diff: %w", err)

      
        
        56
        +	}

      
        
        57
        +

      
        
        58
        +	nd := NiceDiff{}

      
        
        59
        +	nd.Commit.Message = c.Message

      
        
        60
        +	nd.Commit.Author = c.Author

      
        
        61
        +	nd.Commit.This = c.Hash.String()

      
        
        62
        +	nd.Commit.Parent = getParentHash(parent)

      
        
        63
        +	nd.Stat.FilesChanged = len(diffs)

      
        
        64
        +

      
        
        65
        +	nd.Diff = make([]Diff, len(diffs))

      
        
        66
        +	for i, d := range diffs {

      
        
        67
        +		diff := &nd.Diff[i]

      
        
        68
        +		diff.Name.New = d.NewName

      
        
        69
        +		diff.Name.Old = d.OldName

      
        
        70
        +		diff.IsBinary = d.IsBinary

      
        
        71
        +		diff.IsNew = d.IsNew

      
        
        72
        +		diff.IsDelete = d.IsDelete

      
        
        73
        +

      
        
        74
        +		for _, tf := range d.TextFragments {

      
        
        75
        +			diff.TextFragments = append(diff.TextFragments, TextFragment{

      
        
        76
        +				Header: tf.Header(),

      
        
        77
        +				Lines:  tf.Lines,

      
        
        78
        +			})

      
        
        79
        +			for _, l := range tf.Lines {

      
        
        80
        +				switch l.Op {

      
        
        81
        +				case gitdiff.OpAdd:

      
        
        82
        +					nd.Stat.Insertions += 1

      
        
        83
        +				case gitdiff.OpDelete:

      
        
        84
        +					nd.Stat.Deletions += 1

      
        
        85
        +				}

      
        
        86
        +			}

      
        
        87
        +		}

      
        
        88
        +	}

      
        
        89
        +	return &nd, nil

      
        
        90
        +}

      
        
        91
        +

      
        
        92
        +func (g *Repo) getPatch(c *object.Commit) (*object.Patch, *object.Commit, error) {

      
        
        93
        +	commitTree, err := c.Tree()

      
        
        94
        +	if err != nil {

      
        
        95
        +		return nil, nil, err

      
        
        96
        +	}

      
        
        97
        +

      
        
        98
        +	var parentTree *object.Tree

      
        
        99
        +	var parent *object.Commit

      
        
        100
        +

      
        
        101
        +	if c.NumParents() != 0 {

      
        
        102
        +		parent, err = c.Parents().Next()

      
        
        103
        +		if err != nil {

      
        
        104
        +			return nil, nil, err

      
        
        105
        +		}

      
        
        106
        +		parentTree, err = parent.Tree()

      
        
        107
        +		if err != nil {

      
        
        108
        +			return nil, nil, err

      
        
        109
        +		}

      
        
        110
        +	} else {

      
        
        111
        +		parentTree = &object.Tree{}

      
        
        112
        +	}

      
        
        113
        +

      
        
        114
        +	patch, err := parentTree.Patch(commitTree)

      
        
        115
        +	if err != nil {

      
        
        116
        +		return nil, nil, fmt.Errorf("patch: %w", err)

      
        
        117
        +	}

      
        
        118
        +

      
        
        119
        +	return patch, parent, nil

      
        
        120
        +}

      
        
        121
        +

      
        
        122
        +func getParentHash(parent *object.Commit) string {

      
        
        123
        +	if parent == nil || parent.Hash.IsZero() {

      
        
        124
        +		return ""

      
        
        125
        +	}

      
        
        126
        +	return parent.Hash.String()

      
        
        127
        +}

      
D internal/git/git.go
路路路
        1
        
        -package git

      
        2
        
        -

      
        3
        
        -

      
        4
        
        -

      
A internal/git/gitservice/gitservice.go
路路路
        
        1
        +package gitservice

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"bytes"

      
        
        5
        +	"fmt"

      
        
        6
        +	"io"

      
        
        7
        +	"os/exec"

      
        
        8
        +	"strings"

      
        
        9
        +	"syscall"

      
        
        10
        +)

      
        
        11
        +

      
        
        12
        +// Thanks https://git.icyphox.sh/legit/blob/master/git/service/service.go

      
        
        13
        +

      
        
        14
        +// InfoRefs executes git-upload-pack --advertise-refs for smart-HTTP discovery.

      
        
        15
        +func InfoRefs(dir string, out io.Writer) error {

      
        
        16
        +	cmd := exec.Command("git",

      
        
        17
        +		"upload-pack",

      
        
        18
        +		"--stateless-rpc",

      
        
        19
        +		"--advertise-refs",

      
        
        20
        +		".")

      
        
        21
        +	cmd.Dir = dir

      
        
        22
        +	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

      
        
        23
        +	stdoutPipe, _ := cmd.StdoutPipe()

      
        
        24
        +	cmd.Stderr = cmd.Stdout

      
        
        25
        +	defer stdoutPipe.Close()

      
        
        26
        +

      
        
        27
        +	if err := cmd.Start(); err != nil {

      
        
        28
        +		return fmt.Errorf("start git-upload-pack: %w", err)

      
        
        29
        +	}

      
        
        30
        +

      
        
        31
        +	if err := packLine(out, "# service=git-upload-pack\n"); err != nil {

      
        
        32
        +		return fmt.Errorf("write pack line: %w", err)

      
        
        33
        +	}

      
        
        34
        +

      
        
        35
        +	if err := packFlush(out); err != nil {

      
        
        36
        +		return fmt.Errorf("flush pack: %w", err)

      
        
        37
        +	}

      
        
        38
        +

      
        
        39
        +	var buf bytes.Buffer

      
        
        40
        +	if _, err := io.Copy(&buf, stdoutPipe); err != nil {

      
        
        41
        +		return fmt.Errorf("copy stdout to buffer: %w", err)

      
        
        42
        +	}

      
        
        43
        +

      
        
        44
        +	if err := cmd.Wait(); err != nil {

      
        
        45
        +		var out strings.Builder

      
        
        46
        +		io.Copy(&out, &buf)

      
        
        47
        +		return fmt.Errorf("git-upload-pack: %w: %s", err, out.String())

      
        
        48
        +	}

      
        
        49
        +

      
        
        50
        +	if _, err := io.Copy(out, &buf); err != nil {

      
        
        51
        +		return fmt.Errorf("copy buffer to output: %w", err)

      
        
        52
        +	}

      
        
        53
        +

      
        
        54
        +	return nil

      
        
        55
        +}

      
        
        56
        +

      
        
        57
        +// UploadPack executes git-upload-pack for smart-HTTP git fetch/clone.

      
        
        58
        +func UploadPack(dir string, in io.Reader, out io.Writer) error {

      
        
        59
        +	cmd := exec.Command("git",

      
        
        60
        +		"-c", "uploadpack.allowFilter=true",

      
        
        61
        +		"upload-pack",

      
        
        62
        +		"--stateless-rpc",

      
        
        63
        +		".")

      
        
        64
        +	cmd.Dir = dir

      
        
        65
        +	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

      
        
        66
        +

      
        
        67
        +	stdoutPipe, _ := cmd.StdoutPipe()

      
        
        68
        +	cmd.Stderr = cmd.Stdout

      
        
        69
        +	defer stdoutPipe.Close()

      
        
        70
        +

      
        
        71
        +	stdinPipe, err := cmd.StdinPipe()

      
        
        72
        +	if err != nil {

      
        
        73
        +		return fmt.Errorf("stdin pipe: %w", err)

      
        
        74
        +	}

      
        
        75
        +	defer stdinPipe.Close()

      
        
        76
        +

      
        
        77
        +	if err := cmd.Start(); err != nil {

      
        
        78
        +		return fmt.Errorf("start git-upload-pack: %w", err)

      
        
        79
        +	}

      
        
        80
        +

      
        
        81
        +	if _, err := io.Copy(stdinPipe, in); err != nil {

      
        
        82
        +		return fmt.Errorf("copy to stdin: %w", err)

      
        
        83
        +	}

      
        
        84
        +	stdinPipe.Close()

      
        
        85
        +

      
        
        86
        +	if _, err := io.Copy(out, stdoutPipe); err != nil {

      
        
        87
        +		return fmt.Errorf("copy stdout: %w", err)

      
        
        88
        +	}

      
        
        89
        +

      
        
        90
        +	if err := cmd.Wait(); err != nil {

      
        
        91
        +		return fmt.Errorf("git-upload-pack: %w", err)

      
        
        92
        +	}

      
        
        93
        +

      
        
        94
        +	return nil

      
        
        95
        +}

      
        
        96
        +

      
        
        97
        +func packLine(w io.Writer, s string) error {

      
        
        98
        +	_, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s)

      
        
        99
        +	return err

      
        
        100
        +}

      
        
        101
        +

      
        
        102
        +func packFlush(w io.Writer) error {

      
        
        103
        +	_, err := fmt.Fprint(w, "0000")

      
        
        104
        +	return err

      
        
        105
        +}

      
A internal/git/repo.go
路路路
        
        1
        +package git

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"fmt"

      
        
        5
        +	"sort"

      
        
        6
        +

      
        
        7
        +	"github.com/go-git/go-git/v5"

      
        
        8
        +	"github.com/go-git/go-git/v5/plumbing"

      
        
        9
        +	"github.com/go-git/go-git/v5/plumbing/object"

      
        
        10
        +)

      
        
        11
        +

      
        
        12
        +// Thanks https://git.icyphox.sh/legit/blob/master/git/git.go

      
        
        13
        +

      
        
        14
        +type Repo struct {

      
        
        15
        +	path string

      
        
        16
        +	r    *git.Repository

      
        
        17
        +	h    plumbing.Hash

      
        
        18
        +}

      
        
        19
        +

      
        
        20
        +// Open opens a git repository at path. If ref is empty, HEAD is used.

      
        
        21
        +func Open(path string, ref string) (*Repo, error) {

      
        
        22
        +	var err error

      
        
        23
        +	g := Repo{}

      
        
        24
        +	g.path = path

      
        
        25
        +	g.r, err = git.PlainOpen(path)

      
        
        26
        +	if err != nil {

      
        
        27
        +		return nil, fmt.Errorf("opening %s: %w", path, err)

      
        
        28
        +	}

      
        
        29
        +

      
        
        30
        +	if ref == "" {

      
        
        31
        +		head, err := g.r.Head()

      
        
        32
        +		if err != nil {

      
        
        33
        +			return nil, fmt.Errorf("getting head of %s: %w", path, err)

      
        
        34
        +		}

      
        
        35
        +		g.h = head.Hash()

      
        
        36
        +	} else {

      
        
        37
        +		hash, err := g.r.ResolveRevision(plumbing.Revision(ref))

      
        
        38
        +		if err != nil {

      
        
        39
        +			return nil, fmt.Errorf("resolving rev %s for %s: %w", ref, path, err)

      
        
        40
        +		}

      
        
        41
        +		g.h = *hash

      
        
        42
        +	}

      
        
        43
        +	return &g, nil

      
        
        44
        +}

      
        
        45
        +

      
        
        46
        +func (g *Repo) Commits() ([]*object.Commit, error) {

      
        
        47
        +	ci, err := g.r.Log(&git.LogOptions{From: g.h})

      
        
        48
        +	if err != nil {

      
        
        49
        +		return nil, fmt.Errorf("commits from ref: %w", err)

      
        
        50
        +	}

      
        
        51
        +

      
        
        52
        +	commits := []*object.Commit{}

      
        
        53
        +	ci.ForEach(func(c *object.Commit) error {

      
        
        54
        +		commits = append(commits, c)

      
        
        55
        +		return nil

      
        
        56
        +	})

      
        
        57
        +

      
        
        58
        +	return commits, nil

      
        
        59
        +}

      
        
        60
        +

      
        
        61
        +func (g *Repo) LastCommit() (*object.Commit, error) {

      
        
        62
        +	c, err := g.r.CommitObject(g.h)

      
        
        63
        +	if err != nil {

      
        
        64
        +		return nil, fmt.Errorf("last commit: %w", err)

      
        
        65
        +	}

      
        
        66
        +	return c, nil

      
        
        67
        +}

      
        
        68
        +

      
        
        69
        +func (g *Repo) FileContent(path string) (string, error) {

      
        
        70
        +	c, err := g.r.CommitObject(g.h)

      
        
        71
        +	if err != nil {

      
        
        72
        +		return "", fmt.Errorf("commit object: %w", err)

      
        
        73
        +	}

      
        
        74
        +

      
        
        75
        +	tree, err := c.Tree()

      
        
        76
        +	if err != nil {

      
        
        77
        +		return "", fmt.Errorf("file tree: %w", err)

      
        
        78
        +	}

      
        
        79
        +

      
        
        80
        +	file, err := tree.File(path)

      
        
        81
        +	if err != nil {

      
        
        82
        +		return "", err

      
        
        83
        +	}

      
        
        84
        +

      
        
        85
        +	isbin, _ := file.IsBinary()

      
        
        86
        +	if !isbin {

      
        
        87
        +		return file.Contents()

      
        
        88
        +	} else {

      
        
        89
        +		return "Not displaying binary file", nil

      
        
        90
        +	}

      
        
        91
        +}

      
        
        92
        +

      
        
        93
        +func (g *Repo) Tags() ([]*TagReference, error) {

      
        
        94
        +	iter, err := g.r.Tags()

      
        
        95
        +	if err != nil {

      
        
        96
        +		return nil, fmt.Errorf("tag objects: %w", err)

      
        
        97
        +	}

      
        
        98
        +

      
        
        99
        +	tags := make([]*TagReference, 0)

      
        
        100
        +	if err := iter.ForEach(func(ref *plumbing.Reference) error {

      
        
        101
        +		obj, err := g.r.TagObject(ref.Hash())

      
        
        102
        +		switch err {

      
        
        103
        +		case nil:

      
        
        104
        +			tags = append(tags, &TagReference{

      
        
        105
        +				ref: ref,

      
        
        106
        +				tag: obj,

      
        
        107
        +			})

      
        
        108
        +		case plumbing.ErrObjectNotFound:

      
        
        109
        +			tags = append(tags, &TagReference{

      
        
        110
        +				ref: ref,

      
        
        111
        +			})

      
        
        112
        +		default:

      
        
        113
        +			return err

      
        
        114
        +		}

      
        
        115
        +		return nil

      
        
        116
        +	}); err != nil {

      
        
        117
        +		return nil, err

      
        
        118
        +	}

      
        
        119
        +

      
        
        120
        +	tagList := &TagList{r: g.r, refs: tags}

      
        
        121
        +	sort.Sort(tagList)

      
        
        122
        +	return tags, nil

      
        
        123
        +}

      
        
        124
        +

      
        
        125
        +func (g *Repo) Branches() ([]*plumbing.Reference, error) {

      
        
        126
        +	bi, err := g.r.Branches()

      
        
        127
        +	if err != nil {

      
        
        128
        +		return nil, fmt.Errorf("branch: %w", err)

      
        
        129
        +	}

      
        
        130
        +

      
        
        131
        +	branches := []*plumbing.Reference{}

      
        
        132
        +	err = bi.ForEach(func(ref *plumbing.Reference) error {

      
        
        133
        +		branches = append(branches, ref)

      
        
        134
        +		return nil

      
        
        135
        +	})

      
        
        136
        +	return branches, err

      
        
        137
        +}

      
        
        138
        +

      
        
        139
        +func (g *Repo) Description() (string, error) {

      
        
        140
        +	c, err := g.r.Config()

      
        
        141
        +	if err != nil {

      
        
        142
        +		return "", fmt.Errorf("failed to read config: %w", err)

      
        
        143
        +	}

      
        
        144
        +

      
        
        145
        +	s := c.Raw.Section("mugit")

      
        
        146
        +	return s.Options.Get("description"), nil

      
        
        147
        +}

      
        
        148
        +

      
        
        149
        +func (g *Repo) IsPrivate() (bool, error) {

      
        
        150
        +	c, err := g.r.Config()

      
        
        151
        +	if err != nil {

      
        
        152
        +		return false, fmt.Errorf("failed to read config: %w", err)

      
        
        153
        +	}

      
        
        154
        +

      
        
        155
        +	s := c.Raw.Section("mugit")

      
        
        156
        +	return s.Options.Get("private") == "true", nil

      
        
        157
        +}

      
        
        158
        +

      
        
        159
        +func (g *Repo) IsGoMod() bool {

      
        
        160
        +	_, err := g.FileContent("go.mod")

      
        
        161
        +	return err == nil

      
        
        162
        +}

      
        
        163
        +

      
        
        164
        +func (g *Repo) FindMasterBranch(masters []string) (string, error) {

      
        
        165
        +	for _, b := range masters {

      
        
        166
        +		if _, err := g.r.ResolveRevision(plumbing.Revision(b)); err == nil {

      
        
        167
        +			return b, nil

      
        
        168
        +		}

      
        
        169
        +	}

      
        
        170
        +	return "", fmt.Errorf("unable to find master branch")

      
        
        171
        +}

      
A internal/git/tags.go
路路路
        
        1
        +package git

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"time"

      
        
        5
        +

      
        
        6
        +	"github.com/go-git/go-git/v5"

      
        
        7
        +	"github.com/go-git/go-git/v5/plumbing"

      
        
        8
        +	"github.com/go-git/go-git/v5/plumbing/object"

      
        
        9
        +)

      
        
        10
        +

      
        
        11
        +type TagList struct {

      
        
        12
        +	refs []*TagReference

      
        
        13
        +	r    *git.Repository

      
        
        14
        +}

      
        
        15
        +

      
        
        16
        +// TagReference represents both lightweight and annotated tags.

      
        
        17
        +// Lightweight tags contain only a reference.

      
        
        18
        +// Annotated tags contain both a reference and tag metadata.

      
        
        19
        +type TagReference struct {

      
        
        20
        +	ref *plumbing.Reference

      
        
        21
        +	tag *object.Tag

      
        
        22
        +}

      
        
        23
        +

      
        
        24
        +func (t *TagReference) Name() string {

      
        
        25
        +	return t.ref.Name().Short()

      
        
        26
        +}

      
        
        27
        +

      
        
        28
        +func (t *TagReference) Message() string {

      
        
        29
        +	if t.tag != nil {

      
        
        30
        +		return t.tag.Message

      
        
        31
        +	}

      
        
        32
        +	return ""

      
        
        33
        +}

      
        
        34
        +

      
        
        35
        +func (t *TagList) Len() int {

      
        
        36
        +	return len(t.refs)

      
        
        37
        +}

      
        
        38
        +

      
        
        39
        +func (t *TagList) Swap(i, j int) {

      
        
        40
        +	t.refs[i], t.refs[j] = t.refs[j], t.refs[i]

      
        
        41
        +}

      
        
        42
        +

      
        
        43
        +// Less sorting tags in reverse chronological order

      
        
        44
        +func (t *TagList) Less(i, j int) bool {

      
        
        45
        +	return t.getDate(i).After(t.getDate(j))

      
        
        46
        +}

      
        
        47
        +

      
        
        48
        +func (t *TagList) getDate(i int) time.Time {

      
        
        49
        +	if t.refs[i].tag != nil {

      
        
        50
        +		return t.refs[i].tag.Tagger.When

      
        
        51
        +	}

      
        
        52
        +	c, err := t.r.CommitObject(t.refs[i].ref.Hash())

      
        
        53
        +	if err != nil {

      
        
        54
        +		return time.Now()

      
        
        55
        +	}

      
        
        56
        +	return c.Committer.When

      
        
        57
        +}

      
A internal/git/tree.go
路路路
        
        1
        +package git

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"fmt"

      
        
        5
        +

      
        
        6
        +	"github.com/go-git/go-git/v5/plumbing/object"

      
        
        7
        +)

      
        
        8
        +

      
        
        9
        +type NiceTree struct {

      
        
        10
        +	Name      string

      
        
        11
        +	Mode      string

      
        
        12
        +	Size      int64

      
        
        13
        +	IsFile    bool

      
        
        14
        +	IsSubtree bool

      
        
        15
        +}

      
        
        16
        +

      
        
        17
        +func makeNiceTree(t *object.Tree) []NiceTree {

      
        
        18
        +	nts := []NiceTree{}

      
        
        19
        +	for _, e := range t.Entries {

      
        
        20
        +		mode, _ := e.Mode.ToOSFileMode()

      
        
        21
        +		sz, _ := t.Size(e.Name)

      
        
        22
        +		nts = append(nts, NiceTree{

      
        
        23
        +			Name:   e.Name,

      
        
        24
        +			Mode:   mode.String(),

      
        
        25
        +			IsFile: e.Mode.IsFile(),

      
        
        26
        +			Size:   sz,

      
        
        27
        +		})

      
        
        28
        +	}

      
        
        29
        +	return nts

      
        
        30
        +}

      
        
        31
        +

      
        
        32
        +func (g *Repo) FileTree(path string) ([]NiceTree, error) {

      
        
        33
        +	c, err := g.r.CommitObject(g.h)

      
        
        34
        +	if err != nil {

      
        
        35
        +		return nil, fmt.Errorf("commit object: %w", err)

      
        
        36
        +	}

      
        
        37
        +

      
        
        38
        +	files := []NiceTree{}

      
        
        39
        +	tree, err := c.Tree()

      
        
        40
        +	if err != nil {

      
        
        41
        +		return nil, fmt.Errorf("file tree: %w", err)

      
        
        42
        +	}

      
        
        43
        +

      
        
        44
        +	if path == "" {

      
        
        45
        +		files = makeNiceTree(tree)

      
        
        46
        +	} else {

      
        
        47
        +		o, err := tree.FindEntry(path)

      
        
        48
        +		if err != nil {

      
        
        49
        +			return nil, err

      
        
        50
        +		}

      
        
        51
        +

      
        
        52
        +		if !o.Mode.IsFile() {

      
        
        53
        +			subtree, err := tree.Tree(path)

      
        
        54
        +			if err != nil {

      
        
        55
        +				return nil, err

      
        
        56
        +			}

      
        
        57
        +			files = makeNiceTree(subtree)

      
        
        58
        +		}

      
        
        59
        +	}

      
        
        60
        +

      
        
        61
        +	return files, nil

      
        
        62
        +}

      
M internal/handlers/git.go
路路路
        1
        1
         package handlers

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"compress/gzip"

      
        
        5
        +	"io"

      
        
        6
        +	"log/slog"

      
        
        7
        +	"net/http"

      
        
        8
        +	"path/filepath"

      
        
        9
        +

      
        
        10
        +	"olexsmir.xyz/mugit/internal/git/gitservice"

      
        
        11
        +)

      
        
        12
        +

      
        
        13
        +func (h *handlers) infoRefs(w http.ResponseWriter, r *http.Request) {

      
        
        14
        +	// TODO: 404 for private repos

      
        
        15
        +

      
        
        16
        +	name := filepath.Clean(r.PathValue("name"))

      
        
        17
        +

      
        
        18
        +	w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")

      
        
        19
        +	w.WriteHeader(http.StatusOK)

      
        
        20
        +

      
        
        21
        +	if err := gitservice.InfoRefs(

      
        
        22
        +		filepath.Join(h.c.Repo.Dir, name), // FIXME: use securejoin

      
        
        23
        +		w,

      
        
        24
        +	); err != nil {

      
        
        25
        +		slog.Error("git: info/refs", "err", err)

      
        
        26
        +		return

      
        
        27
        +	}

      
        
        28
        +}

      
        
        29
        +

      
        
        30
        +func (h *handlers) uploadPack(w http.ResponseWriter, r *http.Request) {

      
        
        31
        +	// TODO: 404 for private repos

      
        
        32
        +

      
        
        33
        +	name := filepath.Clean(r.PathValue("name"))

      
        
        34
        +

      
        
        35
        +	w.Header().Set("content-type", "application/x-git-upload-pack-result")

      
        
        36
        +	w.Header().Set("Connection", "Keep-Alive")

      
        
        37
        +	w.Header().Set("Transfer-Encoding", "chunked")

      
        
        38
        +	w.WriteHeader(http.StatusOK)

      
        
        39
        +

      
        
        40
        +	reader := io.Reader(r.Body)

      
        
        41
        +	if r.Header.Get("Content-Encoding") == "gzip" {

      
        
        42
        +		gr, err := gzip.NewReader(r.Body)

      
        
        43
        +		if err != nil {

      
        
        44
        +			slog.Error("git: gzip reader", "err", err)

      
        
        45
        +			return

      
        
        46
        +		}

      
        
        47
        +		defer gr.Close()

      
        
        48
        +		reader = gr

      
        
        49
        +	}

      
        
        50
        +

      
        
        51
        +	if err := gitservice.UploadPack(

      
        
        52
        +		filepath.Join(h.c.Repo.Dir, name),

      
        
        53
        +		reader,

      
        
        54
        +		newFlushWriter(w),

      
        
        55
        +	); err != nil {

      
        
        56
        +		slog.Error("git: upload-pack", "err", err)

      
        
        57
        +		return

      
        
        58
        +	}

      
        
        59
        +}

      
        
        60
        +

      
        
        61
        +type flushWriter struct {

      
        
        62
        +	w io.Writer

      
        
        63
        +	f http.Flusher

      
        
        64
        +}

      
        
        65
        +

      
        
        66
        +func newFlushWriter(w http.ResponseWriter) io.Writer {

      
        
        67
        +	f, _ := w.(http.Flusher)

      
        
        68
        +	return &flushWriter{w: w, f: f}

      
        
        69
        +}

      
        
        70
        +

      
        
        71
        +func (fw *flushWriter) Write(p []byte) (int, error) {

      
        
        72
        +	n, err := fw.w.Write(p)

      
        
        73
        +	if fw.f != nil {

      
        
        74
        +		fw.f.Flush()

      
        
        75
        +	}

      
        
        76
        +	return n, err

      
        
        77
        +}

      
M internal/handlers/handles.go
路路路
        24
        24
         

      
        25
        25
         	return mux

      
        26
        26
         }

      
        
        27
        +

      
        
        28
        +// multiplex if request smells like gitprotocol-http(5) passes it  to the git

      
        
        29
        +// http service renders templates.

      
        
        30
        +func (h *handlers) multiplex(w http.ResponseWriter, r *http.Request) {

      
        
        31
        +	if r.URL.RawQuery == "service=git-receive-pack" {

      
        
        32
        +		w.WriteHeader(http.StatusBadRequest)

      
        
        33
        +		w.Write([]byte("http pushing isn't supported"))

      
        
        34
        +		return

      
        
        35
        +	}

      
        
        36
        +

      
        
        37
        +	path := r.PathValue("rest")

      
        
        38
        +	if path == "info/refs" &&

      
        
        39
        +		r.URL.RawQuery == "service=git-upload-pack" &&

      
        
        40
        +		r.Method == "GET" {

      
        
        41
        +		h.infoRefs(w, r)

      
        
        42
        +	} else if path == "git-upload-pack" && r.Method == "POST" {

      
        
        43
        +		h.uploadPack(w, r)

      
        
        44
        +	} else if r.Method == "GET" {

      
        
        45
        +		h.repoIndex(w, r)

      
        
        46
        +	}

      
        
        47
        +}