1// Copyright 2015 The Gogs Authors. All rights reserved. 2// Copyright 2021 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 6package git 7 8import ( 9 "errors" 10 "os" 11 "path" 12 "path/filepath" 13 "strings" 14 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/util" 17) 18 19// hookNames is a list of Git server hooks' name that are supported. 20var hookNames = []string{ 21 "pre-receive", 22 "update", 23 "post-receive", 24} 25 26var ( 27 // ErrNotValidHook error when a git hook is not valid 28 ErrNotValidHook = errors.New("not a valid Git hook") 29) 30 31// IsValidHookName returns true if given name is a valid Git hook. 32func 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. 42type 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. 51func 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 78func (h *Hook) Name() string { 79 return h.name 80} 81 82// Update updates hook settings. 83func (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. 108func 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 123const ( 124 // HookPathUpdate hook update path 125 HookPathUpdate = "hooks/update" 126) 127 128// SetUpdateHook writes given content to update hook of the repository. 129func 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), 0777) 146} 147