@@ -176,6 +176,113 @@ public void WhenCheckingOutAFileFileSmudgeWritesCorrectFileToWorkingDirectory()
176176 GlobalSettings . DeregisterFilter ( filterRegistration ) ;
177177 }
178178
179+ [ Fact ]
180+ public void CanFilterLargeFiles ( )
181+ {
182+ const int ContentLength = 128 * 1024 * 1024 ;
183+ const char ContentValue = 'x' ;
184+
185+ char [ ] content = ( new string ( ContentValue , 1024 ) ) . ToCharArray ( ) ;
186+
187+ string repoPath = InitNewRepository ( ) ;
188+
189+ var filter = new FileExportFilter ( "exportFilter" , attributes ) ;
190+ var filterRegistration = GlobalSettings . RegisterFilter ( filter ) ;
191+
192+ string filePath = Path . Combine ( Directory . GetParent ( repoPath ) . Parent . FullName , Guid . NewGuid ( ) . ToString ( ) + ".blob" ) ;
193+ FileInfo contentFile = new FileInfo ( filePath ) ;
194+ using ( var writer = new StreamWriter ( contentFile . OpenWrite ( ) ) { AutoFlush = true } )
195+ {
196+ for ( int i = 0 ; i < ContentLength / content . Length ; i ++ )
197+ {
198+ writer . Write ( content ) ;
199+ }
200+ }
201+
202+ string attributesPath = Path . Combine ( Directory . GetParent ( repoPath ) . Parent . FullName , ".gitattributes" ) ;
203+ FileInfo attributesFile = new FileInfo ( attributesPath ) ;
204+
205+ string configPath = CreateConfigurationWithDummyUser ( Constants . Signature ) ;
206+ var repositoryOptions = new RepositoryOptions { GlobalConfigurationLocation = configPath } ;
207+
208+ using ( Repository repo = new Repository ( repoPath , repositoryOptions ) )
209+ {
210+ File . WriteAllText ( attributesPath , "*.blob filter=test" ) ;
211+ repo . Stage ( attributesFile . Name ) ;
212+ repo . Stage ( contentFile . Name ) ;
213+ repo . Commit ( "test" ) ;
214+ contentFile . Delete ( ) ;
215+ repo . Checkout ( "HEAD" , new CheckoutOptions ( ) { CheckoutModifiers = CheckoutModifiers . Force } ) ;
216+ }
217+
218+ contentFile = new FileInfo ( filePath ) ;
219+ Assert . True ( contentFile . Exists , "Contents not restored correctly by forced checkout." ) ;
220+ using ( StreamReader reader = contentFile . OpenText ( ) )
221+ {
222+ int totalRead = 0 ;
223+ char [ ] block = new char [ 1024 ] ;
224+ int read ;
225+ while ( ( read = reader . Read ( block , 0 , block . Length ) ) > 0 )
226+ {
227+ Assert . True ( CharArrayAreEqual ( block , content , read ) ) ;
228+ totalRead += read ;
229+ }
230+
231+ Assert . Equal ( ContentLength , totalRead ) ;
232+ }
233+
234+ contentFile . Delete ( ) ;
235+
236+ GlobalSettings . DeregisterFilter ( filterRegistration ) ;
237+ }
238+
239+ private unsafe bool CharArrayAreEqual ( char [ ] array1 , char [ ] array2 , int count )
240+ {
241+ if ( Object . ReferenceEquals ( array1 , array2 ) )
242+ {
243+ return true ;
244+ }
245+ if ( Object . ReferenceEquals ( array1 , null ) || Object . ReferenceEquals ( null , array2 ) )
246+ {
247+ return false ;
248+ }
249+ if ( array1 . Length < count || array2 . Length < count )
250+ {
251+ return false ;
252+ }
253+
254+ int len = count * sizeof ( char ) ;
255+ int cnt = len / sizeof ( long ) ;
256+
257+ fixed ( char * c1 = array1 , c2 = array2 )
258+ {
259+ long * p1 = ( long * ) c1 ,
260+ p2 = ( long * ) c2 ;
261+
262+ for ( int i = 0 ; i < cnt ; i ++ )
263+ {
264+ if ( p1 [ i ] != p2 [ i ] )
265+ {
266+ return false ;
267+ }
268+ }
269+
270+ byte * b1 = ( byte * ) c1 ,
271+ b2 = ( byte * ) c2 ;
272+
273+ for ( int i = len * sizeof ( long ) ; i < len ; i ++ )
274+ {
275+ if ( b1 [ i ] != b2 [ i ] )
276+ {
277+ return false ;
278+ }
279+ }
280+ }
281+
282+ return true ;
283+ }
284+
285+
179286 private FileInfo CheckoutFileForSmudge ( string repoPath , string branchName , string content )
180287 {
181288 FileInfo expectedPath ;
0 commit comments