git.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. package git
  2. import (
  3. "errors"
  4. "io/ioutil"
  5. "os/exec"
  6. "strconv"
  7. "strings"
  8. "github.com/ditcraft/client/helpers"
  9. )
  10. // GetRepository will return the name of the current repostiry
  11. // minus the "https://github.com/" part
  12. func GetRepository() (string, error) {
  13. // Retrieving the current repository url from git
  14. cmdName := "git"
  15. cmdArgs := []string{"config", "--get", "remote.origin.url"}
  16. cmdOut, err := exec.Command(cmdName, cmdArgs...).CombinedOutput()
  17. if err != nil {
  18. return "", errors.New("There was an error running git config - are you in a repository folder?")
  19. }
  20. cmdOutString := string(cmdOut)[0 : len(string(cmdOut))-1]
  21. // Removing unecessary stuff from the url
  22. cmdOutString = sanitizeURL(cmdOutString)
  23. configErr := checkGitSetup()
  24. if configErr != nil {
  25. helpers.PrintLine("You don't have a user.email and/or user.name set in your git config. dit won't work if this is not fixed!", 2)
  26. helpers.PrintLine("Please run 'git config user.name <GITHUB_USERNAME_HERE>' and 'git config user.email <GITHUB_EMAIL_HERE>'", 2)
  27. return "", errors.New("git is not configured correctly")
  28. }
  29. return cmdOutString, nil
  30. }
  31. // Clone wraps the git clone functionality and initialized the ditContract afterwards
  32. func Clone(_repository string) (string, error) {
  33. // Only GitHub repositores are supported right now
  34. // if !strings.Contains(_repository, "github.com") {
  35. // return "", errors.New("Currently only github repositories are supported")
  36. // }
  37. // Calling git clone
  38. cmdArgs := []string{"clone", _repository, "--progress"}
  39. cmdOut, err := exec.Command("git", cmdArgs...).CombinedOutput()
  40. if strings.Contains(string(cmdOut), "Repository not found") {
  41. return "", errors.New("Repository not found")
  42. }
  43. if strings.Contains(string(cmdOut), "unable to update url base from redirection") {
  44. return "", errors.New("Repository URL invalid or not found")
  45. }
  46. if strings.Contains(string(cmdOut), "already exists and is not an empty directory.") {
  47. return "", errors.New("Destination path already exists")
  48. }
  49. if strings.Contains(string(cmdOut), "Invalid username or password") {
  50. helpers.PrintLine("Wrong username or password", 2)
  51. helpers.PrintLine("Note: If you have 2FA activated for GitHub, please go to: ", 1)
  52. helpers.PrintLine("https://github.blog/2013-09-03-two-factor-authentication/#how-does-it-work-for-command-line-git", 1)
  53. return "", errors.New("")
  54. }
  55. if err != nil {
  56. return "", errors.New("There was an error running git clone")
  57. }
  58. // Removing unecessary stuff from the url
  59. _repository = sanitizeURL(_repository)
  60. configErr := checkGitSetup()
  61. if configErr != nil {
  62. helpers.PrintLine("You don't have a user.email and/or user.name set in your git config. dit won't work if this is not fixed!", 1)
  63. helpers.PrintLine("Please run 'git config user.name <GITHUB_USERNAME_HERE>' and 'git config user.email <GITHUB_EMAIL_HERE>'", 1)
  64. }
  65. return _repository, nil
  66. }
  67. func checkGitSetup() error {
  68. // Verifying whether a git user email is set correctly
  69. cmdName := "git"
  70. cmdArgs := []string{"config", "user.email"}
  71. cmdOut, err := exec.Command(cmdName, cmdArgs...).CombinedOutput()
  72. if len(string(cmdOut)) <= 0 || err != nil {
  73. return errors.New("There is no git user.email set")
  74. }
  75. // Verifying whether a git user name is set correctly
  76. cmdName = "git"
  77. cmdArgs = []string{"config", "user.name"}
  78. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  79. if len(string(cmdOut)) <= 0 || err != nil {
  80. return errors.New("There is no git user.name set")
  81. }
  82. // Verifying whether the git password is being cached
  83. // If nothing is set, we will cache it for now
  84. cmdName = "git"
  85. cmdArgs = []string{"config", "credential.helper"}
  86. cmdOut, _ = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  87. if len(string(cmdOut)) <= 0 {
  88. cmdName = "git"
  89. cmdArgs = []string{"config", "credential.helper", "cache"}
  90. _, _ = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  91. }
  92. return nil
  93. }
  94. // Validate will return an error when the connection to
  95. // the git provider fails (validated through a git fetch)
  96. func Validate() error {
  97. repoName, err := GetRepository()
  98. if err != nil {
  99. return err
  100. }
  101. repoName = strings.SplitN(repoName, "/", 2)[1]
  102. // Verifying whether a master branch (or any branch) exists
  103. cmdName := "git"
  104. cmdArgs := []string{"branch"}
  105. cmdOutBranch, err := exec.Command(cmdName, cmdArgs...).CombinedOutput()
  106. if err != nil {
  107. return errors.New("There was an error running git branch")
  108. }
  109. // Verifying whether the connection works correctly
  110. cmdName = "git"
  111. cmdArgs = []string{"fetch", "-v"}
  112. cmdOutFetch, err := exec.Command(cmdName, cmdArgs...).CombinedOutput()
  113. if (!strings.Contains(string(cmdOutFetch), repoName) && len(cmdOutBranch) > 0) || err != nil {
  114. return errors.New("The connection to the git provider failed - please ensure that the repository is configured correctly")
  115. }
  116. return nil
  117. }
  118. // CheckForChanges will return an error if there are no new files to commit
  119. func CheckForChanges() error {
  120. // Verifying whether files have been added
  121. cmdName := "git"
  122. cmdArgs := []string{"ls-files", "--others", "--modified", "--exclude-standard"}
  123. cmdOut, err := exec.Command(cmdName, cmdArgs...).CombinedOutput()
  124. if err != nil {
  125. return errors.New("There was an error running the git ls-files command")
  126. }
  127. // If there are no files added, nothing can be committed
  128. if len(cmdOut) == 0 {
  129. return errors.New("There are no changed files to add for a commit")
  130. }
  131. return nil
  132. }
  133. // Commit will push the changes onto a new proposal branch
  134. func Commit(_proposalID int, _commitMessage string) error {
  135. // Name of the branch that will be used
  136. branchName := "dit_proposal_" + strconv.Itoa(_proposalID)
  137. // Verifying whether a master branch (or any branch) exists
  138. cmdName := "git"
  139. cmdArgs := []string{"branch"}
  140. cmdOut, err := exec.Command(cmdName, cmdArgs...).CombinedOutput()
  141. if err != nil {
  142. return errors.New("There was an error running git branch")
  143. }
  144. // If not: an empty readme will be pushed to master in order to
  145. // prevent the dit_proposal branch to be the main branch through the first commit
  146. if len(cmdOut) == 0 {
  147. repositoryName, err := GetRepository()
  148. if err != nil {
  149. return err
  150. }
  151. // Create a README.md file
  152. err = ioutil.WriteFile("README.md", []byte("# "+repositoryName), 0644)
  153. if err != nil {
  154. return errors.New("Failed to write README.md file")
  155. }
  156. // Add it for a commit
  157. cmdName = "git"
  158. cmdArgs = []string{"add", "README.md"}
  159. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  160. if err != nil {
  161. return errors.New("There was an error adding the README.md")
  162. }
  163. // Commit it
  164. cmdName = "git"
  165. cmdArgs = []string{"commit", "-m", "Initial push to master"}
  166. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  167. if err != nil {
  168. return errors.New("There was an error commiting the initial README")
  169. }
  170. // Push it
  171. cmdName = "git"
  172. cmdArgs = []string{"push", "-v"}
  173. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  174. if strings.Contains(string(cmdOut), "Invalid username or password") {
  175. helpers.PrintLine("Wrong username or password", 2)
  176. helpers.PrintLine("Note: If you have 2FA activated for GitHub, please go to: ", 2)
  177. helpers.PrintLine("https://github.blog/2013-09-03-two-factor-authentication/#how-does-it-work-for-command-line-git", 2)
  178. }
  179. if err != nil {
  180. return errors.New("There was an error pushing the initial commit")
  181. }
  182. }
  183. // Switch to the dit_proposal branch
  184. cmdName = "git"
  185. cmdArgs = []string{"checkout", "-b", branchName}
  186. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  187. if err != nil {
  188. return errors.New("There was an error running git checkout")
  189. }
  190. // Verify whether we are on the dit_proposal branch
  191. cmdName = "git"
  192. cmdArgs = []string{"status"}
  193. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  194. if err != nil || !strings.Contains(string(cmdOut), "On branch "+branchName) {
  195. return errors.New("There was an error running git status")
  196. }
  197. // Adding all files that can be added
  198. cmdName = "git"
  199. cmdArgs = []string{"add", "."}
  200. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  201. if err != nil {
  202. return errors.New("There was an error running the git add command")
  203. }
  204. // Verifying whether the files have been added
  205. cmdName = "git"
  206. cmdArgs = []string{"diff", "--cached", "--name-only"}
  207. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  208. if err != nil {
  209. return errors.New("There was an error running the git diff command")
  210. }
  211. // If there are no files added, nothing can be committed
  212. if len(cmdOut) == 0 {
  213. return errors.New("There are no changed files to add for a commit")
  214. }
  215. // Commit the changes with the provided commit message
  216. cmdName = "git"
  217. cmdArgs = []string{"commit", "-m", _commitMessage, "-v"}
  218. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  219. if err != nil || !strings.Contains(string(cmdOut), _commitMessage) {
  220. return errors.New("There was an error running git commit")
  221. }
  222. // Push these changes to the new branch
  223. cmdName = "git"
  224. cmdArgs = []string{"push", "--set-upstream", "origin", branchName, "-v"}
  225. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  226. if strings.Contains(string(cmdOut), "Invalid username or password") {
  227. helpers.PrintLine("Wrong username or password", 2)
  228. helpers.PrintLine("Note: If you have 2FA activated for GitHub, please go to: ", 2)
  229. helpers.PrintLine("https://github.blog/2013-09-03-two-factor-authentication/#how-does-it-work-for-command-line-git", 2)
  230. }
  231. if err != nil || !strings.Contains(string(cmdOut), "set up to track remote branch") {
  232. return errors.New("There was an error running git push")
  233. }
  234. // Switch to the master branch
  235. cmdName = "git"
  236. cmdArgs = []string{"checkout", "master"}
  237. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  238. if err != nil {
  239. return errors.New("There was an error running the last git checkout")
  240. }
  241. // Verify whether we are on the master branch again
  242. cmdName = "git"
  243. cmdArgs = []string{"status"}
  244. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  245. if err != nil || !strings.Contains(string(cmdOut), "On branch master") {
  246. return errors.New("There was an error running the last git status")
  247. }
  248. return nil
  249. }
  250. // Merge will merge a dit_proposal into the master after a successfull vote
  251. func Merge(_proposalID string) error {
  252. // Name of the branch that will be used
  253. branchName := "dit_proposal_" + _proposalID
  254. // Switching to the master branch
  255. cmdName := "git"
  256. cmdArgs := []string{"checkout", "master"}
  257. _, err := exec.Command(cmdName, cmdArgs...).CombinedOutput()
  258. if err != nil {
  259. return errors.New("There was an error running git checkout master")
  260. }
  261. // Merging the dit_proposal branch into the master
  262. cmdName = "git"
  263. cmdArgs = []string{"merge", branchName, "-v"}
  264. _, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  265. if err != nil {
  266. return errors.New("There was an error running git merge")
  267. }
  268. // Pushing the merged master branch
  269. cmdName = "git"
  270. cmdArgs = []string{"push", "-v"}
  271. cmdOut, err := exec.Command(cmdName, cmdArgs...).CombinedOutput()
  272. if strings.Contains(string(cmdOut), "Invalid username or password") {
  273. helpers.PrintLine("Wrong username or password", 2)
  274. helpers.PrintLine("Note: If you have 2FA activated for GitHub, please go to: ", 2)
  275. helpers.PrintLine("https://github.blog/2013-09-03-two-factor-authentication/#how-does-it-work-for-command-line-git", 2)
  276. }
  277. if err != nil {
  278. return errors.New("There was an error running git push")
  279. }
  280. // Deleting the now obsolete dit_proposal branch
  281. err = DeleteBranch(_proposalID)
  282. if err != nil {
  283. return errors.New("There was an error deleting the branch after a successful merge")
  284. }
  285. return nil
  286. }
  287. // DeleteBranch will delete a dit_proposal branch after it has been merged or after an unsuccessful vote
  288. func DeleteBranch(_proposalID string) error {
  289. // Name of the branch that will be used
  290. branchName := "dit_proposal_" + _proposalID
  291. // Remove the branch on the remote side
  292. cmdName := "git"
  293. cmdArgs := []string{"push", "--delete", "origin", branchName}
  294. cmdOut, err := exec.Command(cmdName, cmdArgs...).CombinedOutput()
  295. if strings.Contains(string(cmdOut), "Invalid username or password") {
  296. helpers.PrintLine("Wrong username or password", 2)
  297. helpers.PrintLine("Note: If you have 2FA activated for GitHub, please go to: ", 2)
  298. helpers.PrintLine("https://github.blog/2013-09-03-two-factor-authentication/#how-does-it-work-for-command-line-git", 2)
  299. }
  300. if err != nil || !strings.Contains(string(cmdOut), "deleted") {
  301. return errors.New("There was an error deleting the branch remotely")
  302. }
  303. // Remove the branch on the local side
  304. cmdName = "git"
  305. cmdArgs = []string{"branch", "-D", branchName}
  306. cmdOut, err = exec.Command(cmdName, cmdArgs...).CombinedOutput()
  307. if err != nil || !strings.Contains(string(cmdOut), "Deleted") {
  308. return errors.New("There was an error deleting the branch locally")
  309. }
  310. return nil
  311. }
  312. func sanitizeURL(_url string) string {
  313. _url = strings.Replace(_url, "http://", "", -1)
  314. _url = strings.Replace(_url, "https://", "", -1)
  315. _url = strings.Replace(_url, "git@", "", -1)
  316. _url = strings.Replace(_url, "github.com:", "github.com/", -1)
  317. _url = strings.Replace(_url, "gitlab.com:", "gitlab.com/", -1)
  318. _url = strings.Replace(_url, "bitbucket.org:", "bitbucket.com/", -1)
  319. _url = strings.Replace(_url, ".git", "", -1)
  320. return _url
  321. }