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-11-27 08:24:24 +03:00
"sync"
2015-12-20 20:02:54 +03:00
2015-11-27 01:33:45 +03:00
"github.com/Unknwon/com"
2015-12-16 01:25:45 +03:00
"github.com/gogits/git-module"
2015-11-27 08:24:24 +03:00
"github.com/gogits/gogs/modules/setting"
2015-11-27 01:33:45 +03:00
)
2015-11-27 08:24:24 +03:00
// workingPool represents a pool of working status which makes sure
// that only one instance of same task is performing at a time.
// However, different type of tasks can performing at the same time.
type workingPool struct {
lock sync . Mutex
pool map [ string ] * sync . Mutex
count map [ string ] int
}
// CheckIn checks in a task and waits if others are running.
func ( p * workingPool ) CheckIn ( name string ) {
p . lock . Lock ( )
lock , has := p . pool [ name ]
if ! has {
lock = & sync . Mutex { }
p . pool [ name ] = lock
}
p . count [ name ] ++
p . lock . Unlock ( )
lock . Lock ( )
}
// CheckOut checks out a task to let other tasks run.
func ( p * workingPool ) CheckOut ( name string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
p . pool [ name ] . Unlock ( )
if p . count [ name ] == 1 {
delete ( p . pool , name )
delete ( p . count , name )
} else {
p . count [ name ] --
}
}
var wikiWorkingPool = & workingPool {
pool : make ( map [ string ] * sync . Mutex ) ,
count : make ( map [ string ] int ) ,
}
// ToWikiPageURL formats a string to corresponding wiki URL name.
func ToWikiPageURL ( 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
}
2016-07-01 10:33:35 +03:00
// ToWikiPageName formats a URL back to corresponding wiki page name,
// and removes leading characters './' to prevent changing files
// that are not belong to wiki repository.
2015-12-20 23:13:12 +03:00
func ToWikiPageName ( urlString string ) string {
name , _ := url . QueryUnescape ( strings . Replace ( urlString , "-" , " " , - 1 ) )
2016-07-01 10:33:35 +03:00
return strings . Replace ( strings . TrimLeft ( name , "./" ) , "/" , " " , - 1 )
2015-11-27 08:24:24 +03:00
}
2015-12-01 04:45:55 +03:00
// WikiCloneLink returns clone URLs of repository wiki.
func ( repo * Repository ) WikiCloneLink ( ) ( cl * CloneLink ) {
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" )
}
func ( repo * Repository ) WikiPath ( ) string {
return WikiPath ( repo . MustOwner ( ) . Name , repo . Name )
}
// 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 )
}
return nil
}
2015-11-27 08:24:24 +03:00
func ( repo * Repository ) LocalWikiPath ( ) string {
return path . Join ( setting . AppDataPath , "tmp/local-wiki" , com . ToStr ( repo . ID ) )
}
// UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date.
func ( repo * Repository ) UpdateLocalWiki ( ) error {
return updateLocalCopy ( repo . WikiPath ( ) , repo . LocalWikiPath ( ) )
}
2016-03-04 21:32:17 +03:00
// discardLocalWikiChanges discards local commits make sure
// it is even to remote branch when local copy exists.
func discardLocalWikiChanges ( localPath string ) error {
if ! com . IsExist ( localPath ) {
return nil
}
// No need to check if nothing in the repository.
if ! git . IsBranchExist ( localPath , "master" ) {
return nil
}
if err := git . ResetHEAD ( localPath , true , "origin/master" ) ; err != nil {
return fmt . Errorf ( "ResetHEAD: %v" , err )
}
return nil
}
2015-11-27 09:50:38 +03:00
// updateWikiPage adds new page to repository wiki.
func ( repo * Repository ) updateWikiPage ( doer * User , oldTitle , title , content , message string , isNew bool ) ( err error ) {
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 )
} else if err = repo . UpdateLocalWiki ( ) ; err != nil {
2015-11-27 08:24:24 +03:00
return fmt . Errorf ( "UpdateLocalWiki: %v" , err )
}
2016-07-01 10:33:35 +03:00
title = ToWikiPageName ( title )
2015-11-27 08:24:24 +03:00
filename := path . Join ( localPath , title + ".md" )
2015-11-27 09:50:38 +03:00
// If not a new file, show perform update not create.
if isNew {
if com . IsExist ( filename ) {
return ErrWikiAlreadyExist { filename }
}
} else {
os . Remove ( path . Join ( localPath , oldTitle + ".md" ) )
}
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.
os . Remove ( filename )
2015-11-27 08:24:24 +03:00
if err = ioutil . WriteFile ( filename , [ ] byte ( content ) , 0666 ) ; err != nil {
return fmt . Errorf ( "WriteFile: %v" , err )
}
if len ( message ) == 0 {
message = "Update page '" + title + "'"
}
if err = git . AddChanges ( localPath , true ) ; err != nil {
return fmt . Errorf ( "AddChanges: %v" , err )
} else if err = git . CommitChanges ( localPath , message , doer . NewGitSig ( ) ) ; err != nil {
return fmt . Errorf ( "CommitChanges: %v" , err )
} else if err = git . Push ( localPath , "origin" , "master" ) ; err != nil {
return fmt . Errorf ( "Push: %v" , err )
}
2015-11-27 01:33:45 +03:00
return nil
}
2015-11-27 09:50:38 +03:00
func ( repo * Repository ) AddWikiPage ( doer * User , title , content , message string ) error {
return repo . updateWikiPage ( doer , "" , title , content , message , true )
}
func ( repo * Repository ) EditWikiPage ( doer * User , oldTitle , title , content , message string ) error {
return repo . updateWikiPage ( doer , oldTitle , title , content , message , false )
}
2016-03-04 01:06:50 +03:00
func ( repo * Repository ) DeleteWikiPage ( doer * User , title string ) ( err error ) {
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 )
} else if err = repo . UpdateLocalWiki ( ) ; err != nil {
2016-03-04 01:06:50 +03:00
return fmt . Errorf ( "UpdateLocalWiki: %v" , err )
}
2016-07-01 10:33:35 +03:00
title = ToWikiPageName ( title )
2016-03-04 01:06:50 +03:00
filename := path . Join ( localPath , title + ".md" )
os . Remove ( filename )
message := "Delete page '" + title + "'"
if err = git . AddChanges ( localPath , true ) ; err != nil {
return fmt . Errorf ( "AddChanges: %v" , err )
} else if err = git . CommitChanges ( localPath , message , doer . NewGitSig ( ) ) ; err != nil {
return fmt . Errorf ( "CommitChanges: %v" , err )
} else if err = git . Push ( localPath , "origin" , "master" ) ; err != nil {
return fmt . Errorf ( "Push: %v" , err )
}
return nil
}