github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/git/hook.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  // Copyright 2015 The Gogs Authors. All rights reserved.
     7  
     8  package git
     9  
    10  import (
    11  	"errors"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"strings"
    16  
    17  	"github.com/gitbundle/modules/log"
    18  	"github.com/gitbundle/modules/util"
    19  )
    20  
    21  // hookNames is a list of Git server hooks' name that are supported.
    22  var hookNames = []string{
    23  	"pre-receive",
    24  	"update",
    25  	"post-receive",
    26  }
    27  
    28  // ErrNotValidHook error when a git hook is not valid
    29  var ErrNotValidHook = errors.New("not a valid Git hook")
    30  
    31  // IsValidHookName returns true if given name is a valid Git hook.
    32  func IsValidHookName(name string) bool {
    33  	for _, hn := range hookNames {
    34  		if hn == name {
    35  			return true
    36  		}
    37  	}
    38  	return false
    39  }
    40  
    41  // Hook represents a Git hook.
    42  type Hook struct {
    43  	name     string
    44  	IsActive bool   // Indicates whether repository has this hook.
    45  	Content  string // Content of hook if it's active.
    46  	Sample   string // Sample content from Git.
    47  	path     string // Hook file path.
    48  }
    49  
    50  // GetHook returns a Git hook by given name and repository.
    51  func GetHook(repoPath, name string) (*Hook, error) {
    52  	if !IsValidHookName(name) {
    53  		return nil, ErrNotValidHook
    54  	}
    55  	h := &Hook{
    56  		name: name,
    57  		path: path.Join(repoPath, "hooks", name+".d", name),
    58  	}
    59  	samplePath := filepath.Join(repoPath, "hooks", name+".sample")
    60  	if isFile(h.path) {
    61  		data, err := os.ReadFile(h.path)
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  		h.IsActive = true
    66  		h.Content = string(data)
    67  	} else if isFile(samplePath) {
    68  		data, err := os.ReadFile(samplePath)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  		h.Sample = string(data)
    73  	}
    74  	return h, nil
    75  }
    76  
    77  // Name return the name of the hook
    78  func (h *Hook) Name() string {
    79  	return h.name
    80  }
    81  
    82  // Update updates hook settings.
    83  func (h *Hook) Update() error {
    84  	if len(strings.TrimSpace(h.Content)) == 0 {
    85  		if isExist(h.path) {
    86  			err := util.Remove(h.path)
    87  			if err != nil {
    88  				return err
    89  			}
    90  		}
    91  		h.IsActive = false
    92  		return nil
    93  	}
    94  	d := filepath.Dir(h.path)
    95  	if err := os.MkdirAll(d, os.ModePerm); err != nil {
    96  		return err
    97  	}
    98  
    99  	err := os.WriteFile(h.path, []byte(strings.ReplaceAll(h.Content, "\r", "")), os.ModePerm)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	h.IsActive = true
   104  	return nil
   105  }
   106  
   107  // ListHooks returns a list of Git hooks of given repository.
   108  func ListHooks(repoPath string) (_ []*Hook, err error) {
   109  	if !isDir(path.Join(repoPath, "hooks")) {
   110  		return nil, errors.New("hooks path does not exist")
   111  	}
   112  
   113  	hooks := make([]*Hook, len(hookNames))
   114  	for i, name := range hookNames {
   115  		hooks[i], err = GetHook(repoPath, name)
   116  		if err != nil {
   117  			return nil, err
   118  		}
   119  	}
   120  	return hooks, nil
   121  }
   122  
   123  const (
   124  	// HookPathUpdate hook update path
   125  	HookPathUpdate = "hooks/update"
   126  )
   127  
   128  // SetUpdateHook writes given content to update hook of the repository.
   129  func SetUpdateHook(repoPath, content string) (err error) {
   130  	log.Debug("Setting update hook: %s", repoPath)
   131  	hookPath := path.Join(repoPath, HookPathUpdate)
   132  	isExist, err := util.IsExist(hookPath)
   133  	if err != nil {
   134  		log.Debug("Unable to check if %s exists. Error: %v", hookPath, err)
   135  		return err
   136  	}
   137  	if isExist {
   138  		err = util.Remove(hookPath)
   139  	} else {
   140  		err = os.MkdirAll(path.Dir(hookPath), os.ModePerm)
   141  	}
   142  	if err != nil {
   143  		return err
   144  	}
   145  	return os.WriteFile(hookPath, []byte(content), 0o777)
   146  }