2015-11-26 17:33:45 -05:00
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"fmt"
2016-03-04 13:32:17 -05:00
"net/url"
2015-11-27 01:50:38 -05:00
"os"
2015-11-26 17:33:45 -05:00
"path/filepath"
"strings"
2015-12-20 18:02:54 +01:00
2019-03-27 17:33:00 +08:00
"code.gitea.io/gitea/modules/git"
2019-05-11 16:29:17 +01:00
"code.gitea.io/gitea/modules/log"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/sync"
2019-03-27 17:33:00 +08:00
2019-08-23 09:40:30 -07:00
"github.com/unknwon/com"
2015-11-26 17:33:45 -05:00
)
2017-01-22 16:08:54 +01:00
var (
2019-02-05 20:58:55 -05:00
reservedWikiNames = [ ] string { "_pages" , "_new" , "_edit" , "raw" }
2017-01-22 16:08:54 +01:00
wikiWorkingPool = sync . NewExclusivePool ( )
)
2015-11-27 00:24:24 -05:00
2017-11-28 01:43:51 -08:00
// NormalizeWikiName normalizes a wiki name
func NormalizeWikiName ( name string ) string {
return strings . Replace ( name , "-" , " " , - 1 )
}
// WikiNameToSubURL converts a wiki name to its corresponding sub-URL.
func WikiNameToSubURL ( name string ) string {
2015-12-20 18:02:54 +01:00
return url . QueryEscape ( strings . Replace ( name , " " , "-" , - 1 ) )
2015-11-26 17:33:45 -05:00
}
2017-11-28 01:43:51 -08:00
// WikiNameToFilename converts a wiki name to its corresponding filename.
func WikiNameToFilename ( name string ) string {
name = strings . Replace ( name , " " , "-" , - 1 )
return url . QueryEscape ( name ) + ".md"
}
// WikiFilenameToName converts a wiki filename to its corresponding page name.
func WikiFilenameToName ( filename string ) ( string , error ) {
if ! strings . HasSuffix ( filename , ".md" ) {
2018-02-05 16:56:30 +02:00
return "" , ErrWikiInvalidFileName { filename }
2017-11-28 01:43:51 -08:00
}
basename := filename [ : len ( filename ) - 3 ]
unescaped , err := url . QueryUnescape ( basename )
if err != nil {
return "" , err
}
return NormalizeWikiName ( unescaped ) , nil
2015-11-27 00:24:24 -05:00
}
2015-11-30 20:45:55 -05:00
// WikiCloneLink returns clone URLs of repository wiki.
2017-01-27 13:04:53 -05:00
func ( repo * Repository ) WikiCloneLink ( ) * CloneLink {
2018-10-20 00:36:42 +08:00
return repo . cloneLink ( x , true )
2015-11-30 20:45:55 -05:00
}
2015-11-26 17:33:45 -05:00
// WikiPath returns wiki data path by given user and repository name.
func WikiPath ( userName , repoName string ) string {
return filepath . Join ( UserPath ( userName ) , strings . ToLower ( repoName ) + ".wiki.git" )
}
2016-11-14 17:58:06 +01:00
// WikiPath returns wiki data path for given repository.
2015-11-26 17:33:45 -05:00
func ( repo * Repository ) WikiPath ( ) string {
2018-05-02 09:10:19 +03:00
return WikiPath ( repo . MustOwnerName ( ) , repo . Name )
2015-11-26 17:33:45 -05:00
}
// HasWiki returns true if repository has wiki.
func ( repo * Repository ) HasWiki ( ) bool {
return com . IsDir ( repo . WikiPath ( ) )
}
// InitWiki initializes a wiki for repository,
// it does nothing when repository already has wiki.
func ( repo * Repository ) InitWiki ( ) error {
if repo . HasWiki ( ) {
return nil
}
if err := git . InitRepository ( repo . WikiPath ( ) , true ) ; err != nil {
return fmt . Errorf ( "InitRepository: %v" , err )
2017-02-23 11:40:44 +08:00
} else if err = createDelegateHooks ( repo . WikiPath ( ) ) ; err != nil {
return fmt . Errorf ( "createDelegateHooks: %v" , err )
2015-11-26 17:33:45 -05:00
}
return nil
}
2017-11-28 01:43:51 -08:00
// nameAllowed checks if a wiki name is allowed
func nameAllowed ( name string ) error {
for _ , reservedName := range reservedWikiNames {
if name == reservedName {
return ErrWikiReservedName { name }
2017-01-22 16:08:54 +01:00
}
}
return nil
}
2017-11-28 01:43:51 -08:00
// updateWikiPage adds a new page to the repository wiki.
func ( repo * Repository ) updateWikiPage ( doer * User , oldWikiName , newWikiName , content , message string , isNew bool ) ( err error ) {
if err = nameAllowed ( newWikiName ) ; err != nil {
2017-01-22 16:08:54 +01:00
return err
}
2015-11-27 00:24:24 -05:00
wikiWorkingPool . CheckIn ( com . ToStr ( repo . ID ) )
defer wikiWorkingPool . CheckOut ( com . ToStr ( repo . ID ) )
2015-11-26 17:33:45 -05:00
if err = repo . InitWiki ( ) ; err != nil {
return fmt . Errorf ( "InitWiki: %v" , err )
}
2019-05-11 16:29:17 +01:00
hasMasterBranch := git . IsBranchExist ( repo . WikiPath ( ) , "master" )
basePath , err := CreateTemporaryPath ( "update-wiki" )
if err != nil {
return err
2015-11-27 00:24:24 -05:00
}
2019-06-12 21:41:28 +02:00
defer func ( ) {
if err := RemoveTemporaryPath ( basePath ) ; err != nil {
log . Error ( "Merge: RemoveTemporaryPath: %s" , err )
}
} ( )
2015-11-27 00:24:24 -05:00
2019-05-11 16:29:17 +01:00
cloneOpts := git . CloneRepoOptions {
Bare : true ,
Shared : true ,
}
if hasMasterBranch {
cloneOpts . Branch = "master"
}
2015-11-27 01:50:38 -05:00
2019-05-11 16:29:17 +01:00
if err := git . Clone ( repo . WikiPath ( ) , basePath , cloneOpts ) ; err != nil {
log . Error ( "Failed to clone repository: %s (%v)" , repo . FullName ( ) , err )
return fmt . Errorf ( "Failed to clone repository: %s (%v)" , repo . FullName ( ) , err )
}
gitRepo , err := git . OpenRepository ( basePath )
if err != nil {
log . Error ( "Unable to open temporary repository: %s (%v)" , basePath , err )
return fmt . Errorf ( "Failed to open new temporary repository in: %s %v" , basePath , err )
}
if hasMasterBranch {
if err := gitRepo . ReadTreeToIndex ( "HEAD" ) ; err != nil {
log . Error ( "Unable to read HEAD tree to index in: %s %v" , basePath , err )
return fmt . Errorf ( "Unable to read HEAD tree to index in: %s %v" , basePath , err )
}
}
newWikiPath := WikiNameToFilename ( newWikiName )
2015-11-27 01:50:38 -05:00
if isNew {
2019-05-11 16:29:17 +01:00
filesInIndex , err := gitRepo . LsFiles ( newWikiPath )
if err != nil {
log . Error ( "%v" , err )
return err
}
for _ , file := range filesInIndex {
if file == newWikiPath {
return ErrWikiAlreadyExist { newWikiPath }
}
2015-11-27 01:50:38 -05:00
}
} else {
2019-05-11 16:29:17 +01:00
oldWikiPath := WikiNameToFilename ( oldWikiName )
filesInIndex , err := gitRepo . LsFiles ( oldWikiPath )
if err != nil {
log . Error ( "%v" , err )
return err
}
found := false
for _ , file := range filesInIndex {
if file == oldWikiPath {
found = true
break
}
}
if found {
err := gitRepo . RemoveFilesFromIndex ( oldWikiPath )
if err != nil {
log . Error ( "%v" , err )
return err
}
2016-12-01 00:56:15 +01:00
}
2015-11-27 01:50:38 -05:00
}
2019-05-11 16:29:17 +01:00
// FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here
objectHash , err := gitRepo . HashObject ( strings . NewReader ( content ) )
if err != nil {
log . Error ( "%v" , err )
2017-11-28 01:43:51 -08:00
return err
}
2016-12-01 00:56:15 +01:00
2019-05-11 16:29:17 +01:00
if err := gitRepo . AddObjectToIndex ( "100644" , objectHash , newWikiPath ) ; err != nil {
log . Error ( "%v" , err )
return err
2015-11-27 00:24:24 -05:00
}
2019-05-11 16:29:17 +01:00
tree , err := gitRepo . WriteTree ( )
if err != nil {
log . Error ( "%v" , err )
return err
2015-11-27 00:24:24 -05:00
}
2019-05-11 16:29:17 +01:00
commitTreeOpts := git . CommitTreeOpts {
Message : message ,
}
2019-10-16 14:42:42 +01:00
sign , signingKey := repo . SignWikiCommit ( doer )
if sign {
commitTreeOpts . KeyID = signingKey
} else {
commitTreeOpts . NoGPGSign = true
}
2019-05-11 16:29:17 +01:00
if hasMasterBranch {
commitTreeOpts . Parents = [ ] string { "HEAD" }
}
commitHash , err := gitRepo . CommitTree ( doer . NewGitSig ( ) , tree , commitTreeOpts )
if err != nil {
log . Error ( "%v" , err )
return err
}
if err := git . Push ( basePath , git . PushOptions {
2017-05-30 05:32:01 -04:00
Remote : "origin" ,
2019-05-11 16:29:17 +01:00
Branch : fmt . Sprintf ( "%s:%s%s" , commitHash . String ( ) , git . BranchPrefix , "master" ) ,
2019-07-26 05:50:20 +08:00
Env : FullPushingEnvironment (
doer ,
doer ,
repo ,
repo . Name + ".wiki" ,
0 ,
) ,
2017-05-30 05:32:01 -04:00
} ) ; err != nil {
2019-05-11 16:29:17 +01:00
log . Error ( "%v" , err )
2015-11-27 00:24:24 -05:00
return fmt . Errorf ( "Push: %v" , err )
}
2015-11-26 17:33:45 -05:00
return nil
}
2015-11-27 01:50:38 -05:00
2017-01-21 20:50:51 +08:00
// AddWikiPage adds a new wiki page with a given wikiPath.
2017-11-28 01:43:51 -08:00
func ( repo * Repository ) AddWikiPage ( doer * User , wikiName , content , message string ) error {
return repo . updateWikiPage ( doer , "" , wikiName , content , message , true )
2015-11-27 01:50:38 -05:00
}
2017-01-21 20:50:51 +08:00
// EditWikiPage updates a wiki page identified by its wikiPath,
// optionally also changing wikiPath.
2017-11-28 01:43:51 -08:00
func ( repo * Repository ) EditWikiPage ( doer * User , oldWikiName , newWikiName , content , message string ) error {
return repo . updateWikiPage ( doer , oldWikiName , newWikiName , content , message , false )
2015-11-27 01:50:38 -05:00
}
2016-03-03 17:06:50 -05:00
2017-11-28 01:43:51 -08:00
// DeleteWikiPage deletes a wiki page identified by its path.
func ( repo * Repository ) DeleteWikiPage ( doer * User , wikiName string ) ( err error ) {
2016-03-03 17:06:50 -05:00
wikiWorkingPool . CheckIn ( com . ToStr ( repo . ID ) )
defer wikiWorkingPool . CheckOut ( com . ToStr ( repo . ID ) )
2019-05-11 16:29:17 +01:00
if err = repo . InitWiki ( ) ; err != nil {
return fmt . Errorf ( "InitWiki: %v" , err )
}
basePath , err := CreateTemporaryPath ( "update-wiki" )
if err != nil {
return err
2016-03-03 17:06:50 -05:00
}
2019-06-12 21:41:28 +02:00
defer func ( ) {
if err := RemoveTemporaryPath ( basePath ) ; err != nil {
log . Error ( "Merge: RemoveTemporaryPath: %s" , err )
}
} ( )
2016-03-03 17:06:50 -05:00
2019-05-11 16:29:17 +01:00
if err := git . Clone ( repo . WikiPath ( ) , basePath , git . CloneRepoOptions {
Bare : true ,
Shared : true ,
Branch : "master" ,
} ) ; err != nil {
log . Error ( "Failed to clone repository: %s (%v)" , repo . FullName ( ) , err )
return fmt . Errorf ( "Failed to clone repository: %s (%v)" , repo . FullName ( ) , err )
}
2016-12-01 00:56:15 +01:00
2019-05-11 16:29:17 +01:00
gitRepo , err := git . OpenRepository ( basePath )
if err != nil {
log . Error ( "Unable to open temporary repository: %s (%v)" , basePath , err )
return fmt . Errorf ( "Failed to open new temporary repository in: %s %v" , basePath , err )
2016-12-01 00:56:15 +01:00
}
2016-03-03 17:06:50 -05:00
2019-05-11 16:29:17 +01:00
if err := gitRepo . ReadTreeToIndex ( "HEAD" ) ; err != nil {
log . Error ( "Unable to read HEAD tree to index in: %s %v" , basePath , err )
return fmt . Errorf ( "Unable to read HEAD tree to index in: %s %v" , basePath , err )
}
wikiPath := WikiNameToFilename ( wikiName )
filesInIndex , err := gitRepo . LsFiles ( wikiPath )
found := false
for _ , file := range filesInIndex {
if file == wikiPath {
found = true
break
}
}
if found {
err := gitRepo . RemoveFilesFromIndex ( wikiPath )
if err != nil {
return err
}
} else {
return os . ErrNotExist
}
// FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here
tree , err := gitRepo . WriteTree ( )
if err != nil {
return err
}
2017-11-28 01:43:51 -08:00
message := "Delete page '" + wikiName + "'"
2019-10-16 14:42:42 +01:00
commitTreeOpts := git . CommitTreeOpts {
2019-05-11 16:29:17 +01:00
Message : message ,
Parents : [ ] string { "HEAD" } ,
2019-10-16 14:42:42 +01:00
}
sign , signingKey := repo . SignWikiCommit ( doer )
if sign {
commitTreeOpts . KeyID = signingKey
} else {
commitTreeOpts . NoGPGSign = true
}
commitHash , err := gitRepo . CommitTree ( doer . NewGitSig ( ) , tree , commitTreeOpts )
2019-05-11 16:29:17 +01:00
if err != nil {
return err
}
if err := git . Push ( basePath , git . PushOptions {
2017-05-30 05:32:01 -04:00
Remote : "origin" ,
2019-05-11 16:29:17 +01:00
Branch : fmt . Sprintf ( "%s:%s%s" , commitHash . String ( ) , git . BranchPrefix , "master" ) ,
Env : PushingEnvironment ( doer , repo ) ,
2017-05-30 05:32:01 -04:00
} ) ; err != nil {
2016-03-03 17:06:50 -05:00
return fmt . Errorf ( "Push: %v" , err )
}
return nil
}