github.com/purpleclay/gitz@v0.8.2-0.20240515052600-43f80eea2fe1/log_test.go (about)

     1  package git_test
     2  
     3  import (
     4  	"bufio"
     5  	"os"
     6  	"strings"
     7  	"testing"
     8  
     9  	git "github.com/purpleclay/gitz"
    10  	"github.com/purpleclay/gitz/gittest"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestLog(t *testing.T) {
    16  	log := `fix: parsing error when input string is too long
    17  ci: extend the existing build workflow to include integration tests
    18  docs: create initial mkdocs material documentation
    19  feat: add second operation to library
    20  feat: add first operation to library`
    21  
    22  	gittest.InitRepository(t, gittest.WithLog(log))
    23  
    24  	client, _ := git.NewClient()
    25  	out, err := client.Log()
    26  	require.NoError(t, err)
    27  
    28  	lines := countLogLines(t, out.Raw)
    29  	require.Equal(t, 6, lines)
    30  	require.Equal(t, 6, len(out.Commits))
    31  
    32  	assert.Contains(t, out.Raw, "fix: parsing error when input string is too long")
    33  	assert.Equal(t, "fix: parsing error when input string is too long", out.Commits[0].Message)
    34  
    35  	assert.Contains(t, out.Raw, "ci: extend the existing build workflow to include integration tests")
    36  	assert.Equal(t, "ci: extend the existing build workflow to include integration tests", out.Commits[1].Message)
    37  
    38  	assert.Contains(t, out.Raw, "docs: create initial mkdocs material documentation")
    39  	assert.Equal(t, "docs: create initial mkdocs material documentation", out.Commits[2].Message)
    40  
    41  	assert.Contains(t, out.Raw, "feat: add second operation to library")
    42  	assert.Equal(t, "feat: add second operation to library", out.Commits[3].Message)
    43  
    44  	assert.Contains(t, out.Raw, "feat: add first operation to library")
    45  	assert.Equal(t, "feat: add first operation to library", out.Commits[4].Message)
    46  
    47  	assert.Contains(t, out.Raw, gittest.InitialCommit)
    48  	assert.Equal(t, gittest.InitialCommit, out.Commits[5].Message)
    49  }
    50  
    51  func TestLogMultiLineCommit(t *testing.T) {
    52  	log := `> feat: this is a commit that will
    53  be spread
    54  across multiple lines`
    55  	gittest.InitRepository(t, gittest.WithLog(log))
    56  
    57  	client, _ := git.NewClient()
    58  	out, err := client.Log()
    59  
    60  	require.NoError(t, err)
    61  	require.Len(t, out.Commits, 2)
    62  	assert.Equal(t, `feat: this is a commit that will
    63  be spread
    64  across multiple lines`, out.Commits[0].Message)
    65  	assert.Equal(t, gittest.InitialCommit, out.Commits[1].Message)
    66  }
    67  
    68  // A utility function that will scan the raw output from a git log and
    69  // count all of the returned log lines. It is important to note, that
    70  // in some scenarios the log will contain the [gittest.InitialCommit]
    71  // used to initialize the repository
    72  func countLogLines(t *testing.T, log string) int {
    73  	t.Helper()
    74  	scanner := bufio.NewScanner(strings.NewReader(log))
    75  	scanner.Split(bufio.ScanLines)
    76  
    77  	count := 0
    78  	for scanner.Scan() {
    79  		count++
    80  	}
    81  
    82  	return count
    83  }
    84  
    85  func TestLogValidateParsing(t *testing.T) {
    86  	gittest.InitRepository(t)
    87  
    88  	client, _ := git.NewClient()
    89  	out, err := client.Log()
    90  
    91  	require.NoError(t, err)
    92  	require.Len(t, out.Commits, 1)
    93  
    94  	assert.Equal(t, gittest.InitialCommit, out.Commits[0].Message)
    95  
    96  	lastCommit := gittest.LastCommit(t)
    97  	assert.Equal(t, lastCommit.Hash, out.Commits[0].Hash)
    98  	assert.Equal(t, lastCommit.AbbrevHash, out.Commits[0].AbbrevHash)
    99  }
   100  
   101  func TestLogError(t *testing.T) {
   102  	nonWorkingDirectory(t)
   103  
   104  	client, _ := git.NewClient()
   105  	_, err := client.Log()
   106  
   107  	require.Error(t, err)
   108  }
   109  
   110  func nonWorkingDirectory(t *testing.T) {
   111  	t.Helper()
   112  
   113  	current, err := os.Getwd()
   114  	require.NoError(t, err)
   115  
   116  	tmpDir := t.TempDir()
   117  	require.NoError(t, os.Chdir(tmpDir))
   118  
   119  	t.Cleanup(func() {
   120  		require.NoError(t, os.Chdir(current))
   121  	})
   122  }
   123  
   124  func TestLogWithRawOnly(t *testing.T) {
   125  	gittest.InitRepository(t)
   126  
   127  	client, _ := git.NewClient()
   128  	out, err := client.Log(git.WithRawOnly())
   129  
   130  	require.NoError(t, err)
   131  	assert.Empty(t, out.Commits)
   132  }
   133  
   134  func TestLogWithRef(t *testing.T) {
   135  	log := `(tag: 0.1.1) fix: unexpected bytes in message while parsing
   136  (tag: 0.1.0) docs: create initial mkdocs material documentation
   137  feat: build exciting new library`
   138  
   139  	gittest.InitRepository(t, gittest.WithLog(log))
   140  
   141  	client, _ := git.NewClient()
   142  	out, err := client.Log(git.WithRef("0.1.0"))
   143  	require.NoError(t, err)
   144  
   145  	lines := countLogLines(t, out.Raw)
   146  	require.Equal(t, 3, lines)
   147  
   148  	assert.Contains(t, out.Raw, "docs: create initial mkdocs material documentation")
   149  	assert.Contains(t, out.Raw, "feat: build exciting new library")
   150  	assert.Contains(t, out.Raw, gittest.InitialCommit)
   151  }
   152  
   153  func TestLogWithRefRange(t *testing.T) {
   154  	log := `(tag: 0.2.0) feat: add ability to filter on results
   155  (tag: 0.1.1) fix: unexpected bytes in message while parsing
   156  docs: update documentation to include fix
   157  (tag: 0.1.0) docs: create initial mkdocs material documentation
   158  feat: build exciting new library`
   159  
   160  	tests := []struct {
   161  		name            string
   162  		fromRef         string
   163  		toRef           string
   164  		expectedLines   int
   165  		expectedCommits []string
   166  	}{
   167  		{
   168  			name:          "FromAndToRefsProvided",
   169  			fromRef:       "0.1.1",
   170  			toRef:         "0.1.0",
   171  			expectedLines: 2,
   172  			expectedCommits: []string{
   173  				"fix: unexpected bytes in message while parsing",
   174  				"docs: update documentation to include fix",
   175  			},
   176  		},
   177  		{
   178  			name:          "FromRefOnly",
   179  			fromRef:       "0.1.0",
   180  			expectedLines: 3,
   181  			expectedCommits: []string{
   182  				"docs: create initial mkdocs material documentation",
   183  				"feat: build exciting new library",
   184  				gittest.InitialCommit,
   185  			},
   186  		},
   187  		{
   188  			name:          "ToRefOnly",
   189  			toRef:         "0.1.1",
   190  			expectedLines: 1,
   191  			expectedCommits: []string{
   192  				"feat: add ability to filter on results",
   193  			},
   194  		},
   195  		{
   196  			name:          "TrimsWhitespaceAroundRefs",
   197  			fromRef:       "  0.2.0  ",
   198  			toRef:         "  0.1.1  ",
   199  			expectedLines: 1,
   200  			expectedCommits: []string{
   201  				"feat: add ability to filter on results",
   202  			},
   203  		},
   204  	}
   205  	for _, tt := range tests {
   206  		t.Run(tt.name, func(t *testing.T) {
   207  			gittest.InitRepository(t, gittest.WithLog(log))
   208  
   209  			client, _ := git.NewClient()
   210  			out, err := client.Log(git.WithRefRange(tt.fromRef, tt.toRef))
   211  			require.NoError(t, err)
   212  
   213  			lines := countLogLines(t, out.Raw)
   214  			require.Equal(t, tt.expectedLines, lines)
   215  
   216  			for _, commit := range tt.expectedCommits {
   217  				require.Contains(t, out.Raw, commit)
   218  			}
   219  		})
   220  	}
   221  }
   222  
   223  func TestLogWithPaths(t *testing.T) {
   224  	gittest.InitRepository(t,
   225  		gittest.WithLocalCommits("this should not appear in the log"),
   226  		gittest.WithStagedFiles("dir1/a.txt", "dir2/b.txt"))
   227  
   228  	gittest.Commit(t, "feat: include both dir1/a.txt and dir2/b.txt")
   229  	overwriteFile(t, "dir1/a.txt", "Help, I have been overwritten!")
   230  	gittest.StageFile(t, "dir1/a.txt")
   231  	gittest.Commit(t, "fix: changed file dir1/a.txt")
   232  
   233  	client, _ := git.NewClient()
   234  	out, err := client.Log(git.WithPaths("dir1"))
   235  	require.NoError(t, err)
   236  
   237  	lines := countLogLines(t, out.Raw)
   238  	require.Equal(t, 2, lines)
   239  	assert.Contains(t, out.Raw, "fix: changed file dir1/a.txt")
   240  	assert.Contains(t, out.Raw, "feat: include both dir1/a.txt and dir2/b.txt")
   241  }
   242  
   243  func overwriteFile(t *testing.T, path, content string) {
   244  	t.Helper()
   245  
   246  	fi, err := os.Create(path)
   247  	require.NoError(t, err)
   248  	defer fi.Close()
   249  
   250  	fi.WriteString(content)
   251  	require.NoError(t, fi.Sync())
   252  }
   253  
   254  func TestLogWithSkip(t *testing.T) {
   255  	log := `feat: add options to support skipping of log entries
   256  ci: improve github workflow
   257  docs: update documentation to include new option`
   258  
   259  	tests := []struct {
   260  		name            string
   261  		skipCount       int
   262  		expectedLines   int
   263  		expectedCommits []string
   264  	}{
   265  		{
   266  			name:          "IsIgnored",
   267  			skipCount:     0,
   268  			expectedLines: 4,
   269  			expectedCommits: []string{
   270  				"feat: add options to support skipping of log entries",
   271  				"ci: improve github workflow",
   272  				"docs: update documentation to include new option",
   273  				gittest.InitialCommit,
   274  			},
   275  		},
   276  		{
   277  			name:          "SkipFirstEntry",
   278  			skipCount:     1,
   279  			expectedLines: 3,
   280  			expectedCommits: []string{
   281  				"ci: improve github workflow",
   282  				"docs: update documentation to include new option",
   283  				gittest.InitialCommit,
   284  			},
   285  		},
   286  		{
   287  			name:          "SkipExceedsLogLength",
   288  			skipCount:     10,
   289  			expectedLines: 0,
   290  		},
   291  	}
   292  	for _, tt := range tests {
   293  		t.Run(tt.name, func(t *testing.T) {
   294  			gittest.InitRepository(t, gittest.WithLog(log))
   295  
   296  			client, _ := git.NewClient()
   297  			out, err := client.Log(git.WithSkip(tt.skipCount))
   298  			require.NoError(t, err)
   299  
   300  			lines := countLogLines(t, out.Raw)
   301  			require.Equal(t, tt.expectedLines, lines)
   302  
   303  			for _, commit := range tt.expectedCommits {
   304  				require.Contains(t, out.Raw, commit)
   305  			}
   306  		})
   307  	}
   308  }
   309  
   310  func TestLogWithTake(t *testing.T) {
   311  	log := `feat: add options to support taking n number of log entries
   312  docs: update documentation to include new option`
   313  
   314  	tests := []struct {
   315  		name            string
   316  		takeCount       int
   317  		expectedLines   int
   318  		expectedCommits []string
   319  	}{
   320  		{
   321  			name:          "TakeZero",
   322  			takeCount:     0,
   323  			expectedLines: 0,
   324  		},
   325  		{
   326  			name:          "TakeLatestEntry",
   327  			takeCount:     1,
   328  			expectedLines: 1,
   329  			expectedCommits: []string{
   330  				"feat: add options to support taking n number of log entries",
   331  			},
   332  		},
   333  		{
   334  			name:          "TakeExceedsLogLength",
   335  			takeCount:     10,
   336  			expectedLines: 3,
   337  			expectedCommits: []string{
   338  				"feat: add options to support taking n number of log entries",
   339  				"docs: update documentation to include new option",
   340  				gittest.InitialCommit,
   341  			},
   342  		},
   343  	}
   344  	for _, tt := range tests {
   345  		t.Run(tt.name, func(t *testing.T) {
   346  			gittest.InitRepository(t, gittest.WithLog(log))
   347  
   348  			client, _ := git.NewClient()
   349  			out, err := client.Log(git.WithTake(tt.takeCount))
   350  			require.NoError(t, err)
   351  
   352  			lines := countLogLines(t, out.Raw)
   353  			require.Equal(t, tt.expectedLines, lines)
   354  
   355  			for _, commit := range tt.expectedCommits {
   356  				require.Contains(t, out.Raw, commit)
   357  			}
   358  		})
   359  	}
   360  }
   361  
   362  func TestLogWithSkipAndTake(t *testing.T) {
   363  	log := `feat: include options to filter logs between points in time and from a specific directory
   364  feat: include option for generating an annotated tag
   365  feat: add basic git push support
   366  feat: detect if git is available when creating a new client
   367  chore: simplify feature request issue
   368  feat: add basic git log operation support
   369  feat: add support for a basic file staging operation
   370  feat: add basic support for git commit operations
   371  chore(deps): bump dependabot/fetch-metadata from 1.3.5 to 1.3.6
   372  feat: add basic support for git tag operations
   373  chore: configure basic structure of project`
   374  
   375  	gittest.InitRepository(t, gittest.WithLog(log))
   376  
   377  	client, _ := git.NewClient()
   378  	out, err := client.Log(git.WithSkip(3), git.WithTake(2))
   379  	require.NoError(t, err)
   380  
   381  	lines := countLogLines(t, out.Raw)
   382  	require.Equal(t, 2, lines)
   383  	assert.Contains(t, out.Raw, "feat: detect if git is available when creating a new client")
   384  	assert.Contains(t, out.Raw, "chore: simplify feature request issue")
   385  }
   386  
   387  func TestWithGrep(t *testing.T) {
   388  	log := `feat: add option to match commits by regex
   389  docs: document how to use new option for commit matching
   390  chore(deps): bump dependabot/fetch-metadata from 1.3.5 to 1.3.6`
   391  
   392  	gittest.InitRepository(t, gittest.WithLog(log))
   393  
   394  	client, _ := git.NewClient()
   395  	out, err := client.Log(git.WithGrep("regex$", "option"))
   396  	require.NoError(t, err)
   397  
   398  	lines := countLogLines(t, out.Raw)
   399  	require.Equal(t, 2, lines)
   400  	assert.Contains(t, out.Raw, "feat: add option to match commits by regex")
   401  	assert.Contains(t, out.Raw, "docs: document how to use new option for commit matching")
   402  }
   403  
   404  func TestWithGrepAndMatchAll(t *testing.T) {
   405  	log := `feat: add option to match commits by regex
   406  docs: document how to use new option for commit matching
   407  chore(deps): bump dependabot/fetch-metadata from 1.3.5 to 1.3.6`
   408  
   409  	gittest.InitRepository(t, gittest.WithLog(log))
   410  
   411  	client, _ := git.NewClient()
   412  	out, err := client.Log(git.WithGrep("regex$", "option"), git.WithMatchAll())
   413  	require.NoError(t, err)
   414  
   415  	lines := countLogLines(t, out.Raw)
   416  	require.Equal(t, 1, lines)
   417  	assert.Contains(t, out.Raw, "feat: add option to match commits by regex")
   418  }
   419  
   420  func TestWithInvertGrep(t *testing.T) {
   421  	log := `feat: add option to match commits by regex
   422  docs: document how to use new option for commit matching
   423  chore(deps): bump dependabot/fetch-metadata from 1.3.5 to 1.3.6`
   424  
   425  	gittest.InitRepository(t, gittest.WithLog(log))
   426  
   427  	client, _ := git.NewClient()
   428  	out, err := client.Log(git.WithInvertGrep("regex$", "option"))
   429  	require.NoError(t, err)
   430  
   431  	lines := countLogLines(t, out.Raw)
   432  	require.Equal(t, 2, lines)
   433  	assert.Contains(t, out.Raw, "chore(deps): bump dependabot/fetch-metadata from 1.3.5 to 1.3.6")
   434  	assert.Contains(t, out.Raw, gittest.InitialCommit)
   435  }