github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/pod-utils/clone/clone_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package clone
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"os/exec"
    23  	"reflect"
    24  	"testing"
    25  	"time"
    26  
    27  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  )
    31  
    32  func TestPathForRefs(t *testing.T) {
    33  	var testCases = []struct {
    34  		name     string
    35  		refs     prowapi.Refs
    36  		expected string
    37  	}{
    38  		{
    39  			name: "literal override",
    40  			refs: prowapi.Refs{
    41  				PathAlias: "alias",
    42  			},
    43  			expected: "base/src/alias",
    44  		},
    45  		{
    46  			name: "default generated",
    47  			refs: prowapi.Refs{
    48  				Org:  "org",
    49  				Repo: "repo",
    50  			},
    51  			expected: "base/src/github.com/org/repo",
    52  		},
    53  	}
    54  
    55  	for _, testCase := range testCases {
    56  		if actual, expected := PathForRefs("base", testCase.refs), testCase.expected; actual != expected {
    57  			t.Errorf("%s: expected path %q, got %q", testCase.name, expected, actual)
    58  		}
    59  	}
    60  }
    61  
    62  func boolPtr(v bool) *bool {
    63  	return &v
    64  }
    65  
    66  func TestCommandsForRefs(t *testing.T) {
    67  	fakeTimestamp := 100200300
    68  	var testCases = []struct {
    69  		name                                       string
    70  		refs                                       prowapi.Refs
    71  		dir, gitUserName, gitUserEmail, cookiePath string
    72  		env                                        []string
    73  		expectedBase                               []runnable
    74  		expectedPull                               []runnable
    75  		authUser                                   string
    76  		authToken                                  string
    77  	}{
    78  		{
    79  			name: "simplest case, minimal refs",
    80  			refs: prowapi.Refs{
    81  				Org:     "org",
    82  				Repo:    "repo",
    83  				BaseRef: "master",
    84  			},
    85  			dir: "/go",
    86  			expectedBase: []runnable{
    87  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
    88  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
    89  				retryCommand{
    90  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
    91  					fetchRetries,
    92  				},
    93  				retryCommand{
    94  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
    95  					fetchRetries,
    96  				},
    97  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
    98  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
    99  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   100  			},
   101  			expectedPull: []runnable{
   102  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   103  			},
   104  		},
   105  		{
   106  			name: "simple case, root dir",
   107  			refs: prowapi.Refs{
   108  				Org:     "org",
   109  				Repo:    "repo",
   110  				BaseRef: "master",
   111  			},
   112  			dir: "/",
   113  			expectedBase: []runnable{
   114  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/src/github.com/org/repo"}},
   115  				cloneCommand{dir: "/src/github.com/org/repo", command: "git", args: []string{"init"}},
   116  				retryCommand{
   117  					cloneCommand{dir: "/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   118  					fetchRetries,
   119  				},
   120  				retryCommand{
   121  					cloneCommand{dir: "/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   122  					fetchRetries,
   123  				},
   124  				cloneCommand{dir: "/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   125  				cloneCommand{dir: "/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   126  				cloneCommand{dir: "/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   127  			},
   128  			expectedPull: []runnable{
   129  				cloneCommand{dir: "/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   130  			},
   131  		},
   132  		{
   133  			name: "minimal refs with git user name",
   134  			refs: prowapi.Refs{
   135  				Org:     "org",
   136  				Repo:    "repo",
   137  				BaseRef: "master",
   138  			},
   139  			gitUserName: "user",
   140  			dir:         "/go",
   141  			expectedBase: []runnable{
   142  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   143  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   144  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"config", "user.name", "user"}},
   145  				retryCommand{
   146  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   147  					fetchRetries,
   148  				},
   149  				retryCommand{
   150  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   151  					fetchRetries,
   152  				},
   153  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   154  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   155  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   156  			},
   157  			expectedPull: []runnable{
   158  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   159  			},
   160  		},
   161  		{
   162  			name: "minimal refs with git user email",
   163  			refs: prowapi.Refs{
   164  				Org:     "org",
   165  				Repo:    "repo",
   166  				BaseRef: "master",
   167  			},
   168  			gitUserEmail: "user@go.com",
   169  			dir:          "/go",
   170  			expectedBase: []runnable{
   171  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   172  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   173  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"config", "user.email", "user@go.com"}},
   174  				retryCommand{
   175  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   176  					fetchRetries,
   177  				},
   178  				retryCommand{
   179  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   180  					fetchRetries,
   181  				},
   182  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   183  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   184  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   185  			},
   186  			expectedPull: []runnable{
   187  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   188  			},
   189  		},
   190  		{
   191  			name: "minimal refs with http cookie file (skip submodules)",
   192  			refs: prowapi.Refs{
   193  				Org:            "org",
   194  				Repo:           "repo",
   195  				BaseRef:        "master",
   196  				SkipSubmodules: true,
   197  			},
   198  			cookiePath: "/cookie.txt",
   199  			dir:        "/go",
   200  			expectedBase: []runnable{
   201  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   202  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   203  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"config", "http.cookiefile", "/cookie.txt"}},
   204  				retryCommand{
   205  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   206  					fetchRetries,
   207  				},
   208  				retryCommand{
   209  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   210  					fetchRetries,
   211  				},
   212  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   213  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   214  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   215  			},
   216  		},
   217  		{
   218  			name: "minimal refs with http cookie file",
   219  			refs: prowapi.Refs{
   220  				Org:     "org",
   221  				Repo:    "repo",
   222  				BaseRef: "master",
   223  			},
   224  			cookiePath: "/cookie.txt",
   225  			dir:        "/go",
   226  			expectedBase: []runnable{
   227  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   228  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   229  				retryCommand{
   230  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   231  					fetchRetries,
   232  				},
   233  				retryCommand{
   234  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   235  					fetchRetries,
   236  				},
   237  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   238  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   239  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   240  			},
   241  			expectedPull: []runnable{
   242  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   243  			},
   244  		},
   245  		{
   246  			name: "minimal refs with no submodules",
   247  			refs: prowapi.Refs{
   248  				Org:            "org",
   249  				Repo:           "repo",
   250  				BaseRef:        "master",
   251  				SkipSubmodules: true,
   252  			},
   253  			dir: "/go",
   254  			expectedBase: []runnable{
   255  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   256  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   257  				retryCommand{
   258  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   259  					fetchRetries,
   260  				},
   261  				retryCommand{
   262  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   263  					fetchRetries,
   264  				},
   265  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   266  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   267  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   268  			},
   269  			expectedPull: nil,
   270  		},
   271  		{
   272  			name:      "minimal refs with oauth token",
   273  			authToken: "12345678",
   274  			refs: prowapi.Refs{
   275  				Org:     "org",
   276  				Repo:    "repo",
   277  				BaseRef: "master",
   278  			},
   279  			dir: "/go",
   280  			expectedBase: []runnable{
   281  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   282  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   283  				retryCommand{
   284  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://12345678:x-oauth-basic@github.com/org/repo.git", "--tags", "--prune"}},
   285  					fetchRetries,
   286  				},
   287  				retryCommand{
   288  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://12345678:x-oauth-basic@github.com/org/repo.git", "master"}},
   289  					fetchRetries,
   290  				},
   291  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   292  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   293  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   294  			},
   295  			expectedPull: []runnable{
   296  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   297  			},
   298  		},
   299  		{
   300  			name:      "minimal refs with GitHub App user and token",
   301  			authUser:  "x-access-token",
   302  			authToken: "xxxxx",
   303  			refs: prowapi.Refs{
   304  				Org:     "org",
   305  				Repo:    "repo",
   306  				BaseRef: "master",
   307  			},
   308  			dir: "/go",
   309  			expectedBase: []runnable{
   310  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   311  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   312  				retryCommand{
   313  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://x-access-token:xxxxx@github.com/org/repo.git", "--tags", "--prune"}},
   314  					fetchRetries,
   315  				},
   316  				retryCommand{
   317  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://x-access-token:xxxxx@github.com/org/repo.git", "master"}},
   318  					fetchRetries,
   319  				},
   320  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   321  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   322  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   323  			},
   324  			expectedPull: []runnable{
   325  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   326  			},
   327  		},
   328  		{
   329  			name:      "minimal refs with GitHub App user and token and clone URI override",
   330  			authUser:  "x-access-token",
   331  			authToken: "xxxxx",
   332  			refs: prowapi.Refs{
   333  				Org:      "org",
   334  				Repo:     "repo",
   335  				BaseRef:  "master",
   336  				CloneURI: "git@github.com:owner/repo",
   337  			},
   338  			dir: "/go",
   339  			expectedBase: []runnable{
   340  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   341  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   342  				retryCommand{
   343  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "git@github.com:owner/repo", "--tags", "--prune"}},
   344  					fetchRetries,
   345  				},
   346  				retryCommand{
   347  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "git@github.com:owner/repo", "master"}},
   348  					fetchRetries,
   349  				},
   350  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   351  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   352  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   353  			},
   354  			expectedPull: []runnable{
   355  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   356  			},
   357  		},
   358  		{
   359  			name: "refs with clone URI override",
   360  			refs: prowapi.Refs{
   361  				Org:      "org",
   362  				Repo:     "repo",
   363  				BaseRef:  "master",
   364  				CloneURI: "internet.com",
   365  			},
   366  			dir: "/go",
   367  			expectedBase: []runnable{
   368  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   369  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   370  				retryCommand{
   371  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "internet.com", "--tags", "--prune"}},
   372  					fetchRetries,
   373  				},
   374  				retryCommand{
   375  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "internet.com", "master"}},
   376  					fetchRetries,
   377  				},
   378  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   379  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   380  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   381  			},
   382  			expectedPull: []runnable{
   383  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   384  			},
   385  		},
   386  		{
   387  			name:      "refs with clone URI override and oauth token specified",
   388  			authToken: "12345678",
   389  			refs: prowapi.Refs{
   390  				Org:      "org",
   391  				Repo:     "repo",
   392  				BaseRef:  "master",
   393  				CloneURI: "https://internet.com",
   394  			},
   395  			dir: "/go",
   396  			expectedBase: []runnable{
   397  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   398  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   399  				retryCommand{
   400  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://12345678:x-oauth-basic@internet.com", "--tags", "--prune"}},
   401  					fetchRetries,
   402  				},
   403  				retryCommand{
   404  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://12345678:x-oauth-basic@internet.com", "master"}},
   405  					fetchRetries,
   406  				},
   407  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   408  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   409  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   410  			},
   411  			expectedPull: []runnable{
   412  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   413  			},
   414  		},
   415  		{
   416  			name: "refs with path alias",
   417  			refs: prowapi.Refs{
   418  				Org:       "org",
   419  				Repo:      "repo",
   420  				BaseRef:   "master",
   421  				PathAlias: "my/favorite/dir",
   422  				RepoLink:  "https://github.com/org/repo",
   423  			},
   424  			dir: "/go",
   425  			expectedBase: []runnable{
   426  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/my/favorite/dir"}},
   427  				cloneCommand{dir: "/go/src/my/favorite/dir", command: "git", args: []string{"init"}},
   428  				retryCommand{
   429  					cloneCommand{dir: "/go/src/my/favorite/dir", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   430  					fetchRetries,
   431  				},
   432  				retryCommand{
   433  					cloneCommand{dir: "/go/src/my/favorite/dir", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   434  					fetchRetries,
   435  				},
   436  				cloneCommand{dir: "/go/src/my/favorite/dir", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   437  				cloneCommand{dir: "/go/src/my/favorite/dir", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   438  				cloneCommand{dir: "/go/src/my/favorite/dir", command: "git", args: []string{"checkout", "master"}},
   439  			},
   440  			expectedPull: []runnable{
   441  				cloneCommand{dir: "/go/src/my/favorite/dir", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   442  			},
   443  		},
   444  		{
   445  			name: "refs with specific base sha",
   446  			refs: prowapi.Refs{
   447  				Org:     "org",
   448  				Repo:    "repo",
   449  				BaseRef: "master",
   450  				BaseSHA: "abcdef",
   451  			},
   452  			dir: "/go",
   453  			expectedBase: []runnable{
   454  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   455  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   456  				retryCommand{
   457  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   458  					fetchRetries,
   459  				},
   460  				retryCommand{
   461  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "abcdef"}},
   462  					fetchRetries,
   463  				},
   464  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "abcdef"}},
   465  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "abcdef"}},
   466  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   467  			},
   468  			expectedPull: []runnable{
   469  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   470  			},
   471  		},
   472  		{
   473  			name: "refs with simple pr ref",
   474  			refs: prowapi.Refs{
   475  				Org:     "org",
   476  				Repo:    "repo",
   477  				BaseRef: "master",
   478  				Pulls: []prowapi.Pull{
   479  					{Number: 1},
   480  				},
   481  			},
   482  			dir: "/go",
   483  			expectedBase: []runnable{
   484  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   485  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   486  				retryCommand{
   487  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   488  					fetchRetries,
   489  				},
   490  				retryCommand{
   491  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   492  					fetchRetries,
   493  				},
   494  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   495  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   496  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   497  			},
   498  			expectedPull: []runnable{
   499  				retryCommand{
   500  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "pull/1/head"}},
   501  					fetchRetries,
   502  				},
   503  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"merge", "--no-ff", "FETCH_HEAD"}, env: gitTimestampEnvs(fakeTimestamp + 1)},
   504  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   505  			},
   506  		},
   507  		{
   508  			name: "refs with simple pr ref, sha takes precedence over virtual pull ref",
   509  			refs: prowapi.Refs{
   510  				Org:     "org",
   511  				Repo:    "repo",
   512  				BaseRef: "master",
   513  				Pulls: []prowapi.Pull{
   514  					{Number: 1, SHA: "pull-1-sha"},
   515  				},
   516  			},
   517  			dir: "/go",
   518  			expectedBase: []runnable{
   519  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   520  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   521  				retryCommand{
   522  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   523  					fetchRetries,
   524  				},
   525  				retryCommand{
   526  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   527  					fetchRetries,
   528  				},
   529  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   530  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   531  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   532  			},
   533  			expectedPull: []runnable{
   534  				retryCommand{
   535  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "pull-1-sha"}},
   536  					fetchRetries,
   537  				},
   538  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"merge", "--no-ff", "pull-1-sha"}, env: gitTimestampEnvs(fakeTimestamp + 1)},
   539  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   540  			},
   541  		},
   542  		{
   543  			name: "refs with pr ref override",
   544  			refs: prowapi.Refs{
   545  				Org:     "org",
   546  				Repo:    "repo",
   547  				BaseRef: "master",
   548  				Pulls: []prowapi.Pull{
   549  					{Number: 1, Ref: "pull-me"},
   550  				},
   551  			},
   552  			dir: "/go",
   553  			expectedBase: []runnable{
   554  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   555  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   556  				retryCommand{
   557  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   558  					fetchRetries,
   559  				},
   560  				retryCommand{
   561  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   562  					fetchRetries,
   563  				},
   564  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   565  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   566  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   567  			},
   568  			expectedPull: []runnable{
   569  				retryCommand{
   570  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "pull-me"}},
   571  					fetchRetries,
   572  				},
   573  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"merge", "--no-ff", "FETCH_HEAD"}, env: gitTimestampEnvs(fakeTimestamp + 1)},
   574  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   575  			},
   576  		},
   577  		{
   578  			name: "blobless refs with pr ref override",
   579  			refs: prowapi.Refs{
   580  				Org:     "org",
   581  				Repo:    "repo",
   582  				BaseRef: "master",
   583  				Pulls: []prowapi.Pull{
   584  					{Number: 1, Ref: "pull-me"},
   585  				},
   586  				BloblessFetch: boolPtr(true),
   587  			},
   588  			dir: "/go",
   589  			expectedBase: []runnable{
   590  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   591  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   592  				retryCommand{
   593  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "--filter=blob:none", "https://github.com/org/repo.git", "--tags", "--prune"}},
   594  					fetchRetries,
   595  				},
   596  				retryCommand{
   597  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "--filter=blob:none", "https://github.com/org/repo.git", "master"}},
   598  					fetchRetries,
   599  				},
   600  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   601  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   602  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   603  			},
   604  			expectedPull: []runnable{
   605  				retryCommand{
   606  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "--filter=blob:none", "https://github.com/org/repo.git", "pull-me"}},
   607  					fetchRetries,
   608  				},
   609  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"merge", "--no-ff", "FETCH_HEAD"}, env: gitTimestampEnvs(fakeTimestamp + 1)},
   610  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   611  			},
   612  		},
   613  		{
   614  			name: "refs with pr ref with specific sha",
   615  			refs: prowapi.Refs{
   616  				Org:     "org",
   617  				Repo:    "repo",
   618  				BaseRef: "master",
   619  				Pulls: []prowapi.Pull{
   620  					{Number: 1, SHA: "abcdef"},
   621  				},
   622  			},
   623  			dir: "/go",
   624  			expectedBase: []runnable{
   625  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   626  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   627  				retryCommand{
   628  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   629  					fetchRetries,
   630  				},
   631  				retryCommand{
   632  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   633  					fetchRetries,
   634  				},
   635  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   636  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   637  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   638  			},
   639  			expectedPull: []runnable{
   640  				retryCommand{
   641  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "abcdef"}},
   642  					fetchRetries,
   643  				},
   644  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"merge", "--no-ff", "abcdef"}, env: gitTimestampEnvs(fakeTimestamp + 1)},
   645  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   646  			},
   647  		},
   648  		{
   649  			name: "refs with multiple simple pr refs",
   650  			refs: prowapi.Refs{
   651  				Org:     "org",
   652  				Repo:    "repo",
   653  				BaseRef: "master",
   654  				Pulls: []prowapi.Pull{
   655  					{Number: 1},
   656  					{Number: 2},
   657  				},
   658  			},
   659  			dir: "/go",
   660  			expectedBase: []runnable{
   661  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.com/org/repo"}},
   662  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"init"}},
   663  				retryCommand{
   664  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "--tags", "--prune"}},
   665  					fetchRetries,
   666  				},
   667  				retryCommand{
   668  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "master"}},
   669  					fetchRetries,
   670  				},
   671  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "FETCH_HEAD"}},
   672  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "FETCH_HEAD"}},
   673  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   674  			},
   675  			expectedPull: []runnable{
   676  				retryCommand{
   677  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "pull/1/head"}},
   678  					fetchRetries,
   679  				},
   680  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"merge", "--no-ff", "FETCH_HEAD"}, env: gitTimestampEnvs(fakeTimestamp + 1)},
   681  				retryCommand{
   682  					cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"fetch", "https://github.com/org/repo.git", "pull/2/head"}},
   683  					fetchRetries,
   684  				},
   685  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"merge", "--no-ff", "FETCH_HEAD"}, env: gitTimestampEnvs(fakeTimestamp + 2)},
   686  				cloneCommand{dir: "/go/src/github.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   687  			},
   688  		},
   689  		{
   690  			name: "refs with repo link",
   691  			refs: prowapi.Refs{
   692  				Org:      "org",
   693  				Repo:     "repo",
   694  				BaseRef:  "master",
   695  				BaseSHA:  "abcdef",
   696  				RepoLink: "https://github.enterprise.com/org/repo",
   697  			},
   698  			dir: "/go",
   699  			expectedBase: []runnable{
   700  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.enterprise.com/org/repo"}},
   701  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"init"}},
   702  				retryCommand{
   703  					cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"fetch", "https://github.enterprise.com/org/repo.git", "--tags", "--prune"}},
   704  					fetchRetries,
   705  				},
   706  				retryCommand{
   707  					cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"fetch", "https://github.enterprise.com/org/repo.git", "abcdef"}},
   708  					fetchRetries,
   709  				},
   710  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"checkout", "abcdef"}},
   711  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "abcdef"}},
   712  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   713  			},
   714  			expectedPull: []runnable{
   715  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   716  			},
   717  		},
   718  		{
   719  			name: "support fetching repo with multiple heads",
   720  			refs: prowapi.Refs{
   721  				Org:           "org",
   722  				Repo:          "repo",
   723  				BaseRef:       "master",
   724  				BaseSHA:       "abcdef",
   725  				RepoLink:      "https://github.enterprise.com/org/repo",
   726  				SkipFetchHead: true, // no single HEAD
   727  			},
   728  			dir: "/go",
   729  			expectedBase: []runnable{
   730  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.enterprise.com/org/repo"}},
   731  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"init"}},
   732  				retryCommand{
   733  					cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"fetch", "https://github.enterprise.com/org/repo.git", "abcdef"}},
   734  					fetchRetries,
   735  				},
   736  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"checkout", "abcdef"}},
   737  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "abcdef"}},
   738  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   739  			},
   740  			expectedPull: []runnable{
   741  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   742  			},
   743  		},
   744  		{
   745  			name: "support shallow fetching repo with multiple heads",
   746  			refs: prowapi.Refs{
   747  				Org:           "org",
   748  				Repo:          "repo",
   749  				BaseRef:       "master",
   750  				BaseSHA:       "abcdef",
   751  				RepoLink:      "https://github.enterprise.com/org/repo",
   752  				SkipFetchHead: true, // no single HEAD
   753  				CloneDepth:    2,
   754  			},
   755  			dir: "/go",
   756  			expectedBase: []runnable{
   757  				cloneCommand{dir: "/", command: "mkdir", args: []string{"-p", "/go/src/github.enterprise.com/org/repo"}},
   758  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"init"}},
   759  				retryCommand{
   760  					cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"fetch", "--depth", "2", "https://github.enterprise.com/org/repo.git", "abcdef"}},
   761  					fetchRetries,
   762  				},
   763  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"checkout", "abcdef"}},
   764  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"branch", "--force", "master", "abcdef"}},
   765  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"checkout", "master"}},
   766  			},
   767  			expectedPull: []runnable{
   768  				cloneCommand{dir: "/go/src/github.enterprise.com/org/repo", command: "git", args: []string{"submodule", "update", "--init", "--recursive"}},
   769  			},
   770  		},
   771  	}
   772  
   773  	allow := cmp.AllowUnexported(retryCommand{}, cloneCommand{})
   774  	for _, testCase := range testCases {
   775  		t.Run(testCase.name, func(t *testing.T) {
   776  			g := gitCtxForRefs(testCase.refs, testCase.dir, testCase.env, testCase.authUser, testCase.authToken)
   777  			actualBase := g.commandsForBaseRef(testCase.refs, testCase.gitUserName, testCase.gitUserEmail, testCase.cookiePath)
   778  			if diff := cmp.Diff(actualBase, testCase.expectedBase, allow); diff != "" {
   779  				t.Errorf("commandsForBaseRef() got unexpected diff (-got, +want):\n%s", diff)
   780  			}
   781  
   782  			actualPull := g.commandsForPullRefs(testCase.refs, fakeTimestamp)
   783  			if diff := cmp.Diff(actualPull, testCase.expectedPull, allow); diff != "" {
   784  				t.Errorf("commandsForPullRefs() got unexpected diff (-got, +want):\n%s", diff)
   785  			}
   786  		})
   787  	}
   788  }
   789  
   790  func TestGitHeadTimestamp(t *testing.T) {
   791  	fakeTimestamp := 987654321
   792  	fakeGitDir, err := makeFakeGitRepo(t, fakeTimestamp)
   793  	if err != nil {
   794  		t.Errorf("error creating fake git dir: %v", err)
   795  	}
   796  
   797  	var testCases = []struct {
   798  		name        string
   799  		dir         string
   800  		noPath      bool
   801  		expected    int
   802  		expectError bool
   803  	}{
   804  		{
   805  			name:        "root - no git",
   806  			dir:         "/",
   807  			expected:    0,
   808  			expectError: true,
   809  		},
   810  		{
   811  			name:        "fake git repo",
   812  			dir:         fakeGitDir,
   813  			expected:    fakeTimestamp,
   814  			expectError: false,
   815  		},
   816  		{
   817  			name:        "fake git repo but no git binary",
   818  			dir:         fakeGitDir,
   819  			noPath:      true,
   820  			expected:    0,
   821  			expectError: true,
   822  		},
   823  	}
   824  	origCwd, err := os.Getwd()
   825  	if err != nil {
   826  		t.Errorf("failed getting cwd: %v", err)
   827  	}
   828  	origPath := os.Getenv("PATH")
   829  	for _, testCase := range testCases {
   830  		t.Run(testCase.name, func(t *testing.T) {
   831  			if err := os.Chdir(testCase.dir); err != nil {
   832  				t.Errorf("%s: failed to chdir to %s: %v", testCase.name, testCase.dir, err)
   833  			}
   834  			if testCase.noPath {
   835  				if err := os.Unsetenv("PATH"); err != nil {
   836  					t.Errorf("%s: failed to unset PATH: %v", testCase.name, err)
   837  				}
   838  			}
   839  			g := gitCtx{
   840  				cloneDir: testCase.dir,
   841  			}
   842  			timestamp, err := g.gitHeadTimestamp()
   843  			if timestamp != testCase.expected {
   844  				t.Errorf("%s: timestamp %d does not match expected timestamp %d", testCase.name, timestamp, testCase.expected)
   845  			}
   846  			if (err == nil && testCase.expectError) || (err != nil && !testCase.expectError) {
   847  				t.Errorf("%s: expect error is %v but received error %v", testCase.name, testCase.expectError, err)
   848  			}
   849  			if err := os.Chdir(origCwd); err != nil {
   850  				t.Errorf("%s: failed to chdir to original cwd %s: %v", testCase.name, origCwd, err)
   851  			}
   852  			if testCase.noPath {
   853  				if err := os.Setenv("PATH", origPath); err != nil {
   854  					t.Errorf("%s: failed to set PATH to original: %v", testCase.name, err)
   855  				}
   856  			}
   857  		})
   858  	}
   859  }
   860  
   861  // makeFakeGitRepo creates a fake git repo with a constant digest and timestamp.
   862  func makeFakeGitRepo(t *testing.T, fakeTimestamp int) (string, error) {
   863  	fakeGitDir := t.TempDir()
   864  	cmds := [][]string{
   865  		{"git", "init"},
   866  		{"git", "config", "user.email", "test@test.test"},
   867  		{"git", "config", "user.name", "test test"},
   868  		{"touch", "a_file"},
   869  		{"git", "add", "a_file"},
   870  		{"git", "commit", "-m", "adding a_file"},
   871  	}
   872  	for _, cmd := range cmds {
   873  		c := exec.Command(cmd[0], cmd[1:]...)
   874  		c.Dir = fakeGitDir
   875  		c.Env = append(os.Environ(), gitTimestampEnvs(fakeTimestamp)...)
   876  		if err := c.Run(); err != nil {
   877  			return fakeGitDir, err
   878  		}
   879  	}
   880  	return fakeGitDir, nil
   881  }
   882  
   883  func TestCensorToken(t *testing.T) {
   884  	testCases := []struct {
   885  		id       string
   886  		token    string
   887  		msg      string
   888  		expected string
   889  	}{
   890  		{
   891  			id:       "no token",
   892  			msg:      "git fetch https://github.com/kubernetes/test-infra.git",
   893  			expected: "git fetch https://github.com/kubernetes/test-infra.git",
   894  		},
   895  		{
   896  			id:       "with token",
   897  			token:    "123456789",
   898  			msg:      "git fetch 123456789:x-oauth-basic@https://github.com/kubernetes/test-infra.git",
   899  			expected: "git fetch CENSORED:x-oauth-basic@https://github.com/kubernetes/test-infra.git",
   900  		},
   901  		{
   902  			id:    "git output with token",
   903  			token: "123456789",
   904  			msg: `
   905  Cloning into 'test-infa'...
   906  remote: Invalid username or password.
   907  fatal: Authentication failed for 'https://123456789@github.com/kubernetes/test-infa/'
   908  `,
   909  			expected: `
   910  Cloning into 'test-infa'...
   911  remote: Invalid username or password.
   912  fatal: Authentication failed for 'https://CENSORED@github.com/kubernetes/test-infa/'
   913  `,
   914  		},
   915  	}
   916  
   917  	for _, tc := range testCases {
   918  		t.Run(tc.id, func(t *testing.T) {
   919  			censoredMsg := censorToken(tc.msg, tc.token)
   920  			if !reflect.DeepEqual(censoredMsg, tc.expected) {
   921  				t.Fatalf("expected: %s got %s", tc.expected, censoredMsg)
   922  			}
   923  		})
   924  	}
   925  }
   926  
   927  // fakeRunner will pass run() if called when calls == 1,
   928  // decrementing calls each time.
   929  type fakeRunner struct {
   930  	calls int
   931  }
   932  
   933  func (fr *fakeRunner) run() (string, string, error) {
   934  	fr.calls--
   935  	if fr.calls == 0 {
   936  		return "command", "output", nil
   937  	}
   938  	return "command", "output", fmt.Errorf("calls: %d", fr.calls)
   939  }
   940  
   941  func TestGitFetch(t *testing.T) {
   942  	const short = time.Nanosecond
   943  	command := func(calls int, retries ...time.Duration) retryCommand {
   944  		return retryCommand{
   945  			runnable: &fakeRunner{calls},
   946  			retries:  retries,
   947  		}
   948  	}
   949  	cases := []struct {
   950  		name string
   951  		retryCommand
   952  		err bool
   953  	}{
   954  		{
   955  			name:         "works without retires",
   956  			retryCommand: command(1),
   957  		},
   958  		{
   959  			name:         "errors if first call fails without retries",
   960  			retryCommand: command(0),
   961  			err:          true,
   962  		},
   963  		{
   964  			name:         "works with retries (without retrying)",
   965  			retryCommand: command(1, short),
   966  		},
   967  		{
   968  			name:         "works with retries (retrying)",
   969  			retryCommand: command(2, short),
   970  		},
   971  		{
   972  			name:         "errors without retries if first call fails",
   973  			retryCommand: command(2),
   974  			err:          true,
   975  		},
   976  		{
   977  			name:         "errors with retries when all retries are consumed",
   978  			retryCommand: command(3, short),
   979  			err:          true,
   980  		},
   981  	}
   982  
   983  	for _, tc := range cases {
   984  		t.Run(tc.name, func(t *testing.T) {
   985  			_, _, err := tc.run()
   986  			switch {
   987  			case err != nil:
   988  				if !tc.err {
   989  					t.Errorf("unexpected error: %v", err)
   990  				}
   991  			case tc.err:
   992  				t.Error("failed to received expected error")
   993  			}
   994  		})
   995  	}
   996  }
   997  
   998  func TestCloneCommandString(t *testing.T) {
   999  	tests := []struct {
  1000  		name string
  1001  		cc   cloneCommand
  1002  		want string
  1003  	}{
  1004  		{
  1005  			name: "empty",
  1006  			cc:   cloneCommand{},
  1007  			want: "PWD=   ",
  1008  		},
  1009  		{
  1010  			name: "base",
  1011  			cc: cloneCommand{
  1012  				dir:     "abc",
  1013  				env:     []string{"d=e", "f=g"},
  1014  				command: "echo",
  1015  				args:    []string{"hij klm"},
  1016  			},
  1017  			want: "PWD=abc d=e f=g echo hij klm",
  1018  		},
  1019  	}
  1020  
  1021  	for _, tc := range tests {
  1022  		tc := tc
  1023  		t.Run(tc.name, func(t *testing.T) {
  1024  			want, got := tc.want, tc.cc.String()
  1025  			if diff := cmp.Diff(want, got); diff != "" {
  1026  				t.Errorf("mismatch. want(-), got(+):\n%s", diff)
  1027  			}
  1028  		})
  1029  	}
  1030  }