history_actions_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. package git
  2. import (
  3. "os"
  4. "path/filepath"
  5. "strings"
  6. "testing"
  7. )
  8. // headHash returns the current HEAD commit hash of a repository.
  9. func headHash(t *testing.T, manager *Manager, repoPath string) string {
  10. t.Helper()
  11. status, err := manager.Status(repoPath)
  12. if err != nil {
  13. t.Fatalf("Status() returned error: %v", err)
  14. }
  15. if status.Head == nil {
  16. t.Fatalf("HEAD is unborn, want a commit")
  17. }
  18. return status.Head.Hash
  19. }
  20. func TestCheckoutCommitDetachesHead(t *testing.T) {
  21. manager := newTestManager(t)
  22. repoPath := newTestRepo(t, manager)
  23. first := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  24. commitFile(t, manager, repoPath, "a.txt", "two\n", "second")
  25. if err := manager.CheckoutCommit(repoPath, first); err != nil {
  26. t.Fatalf("CheckoutCommit() returned error: %v", err)
  27. }
  28. status, err := manager.Status(repoPath)
  29. if err != nil {
  30. t.Fatalf("Status() returned error: %v", err)
  31. }
  32. if !status.Detached {
  33. t.Errorf("Detached = false after checking out a commit, want true")
  34. }
  35. if status.Head.Hash != first {
  36. t.Errorf("HEAD = %q, want %q", status.Head.Hash, first)
  37. }
  38. //The working tree must reflect the older commit
  39. content, _ := os.ReadFile(filepath.Join(repoPath, "a.txt"))
  40. if string(content) != "one\n" {
  41. t.Errorf("working tree = %q, want the first commit's content", string(content))
  42. }
  43. }
  44. func TestCheckoutCommitRefusesDirtyTree(t *testing.T) {
  45. manager := newTestManager(t)
  46. repoPath := newTestRepo(t, manager)
  47. first := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  48. commitFile(t, manager, repoPath, "a.txt", "two\n", "second")
  49. writeFile(t, repoPath, "a.txt", "uncommitted\n")
  50. if err := manager.CheckoutCommit(repoPath, first); err == nil {
  51. t.Errorf("CheckoutCommit() with a dirty tree = nil error, want a refusal")
  52. }
  53. }
  54. func TestResetToCommitModes(t *testing.T) {
  55. tests := []struct {
  56. name string
  57. mode string
  58. wantWorktree string // file content after reset
  59. wantChangeSeen bool // is the reverted change reported as pending?
  60. }{
  61. //soft/mixed keep the newer file on disk, so the difference reappears as
  62. //an uncommitted change; hard rewinds the file too.
  63. {name: "soft", mode: "soft", wantWorktree: "two\n", wantChangeSeen: true},
  64. {name: "mixed", mode: "mixed", wantWorktree: "two\n", wantChangeSeen: true},
  65. {name: "hard", mode: "hard", wantWorktree: "one\n", wantChangeSeen: false},
  66. }
  67. for _, test := range tests {
  68. t.Run(test.name, func(t *testing.T) {
  69. manager := newTestManager(t)
  70. repoPath := newTestRepo(t, manager)
  71. first := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  72. commitFile(t, manager, repoPath, "a.txt", "two\n", "second")
  73. if err := manager.ResetToCommit(repoPath, first, test.mode); err != nil {
  74. t.Fatalf("ResetToCommit(%s) returned error: %v", test.mode, err)
  75. }
  76. if got := headHash(t, manager, repoPath); got != first {
  77. t.Errorf("HEAD = %q, want the first commit %q", got, first)
  78. }
  79. content, _ := os.ReadFile(filepath.Join(repoPath, "a.txt"))
  80. if string(content) != test.wantWorktree {
  81. t.Errorf("working tree = %q, want %q", string(content), test.wantWorktree)
  82. }
  83. status, _ := manager.Status(repoPath)
  84. if status.Clean == test.wantChangeSeen {
  85. t.Errorf("Clean = %v, want %v", status.Clean, !test.wantChangeSeen)
  86. }
  87. })
  88. }
  89. }
  90. func TestResetHardRefusesDirtyTree(t *testing.T) {
  91. manager := newTestManager(t)
  92. repoPath := newTestRepo(t, manager)
  93. first := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  94. commitFile(t, manager, repoPath, "a.txt", "two\n", "second")
  95. writeFile(t, repoPath, "b.txt", "unsaved work\n")
  96. if err := manager.ResetToCommit(repoPath, first, "hard"); err == nil {
  97. t.Errorf("hard reset with a dirty tree = nil error, want a refusal")
  98. }
  99. }
  100. func TestResetToCommitValidation(t *testing.T) {
  101. manager := newTestManager(t)
  102. repoPath := newTestRepo(t, manager)
  103. hash := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  104. if err := manager.ResetToCommit(repoPath, hash, "sideways"); err == nil {
  105. t.Errorf("ResetToCommit() with an unknown mode = nil error, want an error")
  106. }
  107. if err := manager.ResetToCommit(repoPath, "abc1234", "mixed"); err == nil {
  108. t.Errorf("ResetToCommit() with a short hash = nil error, want an error")
  109. }
  110. }
  111. func TestCreateBranchAt(t *testing.T) {
  112. manager := newTestManager(t)
  113. repoPath := newTestRepo(t, manager)
  114. first := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  115. commitFile(t, manager, repoPath, "a.txt", "two\n", "second")
  116. if err := manager.CreateBranchAt(repoPath, "legacy", first); err != nil {
  117. t.Fatalf("CreateBranchAt() returned error: %v", err)
  118. }
  119. status, err := manager.Status(repoPath)
  120. if err != nil {
  121. t.Fatalf("Status() returned error: %v", err)
  122. }
  123. if status.Branch != "legacy" {
  124. t.Errorf("current branch = %q, want %q", status.Branch, "legacy")
  125. }
  126. if status.Head.Hash != first {
  127. t.Errorf("new branch points at %q, want the first commit %q", status.Head.Hash, first)
  128. }
  129. }
  130. func TestCreateBranchAtRejectsExistingName(t *testing.T) {
  131. manager := newTestManager(t)
  132. repoPath := newTestRepo(t, manager)
  133. hash := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  134. branches, _ := manager.Branches(repoPath)
  135. existing := branches[0].Name
  136. if err := manager.CreateBranchAt(repoPath, existing, hash); err == nil {
  137. t.Errorf("CreateBranchAt() with an existing name = nil error, want an error")
  138. }
  139. if err := manager.CreateBranchAt(repoPath, "bad name", hash); err == nil {
  140. t.Errorf("CreateBranchAt() with an invalid name = nil error, want an error")
  141. }
  142. }
  143. func TestCreateTagLightweightAndAnnotated(t *testing.T) {
  144. manager := newTestManager(t)
  145. repoPath := newTestRepo(t, manager)
  146. hash := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  147. if err := manager.CreateTag(repoPath, "v1.0", hash, "", "T", "t@e.com"); err != nil {
  148. t.Fatalf("CreateTag() lightweight returned error: %v", err)
  149. }
  150. if err := manager.CreateTag(repoPath, "v1.0-annotated", hash, "first release", "T", "t@e.com"); err != nil {
  151. t.Fatalf("CreateTag() annotated returned error: %v", err)
  152. }
  153. tags, err := manager.TagsForCommit(repoPath, hash)
  154. if err != nil {
  155. t.Fatalf("TagsForCommit() returned error: %v", err)
  156. }
  157. if len(tags) != 2 {
  158. t.Fatalf("TagsForCommit() = %v, want two tags", tags)
  159. }
  160. found := map[string]bool{}
  161. for _, tag := range tags {
  162. found[tag] = true
  163. }
  164. if !found["v1.0"] || !found["v1.0-annotated"] {
  165. t.Errorf("TagsForCommit() = %v, want both the lightweight and annotated tags", tags)
  166. }
  167. }
  168. func TestCreateTagValidation(t *testing.T) {
  169. manager := newTestManager(t)
  170. repoPath := newTestRepo(t, manager)
  171. hash := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  172. if err := manager.CreateTag(repoPath, "v1.0", hash, "", "T", "t@e.com"); err != nil {
  173. t.Fatalf("CreateTag() returned error: %v", err)
  174. }
  175. tests := []struct {
  176. name string
  177. tag string
  178. }{
  179. {name: "duplicate", tag: "v1.0"},
  180. {name: "empty", tag: ""},
  181. {name: "with space", tag: "v 1.0"},
  182. {name: "with tilde", tag: "v~1"},
  183. }
  184. for _, test := range tests {
  185. t.Run(test.name, func(t *testing.T) {
  186. if err := manager.CreateTag(repoPath, test.tag, hash, "", "T", "t@e.com"); err == nil {
  187. t.Errorf("CreateTag(%q) = nil error, want an error", test.tag)
  188. }
  189. })
  190. }
  191. }
  192. func TestTagsForCommitEmpty(t *testing.T) {
  193. manager := newTestManager(t)
  194. repoPath := newTestRepo(t, manager)
  195. hash := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  196. tags, err := manager.TagsForCommit(repoPath, hash)
  197. if err != nil {
  198. t.Fatalf("TagsForCommit() returned error: %v", err)
  199. }
  200. if len(tags) != 0 {
  201. t.Errorf("TagsForCommit() = %v, want none", tags)
  202. }
  203. }
  204. func TestRevertLatestCommit(t *testing.T) {
  205. manager := newTestManager(t)
  206. repoPath := newTestRepo(t, manager)
  207. commitFile(t, manager, repoPath, "a.txt", "original\n", "first")
  208. commitFile(t, manager, repoPath, "a.txt", "changed\n", "second")
  209. second := headHash(t, manager, repoPath)
  210. revertHash, err := manager.RevertCommit(repoPath, second, &CommitRequest{
  211. Name: "Test User",
  212. Email: "test@arozos.local",
  213. })
  214. if err != nil {
  215. t.Fatalf("RevertCommit() returned error: %v", err)
  216. }
  217. if revertHash == "" {
  218. t.Fatalf("RevertCommit() returned an empty hash")
  219. }
  220. //The file must be back to its state before the reverted commit
  221. content, _ := os.ReadFile(filepath.Join(repoPath, "a.txt"))
  222. if string(content) != "original\n" {
  223. t.Errorf("after revert a.txt = %q, want %q", string(content), "original\n")
  224. }
  225. status, _ := manager.Status(repoPath)
  226. if !status.Clean {
  227. t.Errorf("working tree is not clean after revert: %+v", status.Changes)
  228. }
  229. if status.Head.Subject != `Revert "second"` {
  230. t.Errorf("revert commit subject = %q, want %q", status.Head.Subject, `Revert "second"`)
  231. }
  232. }
  233. func TestRevertAddedFileDeletesIt(t *testing.T) {
  234. manager := newTestManager(t)
  235. repoPath := newTestRepo(t, manager)
  236. commitFile(t, manager, repoPath, "keep.txt", "keep\n", "first")
  237. commitFile(t, manager, repoPath, "added.txt", "new file\n", "add a file")
  238. second := headHash(t, manager, repoPath)
  239. if _, err := manager.RevertCommit(repoPath, second, &CommitRequest{Name: "T", Email: "t@e.com"}); err != nil {
  240. t.Fatalf("RevertCommit() returned error: %v", err)
  241. }
  242. if _, err := os.Stat(filepath.Join(repoPath, "added.txt")); !os.IsNotExist(err) {
  243. t.Errorf("reverting the commit that added the file did not remove it")
  244. }
  245. if _, err := os.Stat(filepath.Join(repoPath, "keep.txt")); err != nil {
  246. t.Errorf("revert removed an unrelated file: %v", err)
  247. }
  248. }
  249. func TestRevertOlderUntouchedCommit(t *testing.T) {
  250. manager := newTestManager(t)
  251. repoPath := newTestRepo(t, manager)
  252. commitFile(t, manager, repoPath, "a.txt", "aaa\n", "add a")
  253. addACommit := headHash(t, manager, repoPath)
  254. commitFile(t, manager, repoPath, "b.txt", "bbb\n", "add b unrelated")
  255. //Reverting the older commit that added a.txt is clean because nothing has
  256. //touched a.txt since.
  257. if _, err := manager.RevertCommit(repoPath, addACommit, &CommitRequest{Name: "T", Email: "t@e.com"}); err != nil {
  258. t.Fatalf("RevertCommit() of an older untouched commit returned error: %v", err)
  259. }
  260. if _, err := os.Stat(filepath.Join(repoPath, "a.txt")); !os.IsNotExist(err) {
  261. t.Errorf("a.txt should be gone after reverting the commit that added it")
  262. }
  263. //The unrelated later file must be untouched
  264. content, _ := os.ReadFile(filepath.Join(repoPath, "b.txt"))
  265. if string(content) != "bbb\n" {
  266. t.Errorf("b.txt = %q, want it left alone", string(content))
  267. }
  268. }
  269. func TestRevertRefusesWhenFileChangedSince(t *testing.T) {
  270. manager := newTestManager(t)
  271. repoPath := newTestRepo(t, manager)
  272. commitFile(t, manager, repoPath, "a.txt", "v1\n", "first")
  273. target := headHash(t, manager, repoPath)
  274. commitFile(t, manager, repoPath, "a.txt", "v2\n", "second touches the same file")
  275. //a.txt was changed again after the target commit, so a clean revert is
  276. //impossible and the operation must refuse rather than corrupt the file.
  277. if _, err := manager.RevertCommit(repoPath, target, &CommitRequest{Name: "T", Email: "t@e.com"}); err == nil {
  278. t.Errorf("RevertCommit() of a commit whose file changed since = nil error, want a refusal")
  279. }
  280. //Nothing should have been written
  281. status, _ := manager.Status(repoPath)
  282. if !status.Clean {
  283. t.Errorf("a refused revert left the tree dirty: %+v", status.Changes)
  284. }
  285. }
  286. func TestRevertRefusesDirtyTree(t *testing.T) {
  287. manager := newTestManager(t)
  288. repoPath := newTestRepo(t, manager)
  289. commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  290. hash := headHash(t, manager, repoPath)
  291. writeFile(t, repoPath, "a.txt", "uncommitted\n")
  292. if _, err := manager.RevertCommit(repoPath, hash, &CommitRequest{Name: "T", Email: "t@e.com"}); err == nil {
  293. t.Errorf("RevertCommit() with a dirty tree = nil error, want a refusal")
  294. }
  295. }
  296. func TestCherryPickAppliesToAnotherBranch(t *testing.T) {
  297. manager := newTestManager(t)
  298. repoPath := newTestRepo(t, manager)
  299. base := commitFile(t, manager, repoPath, "a.txt", "base\n", "base")
  300. //A feature commit that adds a file, on a branch off the base
  301. if err := manager.Checkout(repoPath, "feature", true); err != nil {
  302. t.Fatalf("Checkout(create) returned error: %v", err)
  303. }
  304. commitFile(t, manager, repoPath, "feature.txt", "feature work\n", "add feature")
  305. featureCommit := headHash(t, manager, repoPath)
  306. //Back to the base branch, cherry-pick the feature commit
  307. if err := manager.CheckoutCommit(repoPath, base); err != nil {
  308. t.Fatalf("CheckoutCommit(base) returned error: %v", err)
  309. }
  310. if err := manager.Checkout(repoPath, "main-line", true); err != nil {
  311. t.Fatalf("Checkout(create main-line) returned error: %v", err)
  312. }
  313. pickHash, err := manager.CherryPickCommit(repoPath, featureCommit, &CommitRequest{Name: "T", Email: "t@e.com"})
  314. if err != nil {
  315. t.Fatalf("CherryPickCommit() returned error: %v", err)
  316. }
  317. if pickHash == "" {
  318. t.Fatalf("CherryPickCommit() returned an empty hash")
  319. }
  320. content, err := os.ReadFile(filepath.Join(repoPath, "feature.txt"))
  321. if err != nil {
  322. t.Fatalf("the cherry-picked file is missing: %v", err)
  323. }
  324. if string(content) != "feature work\n" {
  325. t.Errorf("feature.txt = %q, want the cherry-picked content", string(content))
  326. }
  327. //The original commit message is preserved
  328. status, _ := manager.Status(repoPath)
  329. if status.Head.Subject != "add feature" {
  330. t.Errorf("cherry-pick subject = %q, want %q", status.Head.Subject, "add feature")
  331. }
  332. }
  333. func TestCherryPickPreservesOriginalAuthor(t *testing.T) {
  334. manager := newTestManager(t)
  335. repoPath := newTestRepo(t, manager)
  336. base := commitFile(t, manager, repoPath, "a.txt", "base\n", "base")
  337. if err := manager.Checkout(repoPath, "feature", true); err != nil {
  338. t.Fatalf("Checkout(create) returned error: %v", err)
  339. }
  340. writeFile(t, repoPath, "feature.txt", "work\n")
  341. if _, err := manager.Commit(repoPath, &CommitRequest{
  342. Message: "feature commit",
  343. Files: []string{"feature.txt"},
  344. Name: "Original Author",
  345. Email: "original@example.com",
  346. }); err != nil {
  347. t.Fatalf("Commit() returned error: %v", err)
  348. }
  349. featureCommit := headHash(t, manager, repoPath)
  350. if err := manager.CheckoutCommit(repoPath, base); err != nil {
  351. t.Fatalf("CheckoutCommit(base) returned error: %v", err)
  352. }
  353. if err := manager.Checkout(repoPath, "target", true); err != nil {
  354. t.Fatalf("Checkout(create) returned error: %v", err)
  355. }
  356. if _, err := manager.CherryPickCommit(repoPath, featureCommit, &CommitRequest{
  357. Name: "Picker",
  358. Email: "picker@example.com",
  359. }); err != nil {
  360. t.Fatalf("CherryPickCommit() returned error: %v", err)
  361. }
  362. status, _ := manager.Status(repoPath)
  363. if status.Head.AuthorName != "Original Author" {
  364. t.Errorf("author = %q, want the original author preserved", status.Head.AuthorName)
  365. }
  366. if status.Head.AuthorEmail != "original@example.com" {
  367. t.Errorf("author email = %q, want the original preserved", status.Head.AuthorEmail)
  368. }
  369. }
  370. func TestCherryPickRefusesConflict(t *testing.T) {
  371. manager := newTestManager(t)
  372. repoPath := newTestRepo(t, manager)
  373. base := commitFile(t, manager, repoPath, "shared.txt", "base\n", "base")
  374. //Feature edits shared.txt
  375. if err := manager.Checkout(repoPath, "feature", true); err != nil {
  376. t.Fatalf("Checkout(create) returned error: %v", err)
  377. }
  378. commitFile(t, manager, repoPath, "shared.txt", "feature edit\n", "feature edits shared")
  379. featureCommit := headHash(t, manager, repoPath)
  380. //Target branch edits shared.txt differently, so the pre-image no longer
  381. //matches and a clean cherry-pick is impossible.
  382. if err := manager.CheckoutCommit(repoPath, base); err != nil {
  383. t.Fatalf("CheckoutCommit(base) returned error: %v", err)
  384. }
  385. if err := manager.Checkout(repoPath, "target", true); err != nil {
  386. t.Fatalf("Checkout(create) returned error: %v", err)
  387. }
  388. commitFile(t, manager, repoPath, "shared.txt", "target edit\n", "target edits shared")
  389. if _, err := manager.CherryPickCommit(repoPath, featureCommit, &CommitRequest{Name: "T", Email: "t@e.com"}); err == nil {
  390. t.Errorf("CherryPickCommit() onto a diverged file = nil error, want a refusal")
  391. }
  392. status, _ := manager.Status(repoPath)
  393. if !status.Clean {
  394. t.Errorf("a refused cherry-pick left the tree dirty: %+v", status.Changes)
  395. }
  396. }
  397. func TestAmendCommitMessage(t *testing.T) {
  398. manager := newTestManager(t)
  399. repoPath := newTestRepo(t, manager)
  400. commitFile(t, manager, repoPath, "a.txt", "one\n", "typo in mesage")
  401. original := headHash(t, manager, repoPath)
  402. newHash, err := manager.AmendCommitMessage(repoPath, "fixed message", &CommitRequest{
  403. Name: "T",
  404. Email: "t@e.com",
  405. })
  406. if err != nil {
  407. t.Fatalf("AmendCommitMessage() returned error: %v", err)
  408. }
  409. if newHash == original {
  410. t.Errorf("amend produced the same hash %q, want a new commit object", newHash)
  411. }
  412. status, _ := manager.Status(repoPath)
  413. if status.Head.Subject != "fixed message" {
  414. t.Errorf("HEAD subject = %q, want the amended message", status.Head.Subject)
  415. }
  416. if status.Head.Hash != newHash {
  417. t.Errorf("HEAD = %q, want the amended commit %q", status.Head.Hash, newHash)
  418. }
  419. //The file content, i.e. the tree, must be unchanged
  420. content, _ := os.ReadFile(filepath.Join(repoPath, "a.txt"))
  421. if string(content) != "one\n" {
  422. t.Errorf("amend altered the tree, a.txt = %q", string(content))
  423. }
  424. //Only one commit should exist — amend replaces, not appends
  425. log, _ := manager.Log(repoPath, 10)
  426. if len(log) != 1 {
  427. t.Errorf("log has %d commits after amend, want 1", len(log))
  428. }
  429. }
  430. func TestAmendPreservesAuthorAcrossMessageChange(t *testing.T) {
  431. manager := newTestManager(t)
  432. repoPath := newTestRepo(t, manager)
  433. writeFile(t, repoPath, "a.txt", "one\n")
  434. if _, err := manager.Commit(repoPath, &CommitRequest{
  435. Message: "original",
  436. Files: []string{"a.txt"},
  437. Name: "First Author",
  438. Email: "first@example.com",
  439. }); err != nil {
  440. t.Fatalf("Commit() returned error: %v", err)
  441. }
  442. if _, err := manager.AmendCommitMessage(repoPath, "reworded", &CommitRequest{
  443. Name: "Someone Else",
  444. Email: "else@example.com",
  445. }); err != nil {
  446. t.Fatalf("AmendCommitMessage() returned error: %v", err)
  447. }
  448. status, _ := manager.Status(repoPath)
  449. if status.Head.AuthorName != "First Author" {
  450. t.Errorf("author = %q, want the original author preserved through amend", status.Head.AuthorName)
  451. }
  452. }
  453. func TestAmendValidation(t *testing.T) {
  454. manager := newTestManager(t)
  455. repoPath := newTestRepo(t, manager)
  456. commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  457. if _, err := manager.AmendCommitMessage(repoPath, " ", &CommitRequest{}); err == nil {
  458. t.Errorf("AmendCommitMessage() with a blank message = nil error, want an error")
  459. }
  460. }
  461. func TestAmendRefusesDetachedHead(t *testing.T) {
  462. manager := newTestManager(t)
  463. repoPath := newTestRepo(t, manager)
  464. first := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  465. commitFile(t, manager, repoPath, "a.txt", "two\n", "second")
  466. if err := manager.CheckoutCommit(repoPath, first); err != nil {
  467. t.Fatalf("CheckoutCommit() returned error: %v", err)
  468. }
  469. if _, err := manager.AmendCommitMessage(repoPath, "new message", &CommitRequest{}); err == nil {
  470. t.Errorf("AmendCommitMessage() on a detached HEAD = nil error, want an error")
  471. }
  472. }
  473. func TestParseResetMode(t *testing.T) {
  474. tests := []struct {
  475. name string
  476. mode string
  477. wantError bool
  478. }{
  479. {name: "soft", mode: "soft"},
  480. {name: "mixed", mode: "mixed"},
  481. {name: "hard", mode: "hard"},
  482. {name: "empty defaults to mixed", mode: ""},
  483. {name: "uppercase", mode: "HARD"},
  484. {name: "padded", mode: " soft "},
  485. {name: "unknown", mode: "medium", wantError: true},
  486. }
  487. for _, test := range tests {
  488. t.Run(test.name, func(t *testing.T) {
  489. _, err := parseResetMode(test.mode)
  490. if test.wantError && err == nil {
  491. t.Errorf("parseResetMode(%q) = nil error, want an error", test.mode)
  492. }
  493. if !test.wantError && err != nil {
  494. t.Errorf("parseResetMode(%q) returned error: %v", test.mode, err)
  495. }
  496. })
  497. }
  498. }
  499. func TestHistoryActionsOnNonRepo(t *testing.T) {
  500. manager := newTestManager(t)
  501. plain := t.TempDir()
  502. dummy := strings.Repeat("a", 40)
  503. if err := manager.CheckoutCommit(plain, dummy); err != ErrNotARepo {
  504. t.Errorf("CheckoutCommit() on a plain folder = %v, want ErrNotARepo", err)
  505. }
  506. if err := manager.CreateTag(plain, "v1", dummy, "", "T", "t@e.com"); err != ErrNotARepo {
  507. t.Errorf("CreateTag() on a plain folder = %v, want ErrNotARepo", err)
  508. }
  509. if _, err := manager.TagsForCommit(plain, dummy); err != ErrNotARepo {
  510. t.Errorf("TagsForCommit() on a plain folder = %v, want ErrNotARepo", err)
  511. }
  512. }