2015-11-27 01:33:45 +03: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"
2015-11-27 08:24:24 +03:00
"io/ioutil"
2016-03-04 21:32:17 +03:00
"net/url"
2015-11-27 09:50:38 +03:00
"os"
2015-11-27 08:24:24 +03:00
"path"
2015-11-27 01:33:45 +03:00
"path/filepath"
"strings"
2015-12-20 20:02:54 +03:00
2015-11-27 01:33:45 +03:00
"github.com/Unknwon/com"
2016-11-10 19:24:48 +03:00
"code.gitea.io/git"
2015-11-27 08:24:24 +03:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
2015-11-27 01:33:45 +03:00
)
2017-01-22 18:08:54 +03:00
var (
2017-11-28 12:43:51 +03:00
reservedWikiNames = [ ] string { "_pages" , "_new" , "_edit" }
2017-01-22 18:08:54 +03:00
wikiWorkingPool = sync . NewExclusivePool ( )
)
2015-11-27 08:24:24 +03:00
2017-11-28 12:43:51 +03: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 20:02:54 +03:00
return url . QueryEscape ( strings . Replace ( name , " " , "-" , - 1 ) )
2015-11-27 01:33:45 +03:00
}
2017-11-28 12:43:51 +03: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 17:56:30 +03:00
return "" , ErrWikiInvalidFileName { filename }
2017-11-28 12:43:51 +03:00
}
basename := filename [ : len ( filename ) - 3 ]
unescaped , err := url . QueryUnescape ( basename )
if err != nil {
return "" , err
}
return NormalizeWikiName ( unescaped ) , nil
2015-11-27 08:24:24 +03:00
}
2015-12-01 04:45:55 +03:00
// WikiCloneLink returns clone URLs of repository wiki.
2017-01-27 21:04:53 +03:00
func ( repo * Repository ) WikiCloneLink ( ) * CloneLink {
2015-12-01 04:45:55 +03:00
return repo . cloneLink ( true )
}
2015-11-27 01:33:45 +03: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 19:58:06 +03:00
// WikiPath returns wiki data path for given repository.
2015-11-27 01:33:45 +03:00
func ( repo * Repository ) WikiPath ( ) string {
2018-05-02 09:10:19 +03:00
return WikiPath ( repo . MustOwnerName ( ) , repo . Name )
2015-11-27 01:33:45 +03: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 06:40:44 +03:00
} else if err = createDelegateHooks ( repo . WikiPath ( ) ) ; err != nil {
return fmt . Errorf ( "createDelegateHooks: %v" , err )
2015-11-27 01:33:45 +03:00
}
return nil
}
2018-01-11 02:46:59 +03:00
// LocalWikiPath returns the local wiki repository copy path.
func LocalWikiPath ( ) string {
if filepath . IsAbs ( setting . Repository . Local . LocalWikiPath ) {
return setting . Repository . Local . LocalWikiPath
}
return path . Join ( setting . AppDataPath , setting . Repository . Local . LocalWikiPath )
}
2016-11-14 19:58:06 +03:00
// LocalWikiPath returns the path to the local wiki repository (?).
2015-11-27 08:24:24 +03:00
func ( repo * Repository ) LocalWikiPath ( ) string {
2018-01-11 02:46:59 +03:00
return path . Join ( LocalWikiPath ( ) , com . ToStr ( repo . ID ) )
2015-11-27 08:24:24 +03:00
}
// UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date.
2017-11-28 12:43:51 +03:00
func ( repo * Repository ) updateLocalWiki ( ) error {
2016-08-16 01:09:34 +03:00
// Don't pass branch name here because it fails to clone and
// checkout to a specific branch when wiki is an empty repository.
2017-03-20 16:36:19 +03:00
var branch = ""
if com . IsExist ( repo . LocalWikiPath ( ) ) {
branch = "master"
}
return UpdateLocalCopyBranch ( repo . WikiPath ( ) , repo . LocalWikiPath ( ) , branch )
2015-11-27 08:24:24 +03:00
}
2016-03-04 21:32:17 +03:00
func discardLocalWikiChanges ( localPath string ) error {
2016-08-15 09:02:14 +03:00
return discardLocalRepoBranchChanges ( localPath , "master" )
2016-03-04 21:32:17 +03:00
}
2017-11-28 12:43:51 +03: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 18:08:54 +03:00
}
}
return nil
}
2017-11-28 12:43:51 +03: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 18:08:54 +03:00
return err
}
2015-11-27 08:24:24 +03:00
wikiWorkingPool . CheckIn ( com . ToStr ( repo . ID ) )
defer wikiWorkingPool . CheckOut ( com . ToStr ( repo . ID ) )
2015-11-27 01:33:45 +03:00
if err = repo . InitWiki ( ) ; err != nil {
return fmt . Errorf ( "InitWiki: %v" , err )
}
2015-11-27 08:24:24 +03:00
localPath := repo . LocalWikiPath ( )
2016-03-04 21:32:17 +03:00
if err = discardLocalWikiChanges ( localPath ) ; err != nil {
return fmt . Errorf ( "discardLocalWikiChanges: %v" , err )
2017-11-28 12:43:51 +03:00
} else if err = repo . updateLocalWiki ( ) ; err != nil {
2015-11-27 08:24:24 +03:00
return fmt . Errorf ( "UpdateLocalWiki: %v" , err )
}
2017-11-28 12:43:51 +03:00
newWikiPath := path . Join ( localPath , WikiNameToFilename ( newWikiName ) )
2015-11-27 09:50:38 +03:00
// If not a new file, show perform update not create.
if isNew {
2017-11-28 12:43:51 +03:00
if com . IsExist ( newWikiPath ) {
return ErrWikiAlreadyExist { newWikiPath }
2015-11-27 09:50:38 +03:00
}
} else {
2017-11-28 12:43:51 +03:00
oldWikiPath := path . Join ( localPath , WikiNameToFilename ( oldWikiName ) )
if err := os . Remove ( oldWikiPath ) ; err != nil {
return fmt . Errorf ( "Failed to remove %s: %v" , oldWikiPath , err )
2016-12-01 02:56:15 +03:00
}
2015-11-27 09:50:38 +03:00
}
2016-07-01 10:33:35 +03:00
// SECURITY: if new file is a symlink to non-exist critical file,
// attack content can be written to the target file (e.g. authorized_keys2)
// as a new page operation.
// So we want to make sure the symlink is removed before write anything.
// The new file we created will be in normal text format.
2017-11-28 12:43:51 +03:00
if err = os . RemoveAll ( newWikiPath ) ; err != nil {
return err
}
2016-12-01 02:56:15 +03:00
2017-11-28 12:43:51 +03:00
if err = ioutil . WriteFile ( newWikiPath , [ ] byte ( content ) , 0666 ) ; err != nil {
2015-11-27 08:24:24 +03:00
return fmt . Errorf ( "WriteFile: %v" , err )
}
if len ( message ) == 0 {
2017-11-28 12:43:51 +03:00
message = "Update page '" + newWikiName + "'"
2015-11-27 08:24:24 +03:00
}
if err = git . AddChanges ( localPath , true ) ; err != nil {
return fmt . Errorf ( "AddChanges: %v" , err )
2016-08-27 23:37:55 +03:00
} else if err = git . CommitChanges ( localPath , git . CommitChangesOptions {
Committer : doer . NewGitSig ( ) ,
Message : message ,
} ) ; err != nil {
2015-11-27 08:24:24 +03:00
return fmt . Errorf ( "CommitChanges: %v" , err )
2017-05-30 12:32:01 +03:00
} else if err = git . Push ( localPath , git . PushOptions {
Remote : "origin" ,
Branch : "master" ,
} ) ; err != nil {
2015-11-27 08:24:24 +03:00
return fmt . Errorf ( "Push: %v" , err )
}
2015-11-27 01:33:45 +03:00
return nil
}
2015-11-27 09:50:38 +03:00
2017-01-21 15:50:51 +03:00
// AddWikiPage adds a new wiki page with a given wikiPath.
2017-11-28 12:43:51 +03:00
func ( repo * Repository ) AddWikiPage ( doer * User , wikiName , content , message string ) error {
return repo . updateWikiPage ( doer , "" , wikiName , content , message , true )
2015-11-27 09:50:38 +03:00
}
2017-01-21 15:50:51 +03:00
// EditWikiPage updates a wiki page identified by its wikiPath,
// optionally also changing wikiPath.
2017-11-28 12:43:51 +03: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 09:50:38 +03:00
}
2016-03-04 01:06:50 +03:00
2017-11-28 12:43:51 +03:00
// DeleteWikiPage deletes a wiki page identified by its path.
func ( repo * Repository ) DeleteWikiPage ( doer * User , wikiName string ) ( err error ) {
2016-03-04 01:06:50 +03:00
wikiWorkingPool . CheckIn ( com . ToStr ( repo . ID ) )
defer wikiWorkingPool . CheckOut ( com . ToStr ( repo . ID ) )
localPath := repo . LocalWikiPath ( )
2016-03-04 21:32:17 +03:00
if err = discardLocalWikiChanges ( localPath ) ; err != nil {
return fmt . Errorf ( "discardLocalWikiChanges: %v" , err )
2017-11-28 12:43:51 +03:00
} else if err = repo . updateLocalWiki ( ) ; err != nil {
2016-03-04 01:06:50 +03:00
return fmt . Errorf ( "UpdateLocalWiki: %v" , err )
}
2017-11-28 12:43:51 +03:00
filename := path . Join ( localPath , WikiNameToFilename ( wikiName ) )
2016-12-01 02:56:15 +03:00
if err := os . Remove ( filename ) ; err != nil {
2017-01-29 23:13:57 +03:00
return fmt . Errorf ( "Failed to remove %s: %v" , filename , err )
2016-12-01 02:56:15 +03:00
}
2016-03-04 01:06:50 +03:00
2017-11-28 12:43:51 +03:00
message := "Delete page '" + wikiName + "'"
2016-03-04 01:06:50 +03:00
if err = git . AddChanges ( localPath , true ) ; err != nil {
return fmt . Errorf ( "AddChanges: %v" , err )
2016-08-27 23:37:55 +03:00
} else if err = git . CommitChanges ( localPath , git . CommitChangesOptions {
Committer : doer . NewGitSig ( ) ,
Message : message ,
} ) ; err != nil {
2016-03-04 01:06:50 +03:00
return fmt . Errorf ( "CommitChanges: %v" , err )
2017-05-30 12:32:01 +03:00
} else if err = git . Push ( localPath , git . PushOptions {
Remote : "origin" ,
Branch : "master" ,
} ) ; err != nil {
2016-03-04 01:06:50 +03:00
return fmt . Errorf ( "Push: %v" , err )
}
return nil
}