1 package git_test // to avoid import cycles
11 . "github.com/git-lfs/git-lfs/git"
12 test "github.com/git-lfs/git-lfs/t/cmd/util"
13 "github.com/stretchr/testify/assert"
16 func TestRefString(t *testing.T) {
17 const sha = "0000000000000000000000000000000000000000"
18 for s, r := range map[string]*Ref{
19 "refs/heads/master": {
21 Type: RefTypeLocalBranch,
24 "refs/remotes/origin/master": {
25 Name: "origin/master",
26 Type: RefTypeRemoteBranch,
29 "refs/remotes/tags/v1.0.0": {
31 Type: RefTypeRemoteTag,
36 Type: RefTypeLocalTag,
50 assert.Equal(t, s, r.Refspec())
54 func TestParseRefs(t *testing.T) {
55 tests := map[string]RefType{
56 "refs/heads": RefTypeLocalBranch,
57 "refs/tags": RefTypeLocalTag,
58 "refs/remotes/tags": RefTypeRemoteTag,
59 "refs/remotes": RefTypeRemoteBranch,
62 for prefix, expectedType := range tests {
63 r := ParseRef(prefix+"/branch", "abc123")
64 assert.Equal(t, "abc123", r.Sha, "prefix: "+prefix)
65 assert.Equal(t, "branch", r.Name, "prefix: "+prefix)
66 assert.Equal(t, expectedType, r.Type, "prefix: "+prefix)
69 r := ParseRef("refs/foo/branch", "abc123")
70 assert.Equal(t, "abc123", r.Sha, "prefix: refs/foo")
71 assert.Equal(t, "refs/foo/branch", r.Name, "prefix: refs/foo")
72 assert.Equal(t, RefTypeOther, r.Type, "prefix: refs/foo")
74 r = ParseRef("HEAD", "abc123")
75 assert.Equal(t, "abc123", r.Sha, "prefix: HEAD")
76 assert.Equal(t, "HEAD", r.Name, "prefix: HEAD")
77 assert.Equal(t, RefTypeHEAD, r.Type, "prefix: HEAD")
80 func TestCurrentRefAndCurrentRemoteRef(t *testing.T) {
81 repo := test.NewRepo(t)
88 // test commits; we'll just modify the same file each time since we're
89 // only interested in branches
90 inputs := []*test.CommitInput{
92 Files: []*test.FileInput{
93 {Filename: "file1.txt", Size: 20},
98 Files: []*test.FileInput{
99 {Filename: "file1.txt", Size: 25},
103 ParentBranches: []string{"master"}, // back on master
104 Files: []*test.FileInput{
105 {Filename: "file1.txt", Size: 30},
109 NewBranch: "branch3",
110 Files: []*test.FileInput{
111 {Filename: "file1.txt", Size: 32},
116 outputs := repo.AddCommits(inputs)
118 // last commit was on branch3
119 gitConf := repo.GitConfig()
120 ref, err := CurrentRef()
122 assert.Equal(t, &Ref{
124 Type: RefTypeLocalBranch,
127 test.RunGitCommand(t, true, "checkout", "master")
128 ref, err = CurrentRef()
130 assert.Equal(t, &Ref{
132 Type: RefTypeLocalBranch,
136 repo.AddRemote("origin")
137 test.RunGitCommand(t, true, "push", "-u", "origin", "master:someremotebranch")
138 ref, err = gitConf.CurrentRemoteRef()
140 assert.Equal(t, &Ref{
141 Name: "origin/someremotebranch",
142 Type: RefTypeRemoteBranch,
146 refname, err := gitConf.RemoteRefNameForCurrentBranch()
148 assert.Equal(t, "refs/remotes/origin/someremotebranch", refname)
150 ref, err = ResolveRef(outputs[2].Sha)
152 assert.Equal(t, &Ref{
153 Name: outputs[2].Sha,
159 func TestRecentBranches(t *testing.T) {
160 repo := test.NewRepo(t)
168 // test commits; we'll just modify the same file each time since we're
169 // only interested in branches & dates
170 inputs := []*test.CommitInput{
172 CommitDate: now.AddDate(0, 0, -20),
173 Files: []*test.FileInput{
174 {Filename: "file1.txt", Size: 20},
178 CommitDate: now.AddDate(0, 0, -15),
179 NewBranch: "excluded_branch", // new branch & tag but too old
180 Tags: []string{"excluded_tag"},
181 Files: []*test.FileInput{
182 {Filename: "file1.txt", Size: 25},
186 CommitDate: now.AddDate(0, 0, -12),
187 ParentBranches: []string{"master"}, // back on master
188 Files: []*test.FileInput{
189 {Filename: "file1.txt", Size: 30},
193 CommitDate: now.AddDate(0, 0, -6),
194 NewBranch: "included_branch", // new branch within 7 day limit
195 Files: []*test.FileInput{
196 {Filename: "file1.txt", Size: 32},
200 CommitDate: now.AddDate(0, 0, -3),
201 NewBranch: "included_branch_2", // new branch within 7 day limit
202 Files: []*test.FileInput{
203 {Filename: "file1.txt", Size: 36},
207 // Final commit, current date/time
208 ParentBranches: []string{"master"}, // back on master
209 Files: []*test.FileInput{
210 {Filename: "file1.txt", Size: 21},
214 outputs := repo.AddCommits(inputs)
216 // Add a couple of remotes and push some branches
217 repo.AddRemote("origin")
218 repo.AddRemote("upstream")
220 test.RunGitCommand(t, true, "push", "origin", "master")
221 test.RunGitCommand(t, true, "push", "origin", "excluded_branch")
222 test.RunGitCommand(t, true, "push", "origin", "included_branch")
223 test.RunGitCommand(t, true, "push", "upstream", "master")
224 test.RunGitCommand(t, true, "push", "upstream", "included_branch_2")
226 // Recent, local only
227 refs, err := RecentBranches(now.AddDate(0, 0, -7), false, "")
228 assert.Equal(t, nil, err)
229 expectedRefs := []*Ref{
232 Type: RefTypeLocalBranch,
236 Name: "included_branch_2",
237 Type: RefTypeLocalBranch,
241 Name: "included_branch",
242 Type: RefTypeLocalBranch,
246 assert.Equal(t, expectedRefs, refs, "Refs should be correct")
248 // Recent, remotes too (all of them)
249 refs, err = RecentBranches(now.AddDate(0, 0, -7), true, "")
250 assert.Equal(t, nil, err)
251 expectedRefs = []*Ref{
254 Type: RefTypeLocalBranch,
258 Name: "included_branch_2",
259 Type: RefTypeLocalBranch,
263 Name: "included_branch",
264 Type: RefTypeLocalBranch,
268 Name: "upstream/master",
269 Type: RefTypeRemoteBranch,
273 Name: "upstream/included_branch_2",
274 Type: RefTypeRemoteBranch,
278 Name: "origin/master",
279 Type: RefTypeRemoteBranch,
283 Name: "origin/included_branch",
284 Type: RefTypeRemoteBranch,
288 // Need to sort for consistent comparison
289 sort.Sort(test.RefsByName(expectedRefs))
290 sort.Sort(test.RefsByName(refs))
291 assert.Equal(t, expectedRefs, refs, "Refs should be correct")
293 // Recent, only single remote
294 refs, err = RecentBranches(now.AddDate(0, 0, -7), true, "origin")
295 assert.Equal(t, nil, err)
296 expectedRefs = []*Ref{
299 Type: RefTypeLocalBranch,
303 Name: "origin/master",
304 Type: RefTypeRemoteBranch,
308 Name: "included_branch_2",
309 Type: RefTypeLocalBranch,
313 Name: "included_branch",
314 Type: RefTypeLocalBranch,
318 Name: "origin/included_branch",
319 Type: RefTypeRemoteBranch,
323 // Need to sort for consistent comparison
324 sort.Sort(test.RefsByName(expectedRefs))
325 sort.Sort(test.RefsByName(refs))
326 assert.Equal(t, expectedRefs, refs, "Refs should be correct")
329 func TestResolveEmptyCurrentRef(t *testing.T) {
330 repo := test.NewRepo(t)
337 _, err := CurrentRef()
338 assert.NotEqual(t, nil, err)
341 func TestWorkTrees(t *testing.T) {
343 if !IsGitVersionAtLeast("2.5.0") {
347 repo := test.NewRepo(t)
354 // test commits; we'll just modify the same file each time since we're
355 // only interested in branches & dates
356 inputs := []*test.CommitInput{
358 Files: []*test.FileInput{
359 {Filename: "file1.txt", Size: 20},
363 NewBranch: "branch2",
364 Files: []*test.FileInput{
365 {Filename: "file1.txt", Size: 25},
369 NewBranch: "branch3",
370 ParentBranches: []string{"master"}, // back on master
371 Files: []*test.FileInput{
372 {Filename: "file1.txt", Size: 30},
376 NewBranch: "branch4",
377 ParentBranches: []string{"master"}, // back on master
378 Files: []*test.FileInput{
379 {Filename: "file1.txt", Size: 40},
383 outputs := repo.AddCommits(inputs)
384 // Checkout master again otherwise can't create a worktree from branch4 if we're on it here
385 test.RunGitCommand(t, true, "checkout", "master")
387 // We can create worktrees as subfolders for convenience
388 // Each one is checked out to a different branch
389 // Note that we *won't* create one for branch3
390 test.RunGitCommand(t, true, "worktree", "add", "branch2_wt", "branch2")
391 test.RunGitCommand(t, true, "worktree", "add", "branch4_wt", "branch4")
393 refs, err := GetAllWorkTreeHEADs(filepath.Join(repo.Path, ".git"))
394 assert.Equal(t, nil, err)
395 expectedRefs := []*Ref{
398 Type: RefTypeLocalBranch,
403 Type: RefTypeLocalBranch,
408 Type: RefTypeLocalBranch,
412 // Need to sort for consistent comparison
413 sort.Sort(test.RefsByName(expectedRefs))
414 sort.Sort(test.RefsByName(refs))
415 assert.Equal(t, expectedRefs, refs, "Refs should be correct")
418 func TestVersionCompare(t *testing.T) {
419 assert.True(t, IsVersionAtLeast("2.6.0", "2.6.0"))
420 assert.True(t, IsVersionAtLeast("2.6.0", "2.6"))
421 assert.True(t, IsVersionAtLeast("2.6.0", "2"))
422 assert.True(t, IsVersionAtLeast("2.6.10", "2.6.5"))
423 assert.True(t, IsVersionAtLeast("2.8.1", "2.7.2"))
425 assert.False(t, IsVersionAtLeast("1.6.0", "2"))
426 assert.False(t, IsVersionAtLeast("2.5.0", "2.6"))
427 assert.False(t, IsVersionAtLeast("2.5.0", "2.5.1"))
428 assert.False(t, IsVersionAtLeast("2.5.2", "2.5.10"))
431 func TestGitAndRootDirs(t *testing.T) {
432 repo := test.NewRepo(t)
439 git, root, err := GitAndRootDirs()
444 expected, err := os.Stat(git)
449 actual, err := os.Stat(filepath.Join(root, ".git"))
454 assert.True(t, os.SameFile(expected, actual))
457 func TestGetTrackedFiles(t *testing.T) {
458 repo := test.NewRepo(t)
465 // test commits; we'll just modify the same file each time since we're
466 // only interested in branches
467 inputs := []*test.CommitInput{
469 Files: []*test.FileInput{
470 {Filename: "file1.txt", Size: 20},
471 {Filename: "file2.txt", Size: 20},
472 {Filename: "folder1/file10.txt", Size: 20},
473 {Filename: "folder1/anotherfile.txt", Size: 20},
477 Files: []*test.FileInput{
478 {Filename: "file3.txt", Size: 20},
479 {Filename: "file4.txt", Size: 20},
480 {Filename: "folder2/something.txt", Size: 20},
481 {Filename: "folder2/folder3/deep.txt", Size: 20},
485 repo.AddCommits(inputs)
487 tracked, err := GetTrackedFiles("*.txt")
489 sort.Strings(tracked) // for direct comparison
490 fulllist := []string{"file1.txt", "file2.txt", "file3.txt", "file4.txt", "folder1/anotherfile.txt", "folder1/file10.txt", "folder2/folder3/deep.txt", "folder2/something.txt"}
491 assert.Equal(t, fulllist, tracked)
493 tracked, err = GetTrackedFiles("*file*.txt")
495 sort.Strings(tracked)
496 sublist := []string{"file1.txt", "file2.txt", "file3.txt", "file4.txt", "folder1/anotherfile.txt", "folder1/file10.txt"}
497 assert.Equal(t, sublist, tracked)
499 tracked, err = GetTrackedFiles("folder1/*")
501 sort.Strings(tracked)
502 sublist = []string{"folder1/anotherfile.txt", "folder1/file10.txt"}
503 assert.Equal(t, sublist, tracked)
505 tracked, err = GetTrackedFiles("folder2/*")
507 sort.Strings(tracked)
508 sublist = []string{"folder2/folder3/deep.txt", "folder2/something.txt"}
509 assert.Equal(t, sublist, tracked)
513 tracked, err = GetTrackedFiles("*.txt")
515 sort.Strings(tracked)
516 sublist = []string{"anotherfile.txt", "file10.txt"}
517 assert.Equal(t, sublist, tracked)
520 // absolute paths only includes matches in repo root
521 tracked, err = GetTrackedFiles("/*.txt")
523 sort.Strings(tracked)
524 assert.Equal(t, []string{"file1.txt", "file2.txt", "file3.txt", "file4.txt"}, tracked)
526 // Test includes staged but uncommitted files
527 ioutil.WriteFile("z_newfile.txt", []byte("Hello world"), 0644)
528 test.RunGitCommand(t, true, "add", "z_newfile.txt")
529 tracked, err = GetTrackedFiles("*.txt")
531 sort.Strings(tracked)
532 fulllist = append(fulllist, "z_newfile.txt")
533 assert.Equal(t, fulllist, tracked)
535 // Test includes modified files (not staged)
536 ioutil.WriteFile("file1.txt", []byte("Modifications"), 0644)
537 tracked, err = GetTrackedFiles("*.txt")
539 sort.Strings(tracked)
540 assert.Equal(t, fulllist, tracked)
542 // Test includes modified files (staged)
543 test.RunGitCommand(t, true, "add", "file1.txt")
544 tracked, err = GetTrackedFiles("*.txt")
546 sort.Strings(tracked)
547 assert.Equal(t, fulllist, tracked)
549 // Test excludes deleted files (not committed)
550 test.RunGitCommand(t, true, "rm", "file2.txt")
551 tracked, err = GetTrackedFiles("*.txt")
553 sort.Strings(tracked)
554 deletedlist := []string{"file1.txt", "file3.txt", "file4.txt", "folder1/anotherfile.txt", "folder1/file10.txt", "folder2/folder3/deep.txt", "folder2/something.txt", "z_newfile.txt"}
555 assert.Equal(t, deletedlist, tracked)
559 func TestLocalRefs(t *testing.T) {
560 repo := test.NewRepo(t)
567 repo.AddCommits([]*test.CommitInput{
569 Files: []*test.FileInput{
570 {Filename: "file1.txt", Size: 20},
575 ParentBranches: []string{"master"},
576 Files: []*test.FileInput{
577 {Filename: "file1.txt", Size: 20},
582 test.RunGitCommand(t, true, "tag", "v1")
584 refs, err := LocalRefs()
589 actual := make(map[string]bool)
590 for _, r := range refs {
591 t.Logf("REF: %s", r.Name)
594 t.Errorf("Local HEAD ref: %v", r)
596 t.Errorf("Stash or unknown ref: %v", r)
597 case RefTypeRemoteBranch, RefTypeRemoteTag:
598 t.Errorf("Remote ref: %v", r)
600 actual[r.Name] = true
604 expected := []string{"master", "branch", "v1"}
606 for _, refname := range expected {
610 t.Errorf("could not find ref %q", refname)
614 if found != len(expected) {
615 t.Errorf("Unexpected local refs: %v", actual)
619 func TestGetFilesChanges(t *testing.T) {
620 repo := test.NewRepo(t)
627 commits := repo.AddCommits([]*test.CommitInput{
629 Files: []*test.FileInput{
630 {Filename: "file1.txt", Size: 20},
634 Files: []*test.FileInput{
635 {Filename: "file1.txt", Size: 25},
636 {Filename: "file2.txt", Size: 20},
637 {Filename: "folder/file3.txt", Size: 10},
639 Tags: []string{"tag1"},
642 NewBranch: "abranch",
643 ParentBranches: []string{"master"},
644 Files: []*test.FileInput{
645 {Filename: "file1.txt", Size: 30},
646 {Filename: "file4.txt", Size: 40},
651 expected0to1 := []string{"file1.txt", "file2.txt", "folder/file3.txt"}
652 expected1to2 := []string{"file1.txt", "file4.txt"}
653 expected0to2 := []string{"file1.txt", "file2.txt", "file4.txt", "folder/file3.txt"}
655 changes, err := GetFilesChanged(commits[0].Sha, commits[1].Sha)
657 assert.Equal(t, expected0to1, changes)
659 changes, err = GetFilesChanged(commits[0].Sha, "tag1")
661 assert.Equal(t, expected0to1, changes)
663 changes, err = GetFilesChanged(commits[0].Sha, "abranch")
665 assert.Equal(t, expected0to2, changes)
667 changes, err = GetFilesChanged("tag1", "abranch")
669 assert.Equal(t, expected1to2, changes)
671 _, err = GetFilesChanged("tag1", "nonexisting")
672 assert.NotNil(t, err)
673 _, err = GetFilesChanged("nonexisting", "tag1")
674 assert.NotNil(t, err)
675 // Test Single arg version
676 changes, err = GetFilesChanged(commits[1].Sha, "")
678 assert.Equal(t, expected0to1, changes)
679 changes, err = GetFilesChanged("abranch", "")
681 assert.Equal(t, expected1to2, changes)
685 func TestValidateRemoteURL(t *testing.T) {
686 assert.Nil(t, ValidateRemoteURL("https://github.com/git-lfs/git-lfs"))
687 assert.Nil(t, ValidateRemoteURL("http://github.com/git-lfs/git-lfs"))
688 assert.Nil(t, ValidateRemoteURL("git://github.com/git-lfs/git-lfs"))
689 assert.Nil(t, ValidateRemoteURL("ssh://git@github.com/git-lfs/git-lfs"))
690 assert.Nil(t, ValidateRemoteURL("ssh://git@github.com:22/git-lfs/git-lfs"))
691 assert.Nil(t, ValidateRemoteURL("git@github.com:git-lfs/git-lfs"))
692 assert.Nil(t, ValidateRemoteURL("git@server:/absolute/path.git"))
693 assert.NotNil(t, ValidateRemoteURL("ftp://git@github.com/git-lfs/git-lfs"))
696 func TestRefTypeKnownPrefixes(t *testing.T) {
697 for typ, expected := range map[RefType]struct {
701 RefTypeLocalBranch: {"refs/heads", true},
702 RefTypeRemoteBranch: {"refs/remotes", true},
703 RefTypeLocalTag: {"refs/tags", true},
704 RefTypeRemoteTag: {"refs/remotes/tags", true},
705 RefTypeHEAD: {"", false},
706 RefTypeOther: {"", false},
708 prefix, ok := typ.Prefix()
710 assert.Equal(t, expected.Prefix, prefix)
711 assert.Equal(t, expected.Ok, ok)