mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-23 04:52:40 +00:00
Set gist URL and title via push options (#216)
This commit is contained in:
parent
db6d6a5eba
commit
86ad88fb09
8 changed files with 131 additions and 78 deletions
|
@ -4,6 +4,19 @@ Opengist has support for a few [Git push options](https://git-scm.com/docs/git-p
|
||||||
|
|
||||||
These options are passed to `git push` command and can be used to change the metadata of a gist.
|
These options are passed to `git push` command and can be used to change the metadata of a gist.
|
||||||
|
|
||||||
|
## Set URL
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git push -o url=mygist # Will set the URL to https://opengist.example.com/user/mygist
|
||||||
|
```
|
||||||
|
|
||||||
|
## Change title
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git push -o title=Gist123
|
||||||
|
git push -o title="My Gist 123"
|
||||||
|
```
|
||||||
|
|
||||||
## Change visibility
|
## Change visibility
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
|
"github.com/thomiceli/opengist/internal/utils"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -13,7 +14,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func PostReceive(in io.Reader, out, er io.Writer) error {
|
func PostReceive(in io.Reader, out, er io.Writer) error {
|
||||||
|
var outputSb strings.Builder
|
||||||
|
newGist := false
|
||||||
opts := pushOptions()
|
opts := pushOptions()
|
||||||
|
gistUrl := os.Getenv("OPENGIST_REPOSITORY_URL_INTERNAL")
|
||||||
|
validator := utils.NewValidator()
|
||||||
|
|
||||||
scanner := bufio.NewScanner(in)
|
scanner := bufio.NewScanner(in)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
|
@ -29,9 +35,7 @@ func PostReceive(in io.Reader, out, er io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldrev == BaseHash {
|
if oldrev == BaseHash {
|
||||||
_, _ = fmt.Fprintf(out, "\nYour new repository has been created here: %s\n\n", os.Getenv("OPENGIST_REPOSITORY_URL_INTERNAL"))
|
newGist = true
|
||||||
_, _ = fmt.Fprintln(out, "If you want to keep working with your gist, you could set the remote URL via:")
|
|
||||||
_, _ = fmt.Fprintf(out, "git remote set-url origin %s\n\n", os.Getenv("OPENGIST_REPOSITORY_URL_INTERNAL"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +47,22 @@ func PostReceive(in io.Reader, out, er io.Writer) error {
|
||||||
|
|
||||||
if slices.Contains([]string{"public", "unlisted", "private"}, opts["visibility"]) {
|
if slices.Contains([]string{"public", "unlisted", "private"}, opts["visibility"]) {
|
||||||
gist.Private, _ = db.ParseVisibility(opts["visibility"])
|
gist.Private, _ = db.ParseVisibility(opts["visibility"])
|
||||||
_, _ = fmt.Fprintf(out, "\nGist visibility set to %s\n\n", opts["visibility"])
|
outputSb.WriteString(fmt.Sprintf("Gist visibility set to %s\n\n", opts["visibility"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts["url"] != "" && validator.Var(opts["url"], "max=32,alphanumdashorempty") == nil {
|
||||||
|
gist.URL = opts["url"]
|
||||||
|
lastIndex := strings.LastIndex(gistUrl, "/")
|
||||||
|
gistUrl = gistUrl[:lastIndex+1] + gist.URL
|
||||||
|
if !newGist {
|
||||||
|
outputSb.WriteString(fmt.Sprintf("Gist URL set to %s. Set the Git remote URL via:\n", gistUrl))
|
||||||
|
outputSb.WriteString(fmt.Sprintf("git remote set-url origin %s\n\n", gistUrl))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts["title"] != "" && validator.Var(opts["title"], "max=250") == nil {
|
||||||
|
gist.Title = opts["title"]
|
||||||
|
outputSb.WriteString(fmt.Sprintf("Gist title set to \"%s\"\n\n", opts["title"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasNoCommits, err := git.HasNoCommits(gist.User.Username, gist.Uuid); err != nil {
|
if hasNoCommits, err := git.HasNoCommits(gist.User.Username, gist.Uuid); err != nil {
|
||||||
|
@ -65,6 +84,17 @@ func PostReceive(in io.Reader, out, er io.Writer) error {
|
||||||
|
|
||||||
gist.AddInIndex()
|
gist.AddInIndex()
|
||||||
|
|
||||||
|
if newGist {
|
||||||
|
outputSb.WriteString(fmt.Sprintf("Your new gist has been created here: %s\n", gistUrl))
|
||||||
|
outputSb.WriteString("If you want to keep working with your gist, you could set the Git remote URL via:\n")
|
||||||
|
outputSb.WriteString(fmt.Sprintf("git remote set-url origin %s\n\n", gistUrl))
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStr := outputSb.String()
|
||||||
|
if outputStr != "" {
|
||||||
|
_, _ = fmt.Fprint(out, "\n"+outputStr)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
74
internal/utils/validator.go
Normal file
74
internal/utils/validator.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpengistValidator struct {
|
||||||
|
v *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewValidator() *OpengistValidator {
|
||||||
|
v := validator.New()
|
||||||
|
_ = v.RegisterValidation("notreserved", validateReservedKeywords)
|
||||||
|
_ = v.RegisterValidation("alphanumdash", validateAlphaNumDash)
|
||||||
|
_ = v.RegisterValidation("alphanumdashorempty", validateAlphaNumDashOrEmpty)
|
||||||
|
return &OpengistValidator{v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *OpengistValidator) Validate(i interface{}) error {
|
||||||
|
return cv.v.Struct(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *OpengistValidator) Var(field interface{}, tag string) error {
|
||||||
|
return cv.v.Var(field, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidationMessages(err *error) string {
|
||||||
|
errs := (*err).(validator.ValidationErrors)
|
||||||
|
messages := make([]string, len(errs))
|
||||||
|
for i, e := range errs {
|
||||||
|
switch e.Tag() {
|
||||||
|
case "max":
|
||||||
|
messages[i] = e.Field() + " is too long"
|
||||||
|
case "required":
|
||||||
|
messages[i] = e.Field() + " should not be empty"
|
||||||
|
case "excludes":
|
||||||
|
messages[i] = e.Field() + " should not include a sub directory"
|
||||||
|
case "alphanum":
|
||||||
|
messages[i] = e.Field() + " should only contain alphanumeric characters"
|
||||||
|
case "alphanumdash":
|
||||||
|
case "alphanumdashorempty":
|
||||||
|
messages[i] = e.Field() + " should only contain alphanumeric characters and dashes"
|
||||||
|
case "min":
|
||||||
|
messages[i] = "Not enough " + e.Field()
|
||||||
|
case "notreserved":
|
||||||
|
messages[i] = "Invalid " + e.Field()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(messages, " ; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateReservedKeywords(fl validator.FieldLevel) bool {
|
||||||
|
name := fl.Field().String()
|
||||||
|
|
||||||
|
restrictedNames := map[string]struct{}{}
|
||||||
|
for _, restrictedName := range []string{"assets", "register", "login", "logout", "settings", "admin-panel", "all", "search", "init", "healthcheck"} {
|
||||||
|
restrictedNames[restrictedName] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the name is not in the restricted names, it is valid
|
||||||
|
_, ok := restrictedNames[name]
|
||||||
|
return !ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAlphaNumDash(fl validator.FieldLevel) bool {
|
||||||
|
return regexp.MustCompile(`^[a-zA-Z0-9-]+$`).MatchString(fl.Field().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAlphaNumDashOrEmpty(fl validator.FieldLevel) bool {
|
||||||
|
return regexp.MustCompile(`^$|^[a-zA-Z0-9-]+$`).MatchString(fl.Field().String())
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/thomiceli/opengist/internal/utils"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -63,7 +64,7 @@ func processRegister(ctx echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.Validate(dto); err != nil {
|
if err := ctx.Validate(dto); err != nil {
|
||||||
addFlash(ctx, validationMessages(&err), "error")
|
addFlash(ctx, utils.ValidationMessages(&err), "error")
|
||||||
return html(ctx, "auth_form.html")
|
return html(ctx, "auth_form.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
"github.com/thomiceli/opengist/internal/index"
|
"github.com/thomiceli/opengist/internal/index"
|
||||||
"github.com/thomiceli/opengist/internal/render"
|
"github.com/thomiceli/opengist/internal/render"
|
||||||
|
"github.com/thomiceli/opengist/internal/utils"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -539,7 +540,7 @@ func processCreate(ctx echo.Context) error {
|
||||||
|
|
||||||
err = ctx.Validate(dto)
|
err = ctx.Validate(dto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
addFlash(ctx, validationMessages(&err), "error")
|
addFlash(ctx, utils.ValidationMessages(&err), "error")
|
||||||
if isCreate {
|
if isCreate {
|
||||||
return html(ctx, "create.html")
|
return html(ctx, "create.html")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/thomiceli/opengist/internal/index"
|
"github.com/thomiceli/opengist/internal/index"
|
||||||
|
"github.com/thomiceli/opengist/internal/utils"
|
||||||
htmlpkg "html"
|
htmlpkg "html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
@ -205,7 +206,7 @@ func NewServer(isDev bool) *Server {
|
||||||
|
|
||||||
e.Use(sessionInit)
|
e.Use(sessionInit)
|
||||||
|
|
||||||
e.Validator = NewValidator()
|
e.Validator = utils.NewValidator()
|
||||||
|
|
||||||
if !dev {
|
if !dev {
|
||||||
parseManifestEntries()
|
parseManifestEntries()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
|
"github.com/thomiceli/opengist/internal/utils"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -73,7 +74,7 @@ func sshKeysProcess(ctx echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.Validate(dto); err != nil {
|
if err := ctx.Validate(dto); err != nil {
|
||||||
addFlash(ctx, validationMessages(&err), "error")
|
addFlash(ctx, utils.ValidationMessages(&err), "error")
|
||||||
return redirect(ctx, "/settings")
|
return redirect(ctx, "/settings")
|
||||||
}
|
}
|
||||||
key := dto.ToSSHKey()
|
key := dto.ToSSHKey()
|
||||||
|
@ -126,7 +127,7 @@ func passwordProcess(ctx echo.Context) error {
|
||||||
dto.Username = user.Username
|
dto.Username = user.Username
|
||||||
|
|
||||||
if err := ctx.Validate(dto); err != nil {
|
if err := ctx.Validate(dto); err != nil {
|
||||||
addFlash(ctx, validationMessages(&err), "error")
|
addFlash(ctx, utils.ValidationMessages(&err), "error")
|
||||||
return html(ctx, "settings.html")
|
return html(ctx, "settings.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +155,7 @@ func usernameProcess(ctx echo.Context) error {
|
||||||
dto.Password = user.Password
|
dto.Password = user.Password
|
||||||
|
|
||||||
if err := ctx.Validate(dto); err != nil {
|
if err := ctx.Validate(dto); err != nil {
|
||||||
addFlash(ctx, validationMessages(&err), "error")
|
addFlash(ctx, utils.ValidationMessages(&err), "error")
|
||||||
return redirect(ctx, "/settings")
|
return redirect(ctx, "/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
|
@ -16,7 +15,6 @@ import (
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -129,72 +127,6 @@ func loadSettings(ctx echo.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpengistValidator struct {
|
|
||||||
v *validator.Validate
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewValidator() *OpengistValidator {
|
|
||||||
v := validator.New()
|
|
||||||
_ = v.RegisterValidation("notreserved", validateReservedKeywords)
|
|
||||||
_ = v.RegisterValidation("alphanumdash", validateAlphaNumDash)
|
|
||||||
_ = v.RegisterValidation("alphanumdashorempty", validateAlphaNumDashOrEmpty)
|
|
||||||
return &OpengistValidator{v}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cv *OpengistValidator) Validate(i interface{}) error {
|
|
||||||
if err := cv.v.Struct(i); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validationMessages(err *error) string {
|
|
||||||
errs := (*err).(validator.ValidationErrors)
|
|
||||||
messages := make([]string, len(errs))
|
|
||||||
for i, e := range errs {
|
|
||||||
switch e.Tag() {
|
|
||||||
case "max":
|
|
||||||
messages[i] = e.Field() + " is too long"
|
|
||||||
case "required":
|
|
||||||
messages[i] = e.Field() + " should not be empty"
|
|
||||||
case "excludes":
|
|
||||||
messages[i] = e.Field() + " should not include a sub directory"
|
|
||||||
case "alphanum":
|
|
||||||
messages[i] = e.Field() + " should only contain alphanumeric characters"
|
|
||||||
case "alphanumdash":
|
|
||||||
case "alphanumdashorempty":
|
|
||||||
messages[i] = e.Field() + " should only contain alphanumeric characters and dashes"
|
|
||||||
case "min":
|
|
||||||
messages[i] = "Not enough " + e.Field()
|
|
||||||
case "notreserved":
|
|
||||||
messages[i] = "Invalid " + e.Field()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(messages, " ; ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateReservedKeywords(fl validator.FieldLevel) bool {
|
|
||||||
name := fl.Field().String()
|
|
||||||
|
|
||||||
restrictedNames := map[string]struct{}{}
|
|
||||||
for _, restrictedName := range []string{"assets", "register", "login", "logout", "settings", "admin-panel", "all", "search", "init", "healthcheck"} {
|
|
||||||
restrictedNames[restrictedName] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the name is not in the restricted names, it is valid
|
|
||||||
_, ok := restrictedNames[name]
|
|
||||||
return !ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateAlphaNumDash(fl validator.FieldLevel) bool {
|
|
||||||
return regexp.MustCompile(`^[a-zA-Z0-9-]+$`).MatchString(fl.Field().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateAlphaNumDashOrEmpty(fl validator.FieldLevel) bool {
|
|
||||||
return regexp.MustCompile(`^$|^[a-zA-Z0-9-]+$`).MatchString(fl.Field().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPage(ctx echo.Context) int {
|
func getPage(ctx echo.Context) int {
|
||||||
page := ctx.QueryParam("page")
|
page := ctx.QueryParam("page")
|
||||||
if page == "" {
|
if page == "" {
|
||||||
|
|
Loading…
Reference in a new issue