detect_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. package detect
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "github.com/spf13/viper"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/zricethezav/gitleaks/v8/config"
  10. "github.com/zricethezav/gitleaks/v8/report"
  11. )
  12. const configPath = "../testdata/config/"
  13. const repoBasePath = "../testdata/repos/"
  14. func TestDetect(t *testing.T) {
  15. tests := []struct {
  16. cfgName string
  17. fragment Fragment
  18. expectedFindings []report.Finding
  19. wantError error
  20. }{
  21. {
  22. cfgName: "simple",
  23. fragment: Fragment{
  24. Raw: `awsToken := \"AKIALALEMEL33243OKIA\ // gitleaks:allow"`,
  25. FilePath: "tmp.go",
  26. },
  27. expectedFindings: []report.Finding{},
  28. },
  29. {
  30. cfgName: "simple",
  31. fragment: Fragment{
  32. Raw: `awsToken := \
  33. \"AKIALALEMEL33243OKIA\ // gitleaks:allow"
  34. `,
  35. FilePath: "tmp.go",
  36. },
  37. expectedFindings: []report.Finding{},
  38. },
  39. {
  40. cfgName: "simple",
  41. fragment: Fragment{
  42. Raw: `awsToken := \"AKIALALEMEL33243OKIA\"
  43. // gitleaks:allow"
  44. `,
  45. FilePath: "tmp.go",
  46. },
  47. expectedFindings: []report.Finding{
  48. {
  49. Description: "AWS Access Key",
  50. Secret: "AKIALALEMEL33243OKIA",
  51. Match: "AKIALALEMEL33243OKIA",
  52. File: "tmp.go",
  53. Line: `awsToken := \"AKIALALEMEL33243OKIA\"`,
  54. RuleID: "aws-access-key",
  55. Tags: []string{"key", "AWS"},
  56. StartLine: 0,
  57. EndLine: 0,
  58. StartColumn: 15,
  59. EndColumn: 34,
  60. Entropy: 3.1464393,
  61. },
  62. },
  63. },
  64. {
  65. cfgName: "escaped_character_group",
  66. fragment: Fragment{
  67. Raw: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`,
  68. FilePath: "tmp.go",
  69. },
  70. expectedFindings: []report.Finding{
  71. {
  72. Description: "PyPI upload token",
  73. Secret: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
  74. Match: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
  75. Line: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`,
  76. File: "tmp.go",
  77. RuleID: "pypi-upload-token",
  78. Tags: []string{"key", "pypi"},
  79. StartLine: 1,
  80. EndLine: 1,
  81. StartColumn: 1,
  82. EndColumn: 86,
  83. Entropy: 1.9606875,
  84. },
  85. },
  86. },
  87. {
  88. cfgName: "simple",
  89. fragment: Fragment{
  90. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  91. FilePath: "tmp.go",
  92. },
  93. expectedFindings: []report.Finding{
  94. {
  95. Description: "AWS Access Key",
  96. Secret: "AKIALALEMEL33243OLIA",
  97. Match: "AKIALALEMEL33243OLIA",
  98. Line: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  99. File: "tmp.go",
  100. RuleID: "aws-access-key",
  101. Tags: []string{"key", "AWS"},
  102. StartLine: 1,
  103. EndLine: 1,
  104. StartColumn: 15,
  105. EndColumn: 34,
  106. Entropy: 3.0841837,
  107. },
  108. },
  109. },
  110. {
  111. cfgName: "simple",
  112. fragment: Fragment{
  113. Raw: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`,
  114. FilePath: "tmp.sh",
  115. },
  116. expectedFindings: []report.Finding{
  117. {
  118. Description: "Sidekiq Secret",
  119. Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;",
  120. Secret: "cafebabe:deadbeef",
  121. Line: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`,
  122. File: "tmp.sh",
  123. RuleID: "sidekiq-secret",
  124. Tags: []string{},
  125. Entropy: 2.6098502,
  126. StartLine: 1,
  127. EndLine: 1,
  128. StartColumn: 8,
  129. EndColumn: 60,
  130. },
  131. },
  132. },
  133. {
  134. cfgName: "simple",
  135. fragment: Fragment{
  136. Raw: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`,
  137. FilePath: "tmp.sh",
  138. },
  139. expectedFindings: []report.Finding{
  140. {
  141. Description: "Sidekiq Secret",
  142. Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=\"cafebabe:deadbeef\"",
  143. Secret: "cafebabe:deadbeef",
  144. File: "tmp.sh",
  145. Line: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`,
  146. RuleID: "sidekiq-secret",
  147. Tags: []string{},
  148. Entropy: 2.6098502,
  149. StartLine: 1,
  150. EndLine: 1,
  151. StartColumn: 21,
  152. EndColumn: 74,
  153. },
  154. },
  155. },
  156. {
  157. cfgName: "simple",
  158. fragment: Fragment{
  159. Raw: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true&param2=false#heading1"`,
  160. FilePath: "tmp.sh",
  161. },
  162. expectedFindings: []report.Finding{
  163. {
  164. Description: "Sidekiq Sensitive URL",
  165. Match: "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:",
  166. Secret: "cafeb4b3:d3adb33f",
  167. File: "tmp.sh",
  168. Line: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true&param2=false#heading1"`,
  169. RuleID: "sidekiq-sensitive-url",
  170. Tags: []string{},
  171. Entropy: 2.984234,
  172. StartLine: 1,
  173. EndLine: 1,
  174. StartColumn: 8,
  175. EndColumn: 58,
  176. },
  177. },
  178. },
  179. {
  180. cfgName: "allow_aws_re",
  181. fragment: Fragment{
  182. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  183. FilePath: "tmp.go",
  184. },
  185. expectedFindings: []report.Finding{},
  186. },
  187. {
  188. cfgName: "allow_path",
  189. fragment: Fragment{
  190. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  191. FilePath: "tmp.go",
  192. },
  193. expectedFindings: []report.Finding{},
  194. },
  195. {
  196. cfgName: "allow_commit",
  197. fragment: Fragment{
  198. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  199. FilePath: "tmp.go",
  200. CommitSHA: "allowthiscommit",
  201. },
  202. expectedFindings: []report.Finding{},
  203. },
  204. {
  205. cfgName: "entropy_group",
  206. fragment: Fragment{
  207. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  208. FilePath: "tmp.go",
  209. },
  210. expectedFindings: []report.Finding{
  211. {
  212. Description: "Discord API key",
  213. Match: "Discord_Public_Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
  214. Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
  215. Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  216. File: "tmp.go",
  217. RuleID: "discord-api-key",
  218. Tags: []string{},
  219. Entropy: 3.7906237,
  220. StartLine: 1,
  221. EndLine: 1,
  222. StartColumn: 7,
  223. EndColumn: 93,
  224. },
  225. },
  226. },
  227. {
  228. cfgName: "generic_with_py_path",
  229. fragment: Fragment{
  230. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  231. FilePath: "tmp.go",
  232. },
  233. expectedFindings: []report.Finding{},
  234. },
  235. {
  236. cfgName: "generic_with_py_path",
  237. fragment: Fragment{
  238. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  239. FilePath: "tmp.py",
  240. },
  241. expectedFindings: []report.Finding{
  242. {
  243. Description: "Generic API Key",
  244. Match: "Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
  245. Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
  246. Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  247. File: "tmp.py",
  248. RuleID: "generic-api-key",
  249. Tags: []string{},
  250. Entropy: 3.7906237,
  251. StartLine: 1,
  252. EndLine: 1,
  253. StartColumn: 22,
  254. EndColumn: 93,
  255. },
  256. },
  257. },
  258. {
  259. cfgName: "path_only",
  260. fragment: Fragment{
  261. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  262. FilePath: "tmp.py",
  263. },
  264. expectedFindings: []report.Finding{
  265. {
  266. Description: "Python Files",
  267. Match: "file detected: tmp.py",
  268. File: "tmp.py",
  269. RuleID: "python-files-only",
  270. Tags: []string{},
  271. },
  272. },
  273. },
  274. {
  275. cfgName: "bad_entropy_group",
  276. fragment: Fragment{
  277. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  278. FilePath: "tmp.go",
  279. },
  280. expectedFindings: []report.Finding{},
  281. wantError: fmt.Errorf("Discord API key invalid regex secret group 5, max regex secret group 3"),
  282. },
  283. {
  284. cfgName: "simple",
  285. fragment: Fragment{
  286. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  287. FilePath: filepath.Join(configPath, "simple.toml"),
  288. },
  289. expectedFindings: []report.Finding{},
  290. },
  291. {
  292. cfgName: "allow_global_aws_re",
  293. fragment: Fragment{
  294. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  295. FilePath: "tmp.go",
  296. },
  297. expectedFindings: []report.Finding{},
  298. },
  299. {
  300. cfgName: "generic_with_py_path",
  301. fragment: Fragment{
  302. Raw: `const Discord_Public_Key = "load2523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  303. FilePath: "tmp.py",
  304. },
  305. expectedFindings: []report.Finding{},
  306. },
  307. }
  308. for _, tt := range tests {
  309. viper.Reset()
  310. viper.AddConfigPath(configPath)
  311. viper.SetConfigName(tt.cfgName)
  312. viper.SetConfigType("toml")
  313. err := viper.ReadInConfig()
  314. if err != nil {
  315. t.Error(err)
  316. }
  317. var vc config.ViperConfig
  318. err = viper.Unmarshal(&vc)
  319. if err != nil {
  320. t.Error(err)
  321. }
  322. cfg, err := vc.Translate()
  323. cfg.Path = filepath.Join(configPath, tt.cfgName+".toml")
  324. if tt.wantError != nil {
  325. if err == nil {
  326. t.Errorf("expected error")
  327. }
  328. assert.Equal(t, tt.wantError, err)
  329. }
  330. d := NewDetector(cfg)
  331. findings := d.Detect(tt.fragment)
  332. assert.ElementsMatch(t, tt.expectedFindings, findings)
  333. }
  334. }
  335. // TestFromGit tests the FromGit function
  336. func TestFromGit(t *testing.T) {
  337. tests := []struct {
  338. cfgName string
  339. source string
  340. logOpts string
  341. expectedFindings []report.Finding
  342. }{
  343. {
  344. source: filepath.Join(repoBasePath, "small"),
  345. cfgName: "simple",
  346. expectedFindings: []report.Finding{
  347. {
  348. Description: "AWS Access Key",
  349. StartLine: 20,
  350. EndLine: 20,
  351. StartColumn: 19,
  352. EndColumn: 38,
  353. Line: "\n awsToken := \"AKIALALEMEL33243OLIA\"",
  354. Secret: "AKIALALEMEL33243OLIA",
  355. Match: "AKIALALEMEL33243OLIA",
  356. File: "main.go",
  357. Date: "2021-11-02T23:37:53Z",
  358. Commit: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587",
  359. Author: "Zachary Rice",
  360. Email: "zricer@protonmail.com",
  361. Message: "Accidentally add a secret",
  362. RuleID: "aws-access-key",
  363. Tags: []string{"key", "AWS"},
  364. Entropy: 3.0841837,
  365. Fingerprint: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587:main.go:aws-access-key:20",
  366. },
  367. {
  368. Description: "AWS Access Key",
  369. StartLine: 9,
  370. EndLine: 9,
  371. StartColumn: 17,
  372. EndColumn: 36,
  373. Secret: "AKIALALEMEL33243OLIA",
  374. Match: "AKIALALEMEL33243OLIA",
  375. Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"",
  376. File: "foo/foo.go",
  377. Date: "2021-11-02T23:48:06Z",
  378. Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
  379. Author: "Zach Rice",
  380. Email: "zricer@protonmail.com",
  381. Message: "adding foo package with secret",
  382. RuleID: "aws-access-key",
  383. Tags: []string{"key", "AWS"},
  384. Entropy: 3.0841837,
  385. Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9",
  386. },
  387. },
  388. },
  389. {
  390. source: filepath.Join(repoBasePath, "small"),
  391. logOpts: "--all foo...",
  392. cfgName: "simple",
  393. expectedFindings: []report.Finding{
  394. {
  395. Description: "AWS Access Key",
  396. StartLine: 9,
  397. EndLine: 9,
  398. StartColumn: 17,
  399. EndColumn: 36,
  400. Secret: "AKIALALEMEL33243OLIA",
  401. Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"",
  402. Match: "AKIALALEMEL33243OLIA",
  403. Date: "2021-11-02T23:48:06Z",
  404. File: "foo/foo.go",
  405. Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
  406. Author: "Zach Rice",
  407. Email: "zricer@protonmail.com",
  408. Message: "adding foo package with secret",
  409. RuleID: "aws-access-key",
  410. Tags: []string{"key", "AWS"},
  411. Entropy: 3.0841837,
  412. Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9",
  413. },
  414. },
  415. },
  416. }
  417. err := moveDotGit("dotGit", ".git")
  418. if err != nil {
  419. t.Fatal(err)
  420. }
  421. defer func() {
  422. if err := moveDotGit(".git", "dotGit"); err != nil {
  423. t.Error(err)
  424. }
  425. }()
  426. for _, tt := range tests {
  427. viper.AddConfigPath(configPath)
  428. viper.SetConfigName("simple")
  429. viper.SetConfigType("toml")
  430. err = viper.ReadInConfig()
  431. if err != nil {
  432. t.Error(err)
  433. }
  434. var vc config.ViperConfig
  435. err = viper.Unmarshal(&vc)
  436. if err != nil {
  437. t.Error(err)
  438. }
  439. cfg, err := vc.Translate()
  440. if err != nil {
  441. t.Error(err)
  442. }
  443. detector := NewDetector(cfg)
  444. findings, err := detector.DetectGit(tt.source, tt.logOpts, DetectType)
  445. if err != nil {
  446. t.Error(err)
  447. }
  448. for _, f := range findings {
  449. f.Match = "" // remove lines cause copying and pasting them has some wack formatting
  450. }
  451. assert.ElementsMatch(t, tt.expectedFindings, findings)
  452. }
  453. }
  454. // TestFromFiles tests the FromFiles function
  455. func TestFromFiles(t *testing.T) {
  456. tests := []struct {
  457. cfgName string
  458. source string
  459. expectedFindings []report.Finding
  460. }{
  461. {
  462. source: filepath.Join(repoBasePath, "nogit"),
  463. cfgName: "simple",
  464. expectedFindings: []report.Finding{
  465. {
  466. Description: "AWS Access Key",
  467. StartLine: 20,
  468. EndLine: 20,
  469. StartColumn: 16,
  470. EndColumn: 35,
  471. Match: "AKIALALEMEL33243OLIA",
  472. Secret: "AKIALALEMEL33243OLIA",
  473. Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"",
  474. File: "../testdata/repos/nogit/main.go",
  475. SymlinkFile: "",
  476. RuleID: "aws-access-key",
  477. Tags: []string{"key", "AWS"},
  478. Entropy: 3.0841837,
  479. Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20",
  480. },
  481. },
  482. },
  483. {
  484. source: filepath.Join(repoBasePath, "nogit", "main.go"),
  485. cfgName: "simple",
  486. expectedFindings: []report.Finding{
  487. {
  488. Description: "AWS Access Key",
  489. StartLine: 20,
  490. EndLine: 20,
  491. StartColumn: 16,
  492. EndColumn: 35,
  493. Match: "AKIALALEMEL33243OLIA",
  494. Secret: "AKIALALEMEL33243OLIA",
  495. Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"",
  496. File: "../testdata/repos/nogit/main.go",
  497. RuleID: "aws-access-key",
  498. Tags: []string{"key", "AWS"},
  499. Entropy: 3.0841837,
  500. Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20",
  501. },
  502. },
  503. },
  504. }
  505. for _, tt := range tests {
  506. viper.AddConfigPath(configPath)
  507. viper.SetConfigName("simple")
  508. viper.SetConfigType("toml")
  509. err := viper.ReadInConfig()
  510. if err != nil {
  511. t.Error(err)
  512. }
  513. var vc config.ViperConfig
  514. err = viper.Unmarshal(&vc)
  515. if err != nil {
  516. t.Error(err)
  517. }
  518. cfg, _ := vc.Translate()
  519. detector := NewDetector(cfg)
  520. detector.FollowSymlinks = true
  521. findings, err := detector.DetectFiles(tt.source)
  522. if err != nil {
  523. t.Error(err)
  524. }
  525. assert.ElementsMatch(t, tt.expectedFindings, findings)
  526. }
  527. }
  528. func TestDetectWithSymlinks(t *testing.T) {
  529. tests := []struct {
  530. cfgName string
  531. source string
  532. expectedFindings []report.Finding
  533. }{
  534. {
  535. source: filepath.Join(repoBasePath, "symlinks/file_symlink"),
  536. cfgName: "simple",
  537. expectedFindings: []report.Finding{
  538. {
  539. Description: "Asymmetric Private Key",
  540. StartLine: 1,
  541. EndLine: 1,
  542. StartColumn: 1,
  543. EndColumn: 35,
  544. Match: "-----BEGIN OPENSSH PRIVATE KEY-----",
  545. Secret: "-----BEGIN OPENSSH PRIVATE KEY-----",
  546. Line: "-----BEGIN OPENSSH PRIVATE KEY-----",
  547. File: "../testdata/repos/symlinks/source_file/id_ed25519",
  548. SymlinkFile: "../testdata/repos/symlinks/file_symlink/symlinked_id_ed25519",
  549. RuleID: "apkey",
  550. Tags: []string{"key", "AsymmetricPrivateKey"},
  551. Entropy: 3.587164,
  552. Fingerprint: "../testdata/repos/symlinks/source_file/id_ed25519:apkey:1",
  553. },
  554. },
  555. },
  556. }
  557. for _, tt := range tests {
  558. viper.AddConfigPath(configPath)
  559. viper.SetConfigName("simple")
  560. viper.SetConfigType("toml")
  561. err := viper.ReadInConfig()
  562. if err != nil {
  563. t.Error(err)
  564. }
  565. var vc config.ViperConfig
  566. err = viper.Unmarshal(&vc)
  567. if err != nil {
  568. t.Error(err)
  569. }
  570. cfg, _ := vc.Translate()
  571. detector := NewDetector(cfg)
  572. detector.FollowSymlinks = true
  573. findings, err := detector.DetectFiles(tt.source)
  574. if err != nil {
  575. t.Error(err)
  576. }
  577. assert.ElementsMatch(t, tt.expectedFindings, findings)
  578. }
  579. }
  580. func moveDotGit(from, to string) error {
  581. repoDirs, err := os.ReadDir("../testdata/repos")
  582. if err != nil {
  583. return err
  584. }
  585. for _, dir := range repoDirs {
  586. if to == ".git" {
  587. _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), "dotGit"))
  588. if os.IsNotExist(err) {
  589. // dont want to delete the only copy of .git accidentally
  590. continue
  591. }
  592. os.RemoveAll(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), ".git"))
  593. }
  594. if !dir.IsDir() {
  595. continue
  596. }
  597. _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from))
  598. if os.IsNotExist(err) {
  599. continue
  600. }
  601. err = os.Rename(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from),
  602. fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), to))
  603. if err != nil {
  604. return err
  605. }
  606. }
  607. return nil
  608. }