3
3
// See the LICENSE file in the project root for more information
4
4
5
5
using System . Collections . Concurrent ;
6
- using System . Diagnostics ;
7
- using System . Diagnostics . CodeAnalysis ;
8
6
using System . IO . Abstractions ;
9
7
using Elastic . Documentation . Configuration . Assembler ;
10
8
using Elastic . Documentation . Diagnostics ;
11
9
using Elastic . Documentation . LinkIndex ;
12
- using Elastic . Markdown . IO ;
10
+ using Elastic . Documentation . Links ;
13
11
using Microsoft . Extensions . Logging ;
14
12
using ProcNet ;
15
13
using ProcNet . Std ;
@@ -25,29 +23,42 @@ public class AssemblerRepositorySourcer(ILoggerFactory logger, AssembleContext c
25
23
26
24
private RepositorySourcer RepositorySourcer => new ( logger , context . CheckoutDirectory , context . ReadFileSystem , context . Collector ) ;
27
25
28
- public IReadOnlyCollection < Checkout > GetAll ( )
26
+ public CheckoutResult GetAll ( )
29
27
{
30
28
var fs = context . ReadFileSystem ;
31
29
var repositories = Configuration . ReferenceRepositories . Values . Concat < Repository > ( [ Configuration . Narrative ] ) ;
32
30
var checkouts = new List < Checkout > ( ) ;
31
+ var linkRegistrySnapshotPath = Path . Combine ( context . CheckoutDirectory . FullName , CheckoutResult . LinkRegistrySnapshotFileName ) ;
32
+ if ( ! fs . File . Exists ( linkRegistrySnapshotPath ) )
33
+ throw new FileNotFoundException ( "Link-index snapshot not found. Run the clone-all command first." , linkRegistrySnapshotPath ) ;
34
+ var linkRegistrySnapshotStr = File . ReadAllText ( linkRegistrySnapshotPath ) ;
35
+ var linkRegistry = LinkRegistry . Deserialize ( linkRegistrySnapshotStr ) ;
33
36
foreach ( var repo in repositories )
34
37
{
35
38
var checkoutFolder = fs . DirectoryInfo . New ( Path . Combine ( context . CheckoutDirectory . FullName , repo . Name ) ) ;
39
+ IGitRepository gitFacade = new SingleCommitOptimizedGitRepository ( context . Collector , checkoutFolder ) ;
40
+ if ( ! checkoutFolder . Exists )
41
+ {
42
+ context . Collector . EmitError ( checkoutFolder . FullName , $ "'{ repo . Name } ' does not exist in link index checkout directory") ;
43
+ continue ;
44
+ }
45
+ var head = gitFacade . GetCurrentCommit ( ) ;
36
46
var checkout = new Checkout
37
47
{
38
48
Repository = repo ,
39
49
Directory = checkoutFolder ,
40
- //TODO read from links.json and ensure we check out exactly that git reference
41
- //+ validate that git reference belongs to the appropriate branch
42
- HeadReference = Guid . NewGuid ( ) . ToString ( "N" )
50
+ HeadReference = head
43
51
} ;
44
52
checkouts . Add ( checkout ) ;
45
53
}
46
-
47
- return checkouts ;
54
+ return new CheckoutResult
55
+ {
56
+ Checkouts = checkouts ,
57
+ LinkRegistrySnapshot = linkRegistry
58
+ } ;
48
59
}
49
60
50
- public async Task < IReadOnlyCollection < Checkout > > CloneAll ( bool fetchLatest , Cancel ctx = default )
61
+ public async Task < CheckoutResult > CloneAll ( bool fetchLatest , Cancel ctx = default )
51
62
{
52
63
_logger . LogInformation ( "Cloning all repositories for environment {EnvironmentName} using '{ContentSourceStrategy}' content sourcing strategy" ,
53
64
PublishEnvironment . Name ,
@@ -91,8 +102,23 @@ await Task.Run(() =>
91
102
checkouts . Add ( RepositorySourcer . CloneRef ( repo . Value , gitRef , fetchLatest ) ) ;
92
103
} , c ) ;
93
104
} ) . ConfigureAwait ( false ) ;
94
- return checkouts ;
105
+ await context . WriteFileSystem . File . WriteAllTextAsync (
106
+ Path . Combine ( context . CheckoutDirectory . FullName , CheckoutResult . LinkRegistrySnapshotFileName ) ,
107
+ LinkRegistry . Serialize ( linkRegistry ) ,
108
+ ctx
109
+ ) ;
110
+ return new CheckoutResult
111
+ {
112
+ Checkouts = checkouts ,
113
+ LinkRegistrySnapshot = linkRegistry
114
+ } ;
95
115
}
116
+
117
+ public async Task WriteLinkRegistrySnapshot ( LinkRegistry linkRegistrySnapshot , Cancel ctx = default ) => await context . WriteFileSystem . File . WriteAllTextAsync (
118
+ Path . Combine ( context . OutputDirectory . FullName , "docs" , CheckoutResult . LinkRegistrySnapshotFileName ) ,
119
+ LinkRegistry . Serialize ( linkRegistrySnapshot ) ,
120
+ ctx
121
+ ) ;
96
122
}
97
123
98
124
@@ -108,6 +134,7 @@ public class RepositorySourcer(ILoggerFactory logger, IDirectoryInfo checkoutDir
108
134
public Checkout CloneRef ( Repository repository , string gitRef , bool pull = false , int attempt = 1 )
109
135
{
110
136
var checkoutFolder = readFileSystem . DirectoryInfo . New ( Path . Combine ( checkoutDirectory . FullName , repository . Name ) ) ;
137
+ IGitRepository git = new SingleCommitOptimizedGitRepository ( collector , checkoutFolder ) ;
111
138
if ( attempt > 3 )
112
139
{
113
140
collector . EmitError ( "" , $ "Failed to clone repository { repository . Name } @{ gitRef } after 3 attempts") ;
@@ -125,13 +152,13 @@ public Checkout CloneRef(Repository repository, string gitRef, bool pull = false
125
152
checkoutFolder . Create ( ) ;
126
153
checkoutFolder . Refresh ( ) ;
127
154
}
128
- var isGitInitialized = GitInit ( repository , checkoutFolder ) ;
155
+ var isGitInitialized = GitInit ( git , repository ) ;
129
156
string ? head = null ;
130
157
if ( isGitInitialized )
131
158
{
132
159
try
133
160
{
134
- head = Capture ( checkoutFolder , " git" , "rev-parse" , "HEAD" ) ;
161
+ head = git . GetCurrentCommit ( ) ;
135
162
}
136
163
catch ( Exception e )
137
164
{
@@ -147,7 +174,7 @@ public Checkout CloneRef(Repository repository, string gitRef, bool pull = false
147
174
_logger . LogInformation ( "{RepositoryName}: HEAD already at {GitRef}" , repository . Name , gitRef ) ;
148
175
else
149
176
{
150
- FetchAndCheckout ( repository , gitRef , checkoutFolder ) ;
177
+ FetchAndCheckout ( git , repository , gitRef ) ;
151
178
if ( ! pull )
152
179
{
153
180
return new Checkout
@@ -159,11 +186,11 @@ public Checkout CloneRef(Repository repository, string gitRef, bool pull = false
159
186
}
160
187
try
161
188
{
162
- ExecIn ( checkoutFolder , " git" , "pull" , "--depth" , "1" , "--allow-unrelated-histories" , "--no-ff" , "origin" , gitRef ) ;
189
+ git . Pull ( gitRef ) ;
163
190
}
164
191
catch ( Exception e )
165
192
{
166
- _logger . LogError ( e , "{RepositoryName}: Failed to update {GitRef} from {RelativePath }, falling back to recreating from scratch" ,
193
+ _logger . LogError ( e , "{RepositoryName}: Failed to update {GitRef} from {Path }, falling back to recreating from scratch" ,
167
194
repository . Name , gitRef , checkoutFolder . FullName ) ;
168
195
checkoutFolder . Delete ( true ) ;
169
196
checkoutFolder . Refresh ( ) ;
@@ -183,84 +210,31 @@ public Checkout CloneRef(Repository repository, string gitRef, bool pull = false
183
210
/// Initializes the git repository if it is not already initialized.
184
211
/// Returns true if the repository was already initialized.
185
212
/// </summary>
186
- private bool GitInit ( Repository repository , IDirectoryInfo checkoutFolder )
213
+ private static bool GitInit ( IGitRepository git , Repository repository )
187
214
{
188
- var isGitAlreadyInitialized = Directory . Exists ( Path . Combine ( checkoutFolder . FullName , ".git" ) ) ;
215
+ var isGitAlreadyInitialized = git . IsInitialized ( ) ;
189
216
if ( isGitAlreadyInitialized )
190
217
return true ;
191
- ExecIn ( checkoutFolder , " git" , "init" ) ;
192
- ExecIn ( checkoutFolder , " git" , "remote" , "add" , "origin" , repository . Origin ) ;
218
+ git . Init ( ) ;
219
+ git . GitAddOrigin ( repository . Origin ) ;
193
220
return false ;
194
221
}
195
222
196
- private void FetchAndCheckout ( Repository repository , string gitRef , IDirectoryInfo checkoutFolder )
223
+ private static void FetchAndCheckout ( IGitRepository git , Repository repository , string gitRef )
197
224
{
198
- ExecIn ( checkoutFolder , " git" , "fetch" , "--no-tags" , "--prune" , "--no-recurse-submodules" , "--depth" , "1" , "origin" , gitRef ) ;
225
+ git . Fetch ( gitRef ) ;
199
226
switch ( repository . CheckoutStrategy )
200
227
{
201
228
case CheckoutStrategy . Full :
202
- ExecIn ( checkoutFolder , " git" , "sparse-checkout" , "disable" ) ;
229
+ git . DisableSparseCheckout ( ) ;
203
230
break ;
204
231
case CheckoutStrategy . Partial :
205
- ExecIn ( checkoutFolder , " git" , "sparse-checkout" , "set" , "docs" ) ;
232
+ git . EnableSparseCheckout ( "docs" ) ;
206
233
break ;
207
234
default :
208
235
throw new ArgumentOutOfRangeException ( nameof ( repository ) , repository . CheckoutStrategy , null ) ;
209
236
}
210
- ExecIn ( checkoutFolder , "git" , "checkout" , "--force" , gitRef ) ;
211
- }
212
-
213
- private void ExecIn ( IDirectoryInfo ? workingDirectory , string binary , params string [ ] args )
214
- {
215
- var arguments = new ExecArguments ( binary , args )
216
- {
217
- WorkingDirectory = workingDirectory ? . FullName
218
- } ;
219
- var result = Proc . Exec ( arguments ) ;
220
- if ( result != 0 )
221
- collector . EmitError ( "" , $ "Exit code: { result } while executing { binary } { string . Join ( " " , args ) } in { workingDirectory } ") ;
222
- }
223
-
224
- // ReSharper disable once UnusedMember.Local
225
- private string Capture ( IDirectoryInfo ? workingDirectory , string binary , params string [ ] args )
226
- {
227
- // Try 10 times to capture the output of the command, if it fails, we'll throw an exception on the last try
228
- Exception ? e = null ;
229
- for ( var i = 0 ; i <= 9 ; i ++ )
230
- {
231
- try
232
- {
233
- return CaptureOutput ( ) ;
234
- }
235
- catch ( Exception ex )
236
- {
237
- if ( ex is not null )
238
- e = ex ;
239
- }
240
- }
241
-
242
- if ( e is not null )
243
- collector . EmitError ( "" , "failure capturing stdout" , e ) ;
244
-
245
-
246
- return string . Empty ;
247
-
248
- string CaptureOutput ( )
249
- {
250
- var arguments = new StartArguments ( binary , args )
251
- {
252
- WorkingDirectory = workingDirectory ? . FullName ,
253
- //WaitForStreamReadersTimeout = TimeSpan.FromSeconds(3),
254
- Timeout = TimeSpan . FromSeconds ( 3 ) ,
255
- WaitForExit = TimeSpan . FromSeconds ( 3 ) ,
256
- ConsoleOutWriter = NoopConsoleWriter . Instance
257
- } ;
258
- var result = Proc . Start ( arguments ) ;
259
- var line = result . ExitCode != 0
260
- ? throw new Exception ( $ "Exit code is not 0. Received { result . ExitCode } from { binary } : { workingDirectory } ")
261
- : result . ConsoleOut . FirstOrDefault ( ) ? . Line ?? throw new Exception ( $ "No output captured for { binary } : { workingDirectory } ") ;
262
- return line ;
263
- }
237
+ git . Checkout ( gitRef ) ;
264
238
}
265
239
}
266
240
@@ -272,3 +246,10 @@ public void Write(Exception e) { }
272
246
273
247
public void Write ( ConsoleOut consoleOut ) { }
274
248
}
249
+
250
+ public record CheckoutResult
251
+ {
252
+ public static string LinkRegistrySnapshotFileName => "link-index.snapshot.json" ;
253
+ public required LinkRegistry LinkRegistrySnapshot { get ; init ; }
254
+ public required IReadOnlyCollection < Checkout > Checkouts { get ; init ; }
255
+ }
0 commit comments