mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-23 04:52:40 +00:00
Added forks
This commit is contained in:
parent
8630f647c6
commit
1607d8fc93
9 changed files with 235 additions and 86 deletions
|
@ -10,28 +10,21 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetRepositoryPath(user string, gist string) (string, error) {
|
func RepositoryPath(user string, gist string) string {
|
||||||
return filepath.Join(config.GetHomeDir(), "repos", strings.ToLower(user), gist), nil
|
return filepath.Join(config.GetHomeDir(), "repos", strings.ToLower(user), gist)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTmpRepositoryPath(gistId string) (string, error) {
|
func TmpRepositoryPath(gistId string) string {
|
||||||
dirname, err := getTmpRepositoriesPath()
|
dirname := TmpRepositoriesPath()
|
||||||
if err != nil {
|
return filepath.Join(dirname, gistId)
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return filepath.Join(dirname, gistId), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTmpRepositoriesPath() (string, error) {
|
func TmpRepositoriesPath() string {
|
||||||
return filepath.Join(config.GetHomeDir(), "tmp", "repos"), nil
|
return filepath.Join(config.GetHomeDir(), "tmp", "repos")
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitRepository(user string, gist string) error {
|
func InitRepository(user string, gist string) error {
|
||||||
repositoryPath, err := GetRepositoryPath(user, gist)
|
repositoryPath := RepositoryPath(user, gist)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"git",
|
"git",
|
||||||
|
@ -40,7 +33,7 @@ func InitRepository(user string, gist string) error {
|
||||||
repositoryPath,
|
repositoryPath,
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err = cmd.Output()
|
_, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -69,10 +62,7 @@ func InitRepository(user string, gist string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetNumberOfCommitsOfRepository(user string, gist string) (string, error) {
|
func GetNumberOfCommitsOfRepository(user string, gist string) (string, error) {
|
||||||
repositoryPath, err := GetRepositoryPath(user, gist)
|
repositoryPath := RepositoryPath(user, gist)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"git",
|
"git",
|
||||||
|
@ -87,10 +77,7 @@ func GetNumberOfCommitsOfRepository(user string, gist string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFilesOfRepository(user string, gist string, commit string) ([]string, error) {
|
func GetFilesOfRepository(user string, gist string, commit string) ([]string, error) {
|
||||||
repositoryPath, err := GetRepositoryPath(user, gist)
|
repositoryPath := RepositoryPath(user, gist)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"git",
|
"git",
|
||||||
|
@ -110,10 +97,7 @@ func GetFilesOfRepository(user string, gist string, commit string) ([]string, er
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFileContent(user string, gist string, commit string, filename string) (string, error) {
|
func GetFileContent(user string, gist string, commit string, filename string) (string, error) {
|
||||||
repositoryPath, err := GetRepositoryPath(user, gist)
|
repositoryPath := RepositoryPath(user, gist)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"git",
|
"git",
|
||||||
|
@ -128,10 +112,7 @@ func GetFileContent(user string, gist string, commit string, filename string) (s
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLog(user string, gist string, skip string) (string, error) {
|
func GetLog(user string, gist string, skip string) (string, error) {
|
||||||
repositoryPath, err := GetRepositoryPath(user, gist)
|
repositoryPath := RepositoryPath(user, gist)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"git",
|
"git",
|
||||||
|
@ -156,19 +137,13 @@ func GetLog(user string, gist string, skip string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CloneTmp(user string, gist string, gistTmpId string) error {
|
func CloneTmp(user string, gist string, gistTmpId string) error {
|
||||||
repositoryPath, err := GetRepositoryPath(user, gist)
|
repositoryPath := RepositoryPath(user, gist)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpPath, err := getTmpRepositoriesPath()
|
tmpPath := TmpRepositoriesPath()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpRepositoryPath := path.Join(tmpPath, gistTmpId)
|
tmpRepositoryPath := path.Join(tmpPath, gistTmpId)
|
||||||
|
|
||||||
err = os.RemoveAll(tmpRepositoryPath)
|
err := os.RemoveAll(tmpRepositoryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -191,25 +166,33 @@ func CloneTmp(user string, gist string, gistTmpId string) error {
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetFileContent(gistTmpId string, filename string, content string) error {
|
func ForkClone(userSrc string, gistSrc string, userDst string, gistDst string) error {
|
||||||
repositoryPath, err := getTmpRepositoryPath(gistTmpId)
|
repositoryPathSrc := RepositoryPath(userSrc, gistSrc)
|
||||||
if err != nil {
|
repositoryPathDst := RepositoryPath(userDst, gistDst)
|
||||||
|
|
||||||
|
cmd := exec.Command("git", "clone", "--bare", repositoryPathSrc, repositoryPathDst)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd = exec.Command("git", "config", "user.name", userDst)
|
||||||
|
cmd.Dir = repositoryPathDst
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetFileContent(gistTmpId string, filename string, content string) error {
|
||||||
|
repositoryPath := TmpRepositoryPath(gistTmpId)
|
||||||
|
|
||||||
return os.WriteFile(filepath.Join(repositoryPath, filename), []byte(content), 0644)
|
return os.WriteFile(filepath.Join(repositoryPath, filename), []byte(content), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddAll(gistTmpId string) error {
|
func AddAll(gistTmpId string) error {
|
||||||
tmpPath, err := getTmpRepositoryPath(gistTmpId)
|
tmpPath := TmpRepositoryPath(gistTmpId)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// in case of a change where only a file name has its case changed
|
// in case of a change where only a file name has its case changed
|
||||||
cmd := exec.Command("git", "rm", "-r", "--cached", "--ignore-unmatch", ".")
|
cmd := exec.Command("git", "rm", "-r", "--cached", "--ignore-unmatch", ".")
|
||||||
cmd.Dir = tmpPath
|
cmd.Dir = tmpPath
|
||||||
err = cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -222,27 +205,21 @@ func AddAll(gistTmpId string) error {
|
||||||
|
|
||||||
func Commit(gistTmpId string) error {
|
func Commit(gistTmpId string) error {
|
||||||
cmd := exec.Command("git", "commit", "--allow-empty", "-m", `"Opengist commit"`)
|
cmd := exec.Command("git", "commit", "--allow-empty", "-m", `"Opengist commit"`)
|
||||||
tmpPath, err := getTmpRepositoryPath(gistTmpId)
|
tmpPath := TmpRepositoryPath(gistTmpId)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd.Dir = tmpPath
|
cmd.Dir = tmpPath
|
||||||
|
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Push(gistTmpId string) error {
|
func Push(gistTmpId string) error {
|
||||||
tmpRepositoryPath, err := getTmpRepositoryPath(gistTmpId)
|
tmpRepositoryPath := TmpRepositoryPath(gistTmpId)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"git",
|
"git",
|
||||||
"push",
|
"push",
|
||||||
)
|
)
|
||||||
cmd.Dir = tmpRepositoryPath
|
cmd.Dir = tmpRepositoryPath
|
||||||
|
|
||||||
err = cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -255,10 +232,7 @@ func DeleteRepository(user string, gist string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateServerInfo(user string, gist string) error {
|
func UpdateServerInfo(user string, gist string) error {
|
||||||
repositoryPath, err := GetRepositoryPath(user, gist)
|
repositoryPath := RepositoryPath(user, gist)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("git", "update-server-info")
|
cmd := exec.Command("git", "update-server-info")
|
||||||
cmd.Dir = repositoryPath
|
cmd.Dir = repositoryPath
|
||||||
|
@ -266,10 +240,7 @@ func UpdateServerInfo(user string, gist string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RPCRefs(user string, gist string, service string) ([]byte, error) {
|
func RPCRefs(user string, gist string, service string) ([]byte, error) {
|
||||||
repositoryPath, err := GetRepositoryPath(user, gist)
|
repositoryPath := RepositoryPath(user, gist)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("git", service, "--stateless-rpc", "--advertise-refs", ".")
|
cmd := exec.Command("git", service, "--stateless-rpc", "--advertise-refs", ".")
|
||||||
cmd.Dir = repositoryPath
|
cmd.Dir = repositoryPath
|
||||||
|
|
|
@ -16,10 +16,13 @@ type Gist struct {
|
||||||
User User `validate:"-"`
|
User User `validate:"-"`
|
||||||
NbFiles int
|
NbFiles int
|
||||||
NbLikes int
|
NbLikes int
|
||||||
|
NbForks int
|
||||||
CreatedAt int64
|
CreatedAt int64
|
||||||
UpdatedAt int64
|
UpdatedAt int64
|
||||||
|
|
||||||
Likes []User `gorm:"many2many:likes;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
Likes []User `gorm:"many2many:likes;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||||
|
Forked *Gist `gorm:"foreignKey:ForkedID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
|
||||||
|
ForkedID uint
|
||||||
|
|
||||||
Files []File `gorm:"-" validate:"min=1,dive"`
|
Files []File `gorm:"-" validate:"min=1,dive"`
|
||||||
}
|
}
|
||||||
|
@ -40,7 +43,7 @@ type Commit struct {
|
||||||
|
|
||||||
func GetGist(user string, gistUuid string) (*Gist, error) {
|
func GetGist(user string, gistUuid string) (*Gist, error) {
|
||||||
gist := new(Gist)
|
gist := new(Gist)
|
||||||
err := db.Preload("User").
|
err := db.Preload("User").Preload("Forked.User").
|
||||||
Where("gists.uuid = ? AND users.username like ?", gistUuid, user).
|
Where("gists.uuid = ? AND users.username like ?", gistUuid, user).
|
||||||
Joins("join users on gists.user_id = users.id").
|
Joins("join users on gists.user_id = users.id").
|
||||||
First(&gist).Error
|
First(&gist).Error
|
||||||
|
@ -50,7 +53,7 @@ func GetGist(user string, gistUuid string) (*Gist, error) {
|
||||||
|
|
||||||
func GetGistByID(gistId string) (*Gist, error) {
|
func GetGistByID(gistId string) (*Gist, error) {
|
||||||
gist := new(Gist)
|
gist := new(Gist)
|
||||||
err := db.Preload("User").
|
err := db.Preload("User").Preload("Forked.User").
|
||||||
Where("gists.id = ?", gistId).
|
Where("gists.id = ?", gistId).
|
||||||
First(&gist).Error
|
First(&gist).Error
|
||||||
|
|
||||||
|
@ -59,7 +62,7 @@ func GetGistByID(gistId string) (*Gist, error) {
|
||||||
|
|
||||||
func GetAllGistsForCurrentUser(currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
|
func GetAllGistsForCurrentUser(currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
|
||||||
var gists []*Gist
|
var gists []*Gist
|
||||||
err := db.Preload("User").
|
err := db.Preload("User").Preload("Forked.User").
|
||||||
Where("gists.private = 0 or gists.user_id = ?", currentUserId).
|
Where("gists.private = 0 or gists.user_id = ?", currentUserId).
|
||||||
Limit(11).
|
Limit(11).
|
||||||
Offset(offset * 10).
|
Offset(offset * 10).
|
||||||
|
@ -82,7 +85,7 @@ func GetAllGists(offset int) ([]*Gist, error) {
|
||||||
|
|
||||||
func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
|
func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort string, order string) ([]*Gist, error) {
|
||||||
var gists []*Gist
|
var gists []*Gist
|
||||||
err := db.Preload("User").
|
err := db.Preload("User").Preload("Forked.User").
|
||||||
Where("users.username = ? and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", fromUser, currentUserId).
|
Where("users.username = ? and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", fromUser, currentUserId).
|
||||||
Joins("join users on gists.user_id = users.id").
|
Joins("join users on gists.user_id = users.id").
|
||||||
Limit(11).
|
Limit(11).
|
||||||
|
@ -94,11 +97,16 @@ func GetAllGistsFromUser(fromUser string, currentUserId uint, offset int, sort s
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateGist(gist *Gist) error {
|
func CreateGist(gist *Gist) error {
|
||||||
|
// avoids foreign key constraint error because the default value in the struct is 0
|
||||||
|
return db.Omit("forked_id").Create(&gist).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateForkedGist(gist *Gist) error {
|
||||||
return db.Create(&gist).Error
|
return db.Create(&gist).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateGist(gist *Gist) error {
|
func UpdateGist(gist *Gist) error {
|
||||||
return db.Save(&gist).Error
|
return db.Omit("forked_id").Save(&gist).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteGist(gist *Gist) error {
|
func DeleteGist(gist *Gist) error {
|
||||||
|
@ -112,16 +120,40 @@ func GistLastActiveNow(gistID uint) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppendUserLike(gist *Gist, user *User) error {
|
func AppendUserLike(gist *Gist, user *User) error {
|
||||||
db.Model(&gist).Omit("updated_at").Update("nb_likes", gist.NbLikes+1)
|
err := db.Model(&gist).Omit("updated_at").Update("nb_likes", gist.NbLikes+1).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return db.Model(&gist).Omit("updated_at").Association("Likes").Append(user)
|
return db.Model(&gist).Omit("updated_at").Association("Likes").Append(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveUserLike(gist *Gist, user *User) error {
|
func RemoveUserLike(gist *Gist, user *User) error {
|
||||||
db.Model(&gist).Omit("updated_at").Update("nb_likes", gist.NbLikes-1)
|
err := db.Model(&gist).Omit("updated_at").Update("nb_likes", gist.NbLikes-1).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return db.Model(&gist).Omit("updated_at").Association("Likes").Delete(user)
|
return db.Model(&gist).Omit("updated_at").Association("Likes").Delete(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUsersLikesForGists(gist *Gist, offset int) ([]*User, error) {
|
func IncrementGistForkCount(gist *Gist) error {
|
||||||
|
return db.Model(&gist).Omit("updated_at").Update("nb_forks", gist.NbForks+1).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecrementGistForkCount(gist *Gist) error {
|
||||||
|
return db.Model(&gist).Omit("updated_at").Update("nb_forks", gist.NbForks-1).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetForkedGist(gist *Gist, user *User) (*Gist, error) {
|
||||||
|
fork := new(Gist)
|
||||||
|
err := db.Preload("User").
|
||||||
|
Where("forked_id = ? and user_id = ?", gist.ID, user.ID).
|
||||||
|
First(&fork).Error
|
||||||
|
return fork, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUsersLikesForGist(gist *Gist, offset int) ([]*User, error) {
|
||||||
var users []*User
|
var users []*User
|
||||||
err := db.Model(&gist).
|
err := db.Model(&gist).
|
||||||
Where("gist_id = ?", gist.ID).
|
Where("gist_id = ?", gist.ID).
|
||||||
|
@ -131,6 +163,19 @@ func GetUsersLikesForGists(gist *Gist, offset int) ([]*User, error) {
|
||||||
return users, err
|
return users, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUsersForksForGist(gist *Gist, currentUserId uint, offset int) ([]*Gist, error) {
|
||||||
|
var gists []*Gist
|
||||||
|
err := db.Model(&gist).Preload("User").
|
||||||
|
Where("forked_id = ?", gist.ID).
|
||||||
|
Where("(gists.private = 0) or (gists.private = 1 and gists.user_id = ?)", currentUserId).
|
||||||
|
Limit(11).
|
||||||
|
Offset(offset * 10).
|
||||||
|
Order("updated_at desc").
|
||||||
|
Find(&gists).Error
|
||||||
|
|
||||||
|
return gists, err
|
||||||
|
}
|
||||||
|
|
||||||
func UserCanWrite(user *User, gist *Gist) bool {
|
func UserCanWrite(user *User, gist *Gist) bool {
|
||||||
return !(user == nil) && (gist.UserID == user.ID)
|
return !(user == nil) && (gist.UserID == user.ID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,11 +53,7 @@ func runGitCommand(ch ssh.Channel, gitCmd string, keyID uint) error {
|
||||||
|
|
||||||
_ = models.SSHKeyLastUsedNow(keyID)
|
_ = models.SSHKeyLastUsedNow(keyID)
|
||||||
|
|
||||||
repositoryPath, err := git.GetRepositoryPath(gist.User.Username, gist.Uuid)
|
repositoryPath := git.RepositoryPath(gist.User.Username, gist.Uuid)
|
||||||
if err != nil {
|
|
||||||
errorSsh("Failed to get repository path", err)
|
|
||||||
return errors.New("internal server error")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("git", verb, repositoryPath)
|
cmd := exec.Command("git", verb, repositoryPath)
|
||||||
cmd.Dir = repositoryPath
|
cmd.Dir = repositoryPath
|
||||||
|
|
|
@ -3,8 +3,10 @@ package web
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"gorm.io/gorm"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/url"
|
"net/url"
|
||||||
"opengist/internal/config"
|
"opengist/internal/config"
|
||||||
|
@ -413,6 +415,49 @@ func like(ctx echo.Context) error {
|
||||||
return redirect(ctx, redirectTo)
|
return redirect(ctx, redirectTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fork(ctx echo.Context) error {
|
||||||
|
var gist = getData(ctx, "gist").(*models.Gist)
|
||||||
|
currentUser := getUserLogged(ctx)
|
||||||
|
|
||||||
|
alreadyForked, err := models.GetForkedGist(gist, currentUser)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return errorRes(500, "Error checking if gist is already forked", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if alreadyForked.ID != 0 {
|
||||||
|
return redirect(ctx, "/"+alreadyForked.User.Username+"/"+alreadyForked.Uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
uuidGist, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return errorRes(500, "Error creating an UUID", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newGist := &models.Gist{
|
||||||
|
Uuid: strings.Replace(uuidGist.String(), "-", "", -1),
|
||||||
|
Title: gist.Title,
|
||||||
|
Preview: gist.Preview,
|
||||||
|
PreviewFilename: gist.PreviewFilename,
|
||||||
|
Description: gist.Description,
|
||||||
|
Private: gist.Private,
|
||||||
|
UserID: currentUser.ID,
|
||||||
|
ForkedID: gist.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = models.CreateForkedGist(newGist); err != nil {
|
||||||
|
return errorRes(500, "Error forking the gist in database", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = git.ForkClone(gist.User.Username, gist.Uuid, currentUser.Username, newGist.Uuid); err != nil {
|
||||||
|
return errorRes(500, "Error cloning the repository while forking", err)
|
||||||
|
}
|
||||||
|
if err = models.IncrementGistForkCount(gist); err != nil {
|
||||||
|
return errorRes(500, "Error incrementing the fork count", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect(ctx, "/"+currentUser.Username+"/"+newGist.Uuid)
|
||||||
|
}
|
||||||
|
|
||||||
func rawFile(ctx echo.Context) error {
|
func rawFile(ctx echo.Context) error {
|
||||||
gist := getData(ctx, "gist").(*models.Gist)
|
gist := getData(ctx, "gist").(*models.Gist)
|
||||||
fileContent, err := git.GetFileContent(
|
fileContent, err := git.GetFileContent(
|
||||||
|
@ -505,7 +550,7 @@ func likes(ctx echo.Context) error {
|
||||||
|
|
||||||
pageInt := getPage(ctx)
|
pageInt := getPage(ctx)
|
||||||
|
|
||||||
likers, err := models.GetUsersLikesForGists(gist, pageInt-1)
|
likers, err := models.GetUsersLikesForGist(gist, pageInt-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorRes(500, "Error getting users who liked this gist", err)
|
return errorRes(500, "Error getting users who liked this gist", err)
|
||||||
}
|
}
|
||||||
|
@ -518,3 +563,27 @@ func likes(ctx echo.Context) error {
|
||||||
setData(ctx, "revision", "HEAD")
|
setData(ctx, "revision", "HEAD")
|
||||||
return html(ctx, "likes.html")
|
return html(ctx, "likes.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func forks(ctx echo.Context) error {
|
||||||
|
var gist = getData(ctx, "gist").(*models.Gist)
|
||||||
|
pageInt := getPage(ctx)
|
||||||
|
|
||||||
|
currentUser := getUserLogged(ctx)
|
||||||
|
var fromUserID uint = 0
|
||||||
|
if currentUser != nil {
|
||||||
|
fromUserID = currentUser.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
forks, err := models.GetUsersForksForGist(gist, fromUserID, pageInt-1)
|
||||||
|
if err != nil {
|
||||||
|
return errorRes(500, "Error getting users who liked this gist", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = paginate(ctx, forks, pageInt, 30, "forks", gist.User.Username+"/"+gist.Uuid+"/forks"); err != nil {
|
||||||
|
return errorRes(404, "Page not found", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(ctx, "htmlTitle", "Forks for "+gist.Title)
|
||||||
|
setData(ctx, "revision", "HEAD")
|
||||||
|
return html(ctx, "forks.html")
|
||||||
|
}
|
||||||
|
|
|
@ -50,12 +50,9 @@ func gitHttp(ctx echo.Context) error {
|
||||||
strings.HasSuffix(ctx.Request().URL.Path, "git-upload-pack") ||
|
strings.HasSuffix(ctx.Request().URL.Path, "git-upload-pack") ||
|
||||||
ctx.Request().Method == "GET"
|
ctx.Request().Method == "GET"
|
||||||
|
|
||||||
repositoryPath, err := git.GetRepositoryPath(gist.User.Username, gist.Uuid)
|
repositoryPath := git.RepositoryPath(gist.User.Username, gist.Uuid)
|
||||||
if err != nil {
|
|
||||||
return errorRes(500, "Cannot get repository path", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = os.Stat(repositoryPath); os.IsNotExist(err) {
|
if _, err := os.Stat(repositoryPath); os.IsNotExist(err) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorRes(500, "Repository does not exist", err)
|
return errorRes(500, "Repository does not exist", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,8 @@ func Start() {
|
||||||
g3.POST("/edit", processCreate, logged, writePermission)
|
g3.POST("/edit", processCreate, logged, writePermission)
|
||||||
g3.POST("/like", like, logged)
|
g3.POST("/like", like, logged)
|
||||||
g3.GET("/likes", likes)
|
g3.GET("/likes", likes)
|
||||||
|
g3.POST("/fork", fork, logged)
|
||||||
|
g3.GET("/forks", forks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
templates/base/gist_header.html
vendored
26
templates/base/gist_header.html
vendored
|
@ -26,6 +26,18 @@
|
||||||
{{ .gist.NbLikes }}
|
{{ .gist.NbLikes }}
|
||||||
</a>
|
</a>
|
||||||
</form>
|
</form>
|
||||||
|
<form id="fork" class="ml-2 flex items-center" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/fork">
|
||||||
|
{{ .csrfHtml }}
|
||||||
|
<button type="submit" class="ml-auto focus-within:z-10 text-slate-300 relative inline-flex items-center space-x-2 rounded-l-md border border-gray-600 bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-300 hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" />
|
||||||
|
</svg>
|
||||||
|
Fork
|
||||||
|
</button>
|
||||||
|
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/forks" class="text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-700 bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-300 hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||||
|
{{ .gist.NbForks }}
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="ml-auto flex items-center">
|
<div class="ml-auto flex items-center">
|
||||||
<a href="/login" type="submit" class="ml-auto focus-within:z-10 text-slate-300 relative inline-flex items-center space-x-2 rounded-l-md border border-gray-600 bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-300 hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
<a href="/login" type="submit" class="ml-auto focus-within:z-10 text-slate-300 relative inline-flex items-center space-x-2 rounded-l-md border border-gray-600 bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-300 hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||||
|
@ -38,6 +50,17 @@
|
||||||
{{ .gist.NbLikes }}
|
{{ .gist.NbLikes }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ml-2 flex items-center">
|
||||||
|
<a href="/login" type="submit" class="ml-auto focus-within:z-10 text-slate-300 relative inline-flex items-center space-x-2 rounded-l-md border border-gray-600 bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-300 hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" />
|
||||||
|
</svg>
|
||||||
|
Fork
|
||||||
|
</a>
|
||||||
|
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/forks" class="text-slate-300 relative inline-flex align-middle items-center space-x-2 rounded-r-md border border-gray-700 bg-gray-900 px-2 py-1.5 -ml-px text-xs font-medium text-slate-300 hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
||||||
|
{{ .gist.NbForks }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ if .userLogged }}{{ if eq .gist.User.Username .userLogged.Username }}
|
{{ if .userLogged }}{{ if eq .gist.User.Username .userLogged.Username }}
|
||||||
<form id="visibility" class="ml-2" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/visibility">
|
<form id="visibility" class="ml-2" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/visibility">
|
||||||
|
@ -79,6 +102,9 @@
|
||||||
{{ end }}{{ end }}
|
{{ end }}{{ end }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{{ if .gist.Forked }}
|
||||||
|
<p class="mt-1 max-w-2xl text-sm text-slate-500">Forked from <a href="/{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Uuid }}">{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Title }}</a></p>
|
||||||
|
{{ end }}
|
||||||
<p class="mt-1 max-w-2xl text-sm text-slate-500">Last active <span class="moment-timestamp"> {{ .gist.UpdatedAt }} </span> •
|
<p class="mt-1 max-w-2xl text-sm text-slate-500">Last active <span class="moment-timestamp"> {{ .gist.UpdatedAt }} </span> •
|
||||||
{{ if .gist.Private }}<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-700 text-slate-300"> Unlisted </span>
|
{{ if .gist.Private }}<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-700 text-slate-300"> Unlisted </span>
|
||||||
{{else}}<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-emerald-700 text-emerald-300"> Public </span>{{ end }}
|
{{else}}<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-emerald-700 text-emerald-300"> Public </span>{{ end }}
|
||||||
|
|
7
templates/pages/all.html
vendored
7
templates/pages/all.html
vendored
|
@ -55,6 +55,12 @@
|
||||||
</svg>
|
</svg>
|
||||||
<span>{{ $gist.NbLikes }} likes</span>
|
<span>{{ $gist.NbLikes }} likes</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center float-right text-sm">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-1 inline-flex">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z" />
|
||||||
|
</svg>
|
||||||
|
<span>{{ $gist.NbForks }} forks</span>
|
||||||
|
</div>
|
||||||
<div class="flex items-center float-right text-sm">
|
<div class="flex items-center float-right text-sm">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-1 inline-flex">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-1 inline-flex">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z" />
|
||||||
|
@ -65,6 +71,7 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<h5 class="text-sm text-slate-500 pb-1">Last active <span class="moment-timestamp">{{ $gist.UpdatedAt }}</span>
|
<h5 class="text-sm text-slate-500 pb-1">Last active <span class="moment-timestamp">{{ $gist.UpdatedAt }}</span>
|
||||||
|
{{ if $gist.Forked }} • Forked from <a href="/{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Uuid }}">{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Title }}</a> {{ end }}
|
||||||
{{ if $gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-700 text-slate-300"> Unlisted </span>{{ end }}</h5>
|
{{ if $gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-700 text-slate-300"> Unlisted </span>{{ end }}</h5>
|
||||||
<h5 class="text-xs text-slate-300 py-1">{{ $gist.Description }}</h5>
|
<h5 class="text-xs text-slate-300 py-1">{{ $gist.Description }}</h5>
|
||||||
<a href="/{{ $gist.User.Username }}/{{ $gist.Uuid }}" class="hover:text-slate-300">
|
<a href="/{{ $gist.User.Username }}/{{ $gist.Uuid }}" class="hover:text-slate-300">
|
||||||
|
|
36
templates/pages/forks.html
vendored
Normal file
36
templates/pages/forks.html
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{{ template "header" .}}
|
||||||
|
{{ template "gist_header" .}}
|
||||||
|
{{ if ne (len .forks) 0 }}
|
||||||
|
<div class="mx-auto max-w-xl">
|
||||||
|
<h3 class="text-xl font-bold leading-tight break-all py-2">Forks</h3>
|
||||||
|
|
||||||
|
<ul role="list" class="divide-y divide-gray-700">
|
||||||
|
{{ range $gist := .forks }}
|
||||||
|
<li class="flex py-4">
|
||||||
|
<div>
|
||||||
|
<a href="/{{ $gist.User.Username }}" class="text-sm font-medium text-slate-300">{{ $gist.User.Username }}</a>
|
||||||
|
<p class="text-sm text-slate-500">Forked <span class="moment-timestamp">{{ $gist.CreatedAt }}</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto">
|
||||||
|
<a class="ml-auto text-slate-300 relative inline-flex items-center space-x-2 rounded-md border border-gray-600 bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-300 hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3" href="/{{ $gist.User.Username }}/{{ $gist.Uuid }}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" />
|
||||||
|
</svg>
|
||||||
|
View fork
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{ else }}
|
||||||
|
<div class="text-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mx-auto h-12 w-12 text-slate-400">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<h3 class="mt-2 text-sm font-medium text-slate-300">No public forks</h3>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ template "gist_footer" .}}
|
||||||
|
{{ template "footer" .}}
|
Loading…
Reference in a new issue