@@ -109,6 +109,7 @@ const defaultDirMode = os.FileMode(0775) // subject to umask
109
109
type repoSync struct {
110
110
cmd string // the git command to run
111
111
root absPath // absolute path to the root directory
112
+ localRepo absPath // absolute path to the local repo
112
113
remoteRepo string // remote repo to sync
113
114
ref string // the ref to sync
114
115
depth int // for shallow sync
@@ -618,6 +619,7 @@ func main() {
618
619
cmd : * flGitCmd ,
619
620
root : absRoot ,
620
621
remoteRepo : * flRepo ,
622
+ localRepo : absRoot .Join (".repo" ),
621
623
ref : * flRef ,
622
624
depth : * flDepth ,
623
625
submodules : submodulesMode (* flSubmodules ),
@@ -1076,40 +1078,37 @@ func (git *repoSync) initRepo(ctx context.Context) error {
1076
1078
needGitInit := false
1077
1079
1078
1080
// Check out the git root, and see if it is already usable.
1079
- _ , err := os .Stat (git .root .String ())
1081
+ _ , err := os .Stat (git .localRepo .String ())
1080
1082
switch {
1081
1083
case os .IsNotExist (err ):
1082
1084
// Probably the first sync. defaultDirMode ensures that this is usable
1083
1085
// as a volume when the consumer isn't running as the same UID.
1084
- git .log .V (1 ).Info ("repo directory does not exist, creating it" , "path" , git .root )
1085
- if err := os .MkdirAll (git .root .String (), defaultDirMode ); err != nil {
1086
+ git .log .V (1 ).Info ("repo directory does not exist, creating it" , "path" , git .localRepo )
1087
+ if err := os .MkdirAll (git .localRepo .String (), defaultDirMode ); err != nil {
1086
1088
return err
1087
1089
}
1088
1090
needGitInit = true
1089
1091
case err != nil :
1090
1092
return err
1091
1093
default :
1092
1094
// Make sure the directory we found is actually usable.
1093
- git .log .V (3 ).Info ("repo directory exists" , "path" , git .root )
1095
+ git .log .V (3 ).Info ("repo directory exists" , "path" , git .localRepo )
1094
1096
if git .sanityCheckRepo (ctx ) {
1095
- git .log .V (4 ).Info ("repo directory is valid" , "path" , git .root )
1097
+ git .log .V (4 ).Info ("repo directory is valid" , "path" , git .localRepo )
1096
1098
} else {
1097
- // Maybe a previous run crashed? Git won't use this dir. We remove
1098
- // the contents rather than the dir itself, because a common use-case
1099
- // is to have a volume mounted at git.root, which makes removing it
1100
- // impossible.
1101
- git .log .V (0 ).Info ("repo directory was empty or failed checks" , "path" , git .root )
1102
- if err := removeDirContents (git .root , git .log ); err != nil {
1103
- return fmt .Errorf ("can't wipe unusable root directory: %w" , err )
1099
+ // Maybe a previous run crashed? Git won't use this dir.
1100
+ git .log .V (0 ).Info ("repo directory was empty or failed checks" , "path" , git .localRepo )
1101
+ if err := os .RemoveAll (git .localRepo .String ()); err != nil {
1102
+ return fmt .Errorf ("can't remove unusable repo directory: %w" , err )
1104
1103
}
1105
1104
needGitInit = true
1106
1105
}
1107
1106
}
1108
1107
1109
1108
if needGitInit {
1110
1109
// Running `git init` in an existing repo is safe (according to git docs).
1111
- git .log .V (0 ).Info ("initializing repo directory" , "path" , git .root )
1112
- if _ , _ , err := git .Run (ctx , git .root , "init" , "-b" , "git-sync" ); err != nil {
1110
+ git .log .V (0 ).Info ("initializing repo directory" , "path" , git .localRepo )
1111
+ if _ , _ , err := git .Run (ctx , git .localRepo , "init" , "-b" , "git-sync" ); err != nil {
1113
1112
return err
1114
1113
}
1115
1114
if ! git .sanityCheckRepo (ctx ) {
@@ -1119,17 +1118,17 @@ func (git *repoSync) initRepo(ctx context.Context) error {
1119
1118
1120
1119
// The "origin" remote has special meaning, like in relative-path
1121
1120
// submodules.
1122
- if stdout , stderr , err := git .Run (ctx , git .root , "remote" , "get-url" , "origin" ); err != nil {
1121
+ if stdout , stderr , err := git .Run (ctx , git .localRepo , "remote" , "get-url" , "origin" ); err != nil {
1123
1122
if ! strings .Contains (stderr , "No such remote" ) {
1124
1123
return err
1125
1124
}
1126
1125
// It doesn't exist - make it.
1127
- if _ , _ , err := git .Run (ctx , git .root , "remote" , "add" , "origin" , git .remoteRepo ); err != nil {
1126
+ if _ , _ , err := git .Run (ctx , git .localRepo , "remote" , "add" , "origin" , git .remoteRepo ); err != nil {
1128
1127
return err
1129
1128
}
1130
1129
} else if strings .TrimSpace (stdout ) != git .remoteRepo {
1131
1130
// It exists, but is wrong.
1132
- if _ , _ , err := git .Run (ctx , git .root , "remote" , "set-url" , "origin" , git .remoteRepo ); err != nil {
1131
+ if _ , _ , err := git .Run (ctx , git .localRepo , "remote" , "set-url" , "origin" , git .remoteRepo ); err != nil {
1133
1132
return err
1134
1133
}
1135
1134
}
@@ -1162,32 +1161,32 @@ func (git *repoSync) removeStaleWorktrees() (int, error) {
1162
1161
1163
1162
// sanityCheckRepo tries to make sure that the repo dir is a valid git repository.
1164
1163
func (git * repoSync ) sanityCheckRepo (ctx context.Context ) bool {
1165
- git .log .V (3 ).Info ("sanity-checking git repo" , "repo" , git .root )
1164
+ git .log .V (3 ).Info ("sanity-checking git repo" , "repo" , git .localRepo )
1166
1165
// If it is empty, we are done.
1167
- if empty , err := dirIsEmpty (git .root ); err != nil {
1168
- git .log .Error (err , "can't list repo directory" , "path" , git .root )
1166
+ if empty , err := dirIsEmpty (git .localRepo ); err != nil {
1167
+ git .log .Error (err , "can't list repo directory" , "path" , git .localRepo )
1169
1168
return false
1170
1169
} else if empty {
1171
- git .log .V (3 ).Info ("repo directory is empty" , "path" , git .root )
1170
+ git .log .V (3 ).Info ("repo directory is empty" , "path" , git .localRepo )
1172
1171
return false
1173
1172
}
1174
1173
1175
1174
// Check that this is actually the root of the repo.
1176
- if root , _ , err := git .Run (ctx , git .root , "rev-parse" , "--show-toplevel" ); err != nil {
1177
- git .log .Error (err , "can't get repo toplevel" , "path" , git .root )
1175
+ if root , _ , err := git .Run (ctx , git .localRepo , "rev-parse" , "--show-toplevel" ); err != nil {
1176
+ git .log .Error (err , "can't get repo toplevel" , "path" , git .localRepo )
1178
1177
return false
1179
1178
} else {
1180
1179
root = strings .TrimSpace (root )
1181
- if root != git .root .String () {
1182
- git .log .Error (nil , "repo directory is under another repo" , "path" , git .root , "parent" , root )
1180
+ if root != git .localRepo .String () {
1181
+ git .log .Error (nil , "repo directory is under another repo" , "path" , git .localRepo , "parent" , root )
1183
1182
return false
1184
1183
}
1185
1184
}
1186
1185
1187
1186
// Consistency-check the repo. Don't use --verbose because it can be
1188
1187
// REALLY verbose.
1189
- if _ , _ , err := git .Run (ctx , git .root , "fsck" , "--no-progress" , "--connectivity-only" ); err != nil {
1190
- git .log .Error (err , "repo fsck failed" , "path" , git .root )
1188
+ if _ , _ , err := git .Run (ctx , git .localRepo , "fsck" , "--no-progress" , "--connectivity-only" ); err != nil {
1189
+ git .log .Error (err , "repo fsck failed" , "path" , git .localRepo )
1191
1190
return false
1192
1191
}
1193
1192
@@ -1199,7 +1198,7 @@ func (git *repoSync) sanityCheckRepo(ctx context.Context) bool {
1199
1198
// files checked out - git could have died halfway through and the repo will
1200
1199
// still pass this check.
1201
1200
func (git * repoSync ) sanityCheckWorktree (ctx context.Context , worktree worktree ) bool {
1202
- git .log .V (3 ).Info ("sanity-checking worktree" , "repo" , git .root , "worktree" , worktree )
1201
+ git .log .V (3 ).Info ("sanity-checking worktree" , "repo" , git .localRepo , "worktree" , worktree )
1203
1202
1204
1203
// If it is empty, we are done.
1205
1204
if empty , err := dirIsEmpty (worktree .Path ()); err != nil {
@@ -1239,13 +1238,6 @@ func dirIsEmpty(dir absPath) (bool, error) {
1239
1238
return len (dirents ) == 0 , nil
1240
1239
}
1241
1240
1242
- // removeDirContents iterated the specified dir and removes all contents
1243
- func removeDirContents (dir absPath , log * logging.Logger ) error {
1244
- return removeDirContentsIf (dir , log , func (fi os.FileInfo ) (bool , error ) {
1245
- return true , nil
1246
- })
1247
- }
1248
-
1249
1241
func removeDirContentsIf (dir absPath , log * logging.Logger , fn func (fi os.FileInfo ) (bool , error )) error {
1250
1242
dirents , err := os .ReadDir (dir .String ())
1251
1243
if err != nil {
@@ -1329,7 +1321,7 @@ func (git *repoSync) removeWorktree(ctx context.Context, worktree worktree) erro
1329
1321
if err := os .RemoveAll (worktree .Path ().String ()); err != nil {
1330
1322
return fmt .Errorf ("error removing directory: %w" , err )
1331
1323
}
1332
- if _ , _ , err := git .Run (ctx , git .root , "worktree" , "prune" , "--verbose" ); err != nil {
1324
+ if _ , _ , err := git .Run (ctx , git .localRepo , "worktree" , "prune" , "--verbose" ); err != nil {
1333
1325
return err
1334
1326
}
1335
1327
return nil
@@ -1350,7 +1342,7 @@ func (git *repoSync) createWorktree(ctx context.Context, hash string) (worktree,
1350
1342
}
1351
1343
1352
1344
git .log .V (1 ).Info ("adding worktree" , "path" , worktree .Path (), "hash" , hash )
1353
- _ , _ , err := git .Run (ctx , git .root , "worktree" , "add" , "--force" , "--detach" , worktree .Path ().String (), hash , "--no-checkout" )
1345
+ _ , _ , err := git .Run (ctx , git .localRepo , "worktree" , "add" , "--force" , "--detach" , worktree .Path ().String (), hash , "--no-checkout" )
1354
1346
if err != nil {
1355
1347
return "" , err
1356
1348
}
@@ -1368,7 +1360,7 @@ func (git *repoSync) configureWorktree(ctx context.Context, worktree worktree) e
1368
1360
// using relative paths, so that other containers can use a different volume
1369
1361
// mount name.
1370
1362
rootDotGit := ""
1371
- if rel , err := filepath .Rel (worktree .Path ().String (), git .root .String ()); err != nil {
1363
+ if rel , err := filepath .Rel (worktree .Path ().String (), git .localRepo .String ()); err != nil {
1372
1364
return err
1373
1365
} else {
1374
1366
rootDotGit = filepath .Join (rel , ".git" )
@@ -1380,7 +1372,7 @@ func (git *repoSync) configureWorktree(ctx context.Context, worktree worktree) e
1380
1372
1381
1373
// If sparse checkout is requested, configure git for it, otherwise
1382
1374
// unconfigure it.
1383
- gitInfoPath := filepath .Join (git .root .String (), ".git/worktrees" , hash , "info" )
1375
+ gitInfoPath := filepath .Join (git .localRepo .String (), ".git/worktrees" , hash , "info" )
1384
1376
gitSparseConfigPath := filepath .Join (gitInfoPath , "sparse-checkout" )
1385
1377
if git .sparseFile == "" {
1386
1378
os .RemoveAll (gitSparseConfigPath )
@@ -1461,13 +1453,13 @@ func (git *repoSync) cleanup(ctx context.Context) error {
1461
1453
1462
1454
// Let git know we don't need those old commits any more.
1463
1455
git .log .V (3 ).Info ("pruning worktrees" )
1464
- if _ , _ , err := git .Run (ctx , git .root , "worktree" , "prune" , "--verbose" ); err != nil {
1456
+ if _ , _ , err := git .Run (ctx , git .localRepo , "worktree" , "prune" , "--verbose" ); err != nil {
1465
1457
cleanupErrs = append (cleanupErrs , err )
1466
1458
}
1467
1459
1468
1460
// Expire old refs.
1469
1461
git .log .V (3 ).Info ("expiring unreachable refs" )
1470
- if _ , _ , err := git .Run (ctx , git .root , "reflog" , "expire" , "--expire-unreachable=all" , "--all" ); err != nil {
1462
+ if _ , _ , err := git .Run (ctx , git .localRepo , "reflog" , "expire" , "--expire-unreachable=all" , "--all" ); err != nil {
1471
1463
cleanupErrs = append (cleanupErrs , err )
1472
1464
}
1473
1465
@@ -1483,7 +1475,7 @@ func (git *repoSync) cleanup(ctx context.Context) error {
1483
1475
args = append (args , "--aggressive" )
1484
1476
}
1485
1477
git .log .V (3 ).Info ("running git garbage collection" )
1486
- if _ , _ , err := git .Run (ctx , git .root , args ... ); err != nil {
1478
+ if _ , _ , err := git .Run (ctx , git .localRepo , args ... ); err != nil {
1487
1479
cleanupErrs = append (cleanupErrs , err )
1488
1480
}
1489
1481
}
@@ -1586,7 +1578,7 @@ func (git *repoSync) SyncRepo(ctx context.Context, refreshCreds func(context.Con
1586
1578
// their underlying commit hashes, but has no effect if we fetched a
1587
1579
// branch, plain tag, or hash.
1588
1580
remoteHash := ""
1589
- if output , _ , err := git .Run (ctx , git .root , "rev-parse" , "FETCH_HEAD^{}" ); err != nil {
1581
+ if output , _ , err := git .Run (ctx , git .localRepo , "rev-parse" , "FETCH_HEAD^{}" ); err != nil {
1590
1582
return false , "" , err
1591
1583
} else {
1592
1584
remoteHash = strings .Trim (output , "\n " )
@@ -1618,14 +1610,14 @@ func (git *repoSync) SyncRepo(ctx context.Context, refreshCreds func(context.Con
1618
1610
// Reset the repo (note: not the worktree - that happens later) to the new
1619
1611
// ref. This makes subsequent fetches much less expensive. It uses --soft
1620
1612
// so no files are checked out.
1621
- if _ , _ , err := git .Run (ctx , git .root , "reset" , "--soft" , remoteHash ); err != nil {
1613
+ if _ , _ , err := git .Run (ctx , git .localRepo , "reset" , "--soft" , remoteHash ); err != nil {
1622
1614
return false , "" , err
1623
1615
}
1624
1616
1625
1617
// If we have a new hash, make a new worktree
1626
1618
newWorktree := currentWorktree
1627
1619
if changed {
1628
- // Create a worktree for this hash in git.root .
1620
+ // Create a worktree for this hash.
1629
1621
if wt , err := git .createWorktree (ctx , remoteHash ); err != nil {
1630
1622
return false , "" , err
1631
1623
} else {
@@ -1698,15 +1690,15 @@ func (git *repoSync) fetch(ctx context.Context, ref string) error {
1698
1690
args = append (args , "--unshallow" )
1699
1691
}
1700
1692
}
1701
- if _ , _ , err := git .Run (ctx , git .root , args ... ); err != nil {
1693
+ if _ , _ , err := git .Run (ctx , git .localRepo , args ... ); err != nil {
1702
1694
return err
1703
1695
}
1704
1696
1705
1697
return nil
1706
1698
}
1707
1699
1708
1700
func (git * repoSync ) isShallow (ctx context.Context ) (bool , error ) {
1709
- boolStr , _ , err := git .Run (ctx , git .root , "rev-parse" , "--is-shallow-repository" )
1701
+ boolStr , _ , err := git .Run (ctx , git .localRepo , "rev-parse" , "--is-shallow-repository" )
1710
1702
if err != nil {
1711
1703
return false , fmt .Errorf ("can't determine repo shallowness: %w" , err )
1712
1704
}
0 commit comments