~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to external/ngit/NGit/NGit.Storage.File/GC.cs

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
This code is derived from jgit (http://eclipse.org/jgit).
 
3
Copyright owners are documented in jgit's IP log.
 
4
 
 
5
This program and the accompanying materials are made available
 
6
under the terms of the Eclipse Distribution License v1.0 which
 
7
accompanies this distribution, is reproduced below, and is
 
8
available at http://www.eclipse.org/org/documents/edl-v10.php
 
9
 
 
10
All rights reserved.
 
11
 
 
12
Redistribution and use in source and binary forms, with or
 
13
without modification, are permitted provided that the following
 
14
conditions are met:
 
15
 
 
16
- Redistributions of source code must retain the above copyright
 
17
  notice, this list of conditions and the following disclaimer.
 
18
 
 
19
- Redistributions in binary form must reproduce the above
 
20
  copyright notice, this list of conditions and the following
 
21
  disclaimer in the documentation and/or other materials provided
 
22
  with the distribution.
 
23
 
 
24
- Neither the name of the Eclipse Foundation, Inc. nor the
 
25
  names of its contributors may be used to endorse or promote
 
26
  products derived from this software without specific prior
 
27
  written permission.
 
28
 
 
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 
30
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 
31
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 
32
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 
33
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 
34
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 
35
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 
36
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 
37
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 
38
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 
39
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 
40
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 
41
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
42
*/
 
43
 
 
44
using System;
 
45
using System.Collections.Generic;
 
46
using System.IO;
 
47
using NGit;
 
48
using NGit.Dircache;
 
49
using NGit.Errors;
 
50
using NGit.Internal;
 
51
using NGit.Revwalk;
 
52
using NGit.Storage.File;
 
53
using NGit.Storage.Pack;
 
54
using NGit.Treewalk;
 
55
using NGit.Treewalk.Filter;
 
56
using NGit.Util;
 
57
using Sharpen;
 
58
 
 
59
namespace NGit.Storage.File
 
60
{
 
61
        /// <summary>
 
62
        /// A garbage collector for git
 
63
        /// <see cref="FileRepository">FileRepository</see>
 
64
        /// . Instances of this class
 
65
        /// are not thread-safe. Don't use the same instance from multiple threads.
 
66
        /// This class started as a copy of DfsGarbageCollector from Shawn O. Pearce
 
67
        /// adapted to FileRepositories.
 
68
        /// </summary>
 
69
        public class GC
 
70
        {
 
71
                private static readonly string PRUNE_EXPIRE_DEFAULT = "2.weeks.ago";
 
72
 
 
73
                private readonly FileRepository repo;
 
74
 
 
75
                private ProgressMonitor pm;
 
76
 
 
77
                private long expireAgeMillis = -1;
 
78
 
 
79
                private DateTime? expire;
 
80
 
 
81
                /// <summary>
 
82
                /// the refs which existed during the last call to
 
83
                /// <see cref="Repack()">Repack()</see>
 
84
                /// . This is
 
85
                /// needed during
 
86
                /// <see cref="Prune(System.Collections.Generic.ICollection{E})">Prune(System.Collections.Generic.ICollection&lt;E&gt;)
 
87
                ///     </see>
 
88
                /// where we can optimize by looking at the
 
89
                /// difference between the current refs and the refs which existed during
 
90
                /// last
 
91
                /// <see cref="Repack()">Repack()</see>
 
92
                /// .
 
93
                /// </summary>
 
94
                private IDictionary<string, Ref> lastPackedRefs;
 
95
 
 
96
                /// <summary>Holds the starting time of the last repack() execution.</summary>
 
97
                /// <remarks>
 
98
                /// Holds the starting time of the last repack() execution. This is needed in
 
99
                /// prune() to inspect only those reflog entries which have been added since
 
100
                /// last repack().
 
101
                /// </remarks>
 
102
                private long lastRepackTime;
 
103
 
 
104
                /// <summary>Creates a new garbage collector with default values.</summary>
 
105
                /// <remarks>
 
106
                /// Creates a new garbage collector with default values. An expirationTime of
 
107
                /// two weeks and <code>null</code> as progress monitor will be used.
 
108
                /// </remarks>
 
109
                /// <param name="repo">the repo to work on</param>
 
110
                public GC(FileRepository repo)
 
111
                {
 
112
                        this.repo = repo;
 
113
                        this.pm = NullProgressMonitor.INSTANCE;
 
114
                }
 
115
 
 
116
                /// <summary>
 
117
                /// Runs a garbage collector on a
 
118
                /// <see cref="FileRepository">FileRepository</see>
 
119
                /// . It will
 
120
                /// <ul>
 
121
                /// <li>pack loose references into packed-refs</li>
 
122
                /// <li>repack all reachable objects into new pack files and delete the old
 
123
                /// pack files</li>
 
124
                /// <li>prune all loose objects which are now reachable by packs</li>
 
125
                /// </ul>
 
126
                /// </summary>
 
127
                /// <returns>
 
128
                /// the collection of
 
129
                /// <see cref="PackFile">PackFile</see>
 
130
                /// 's which are newly created
 
131
                /// </returns>
 
132
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
133
                /// <exception cref="Sharpen.ParseException">
 
134
                /// If the configuration parameter "gc.pruneexpire" couldn't be
 
135
                /// parsed
 
136
                /// </exception>
 
137
                public virtual ICollection<PackFile> Gc()
 
138
                {
 
139
                        pm.Start(6);
 
140
                        PackRefs();
 
141
                        // TODO: implement reflog_expire(pm, repo);
 
142
                        ICollection<PackFile> newPacks = Repack();
 
143
                        Prune(Sharpen.Collections.EmptySet<ObjectId>());
 
144
                        // TODO: implement rerere_gc(pm);
 
145
                        return newPacks;
 
146
                }
 
147
 
 
148
                /// <summary>Delete old pack files.</summary>
 
149
                /// <remarks>
 
150
                /// Delete old pack files. What is 'old' is defined by specifying a set of
 
151
                /// old pack files and a set of new pack files. Each pack file contained in
 
152
                /// old pack files but not contained in new pack files will be deleted.
 
153
                /// </remarks>
 
154
                /// <param name="oldPacks"></param>
 
155
                /// <param name="newPacks"></param>
 
156
                /// <param name="ignoreErrors">
 
157
                /// <code>true</code> if we should ignore the fact that a certain
 
158
                /// pack files or index files couldn't be deleted.
 
159
                /// <code>false</code> if an exception should be thrown in such
 
160
                /// cases
 
161
                /// </param>
 
162
                /// <exception cref="System.IO.IOException">
 
163
                /// if a pack file couldn't be deleted and
 
164
                /// <code>ignoreErrors</code> is set to <code>false</code>
 
165
                /// </exception>
 
166
                private void DeleteOldPacks(ICollection<PackFile> oldPacks, ICollection<PackFile>
 
167
                         newPacks, bool ignoreErrors)
 
168
                {
 
169
                        int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING;
 
170
                        if (ignoreErrors)
 
171
                        {
 
172
                                deleteOptions |= FileUtils.IGNORE_ERRORS;
 
173
                        }
 
174
                        foreach (PackFile oldPack in oldPacks)
 
175
                        {
 
176
                                bool retainPack = false;
 
177
                                string oldName = oldPack.GetPackName();
 
178
                                // check whether an old pack file is also among the list of new
 
179
                                // pack files. Then we must not delete it.
 
180
                                foreach (PackFile newPack in newPacks)
 
181
                                {
 
182
                                        if (oldName.Equals(newPack.GetPackName()))
 
183
                                        {
 
184
                                                retainPack = true;
 
185
                                                break;
 
186
                                        }
 
187
                                }
 
188
                                if (!retainPack && !oldPack.ShouldBeKept())
 
189
                                {
 
190
                                        oldPack.Close();
 
191
                                        FileUtils.Delete(NameFor(oldName, ".pack"), deleteOptions);
 
192
                                        FileUtils.Delete(NameFor(oldName, ".idx"), deleteOptions);
 
193
                                }
 
194
                        }
 
195
 
 
196
                        // close the complete object database. Thats my only chance to force
 
197
                        // rescanning and to detect that certain pack files are now deleted.
 
198
                        ((ObjectDirectory)repo.ObjectDatabase).Close();
 
199
                }
 
200
 
 
201
                /// <summary>
 
202
                /// Like "git prune-packed" this method tries to prune all loose objects
 
203
                /// which can be found in packs.
 
204
                /// </summary>
 
205
                /// <remarks>
 
206
                /// Like "git prune-packed" this method tries to prune all loose objects
 
207
                /// which can be found in packs. If certain objects can't be pruned (e.g.
 
208
                /// because the filesystem delete operation fails) this is silently ignored.
 
209
                /// </remarks>
 
210
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
211
                public virtual void PrunePacked()
 
212
                {
 
213
                        ObjectDirectory objdb = ((ObjectDirectory)repo.ObjectDatabase);
 
214
                        ICollection<PackFile> packs = objdb.GetPacks();
 
215
                        FilePath objects = repo.ObjectsDirectory;
 
216
                        string[] fanout = objects.List();
 
217
                        if (fanout != null && fanout.Length > 0)
 
218
                        {
 
219
                                pm.BeginTask(JGitText.Get().pruneLoosePackedObjects, fanout.Length);
 
220
                                try
 
221
                                {
 
222
                                        foreach (string d in fanout)
 
223
                                        {
 
224
                                                pm.Update(1);
 
225
                                                if (d.Length != 2)
 
226
                                                {
 
227
                                                        continue;
 
228
                                                }
 
229
                                                string[] entries = new FilePath(objects, d).List();
 
230
                                                if (entries == null)
 
231
                                                {
 
232
                                                        continue;
 
233
                                                }
 
234
                                                foreach (string e in entries)
 
235
                                                {
 
236
                                                        if (e.Length != Constants.OBJECT_ID_STRING_LENGTH - 2)
 
237
                                                        {
 
238
                                                                continue;
 
239
                                                        }
 
240
                                                        ObjectId id;
 
241
                                                        try
 
242
                                                        {
 
243
                                                                id = ObjectId.FromString(d + e);
 
244
                                                        }
 
245
                                                        catch (ArgumentException)
 
246
                                                        {
 
247
                                                                // ignoring the file that does not represent loose
 
248
                                                                // object
 
249
                                                                continue;
 
250
                                                        }
 
251
                                                        bool found = false;
 
252
                                                        foreach (PackFile p in packs)
 
253
                                                        {
 
254
                                                                if (p.HasObject(id))
 
255
                                                                {
 
256
                                                                        found = true;
 
257
                                                                        break;
 
258
                                                                }
 
259
                                                        }
 
260
                                                        if (found)
 
261
                                                        {
 
262
                                                                FileUtils.Delete(objdb.FileFor(id), FileUtils.RETRY | FileUtils.SKIP_MISSING | FileUtils
 
263
                                                                        .IGNORE_ERRORS);
 
264
                                                        }
 
265
                                                }
 
266
                                        }
 
267
                                }
 
268
                                finally
 
269
                                {
 
270
                                        pm.EndTask();
 
271
                                }
 
272
                        }
 
273
                }
 
274
 
 
275
                /// <summary>
 
276
                /// Like "git prune" this method tries to prune all loose objects which are
 
277
                /// unreferenced.
 
278
                /// </summary>
 
279
                /// <remarks>
 
280
                /// Like "git prune" this method tries to prune all loose objects which are
 
281
                /// unreferenced. If certain objects can't be pruned (e.g. because the
 
282
                /// filesystem delete operation fails) this is silently ignored.
 
283
                /// </remarks>
 
284
                /// <param name="objectsToKeep">a set of objects which should explicitly not be pruned
 
285
                ///     </param>
 
286
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
287
                /// <exception cref="Sharpen.ParseException">
 
288
                /// If the configuration parameter "gc.pruneexpire" couldn't be
 
289
                /// parsed
 
290
                /// </exception>
 
291
                public virtual void Prune(ICollection<ObjectId> objectsToKeep)
 
292
                {
 
293
                        long expireDate = long.MaxValue;
 
294
                        if (expire == null && expireAgeMillis == -1)
 
295
                        {
 
296
                                string pruneExpireStr = ((FileBasedConfig)repo.GetConfig()).GetString(ConfigConstants
 
297
                                        .CONFIG_GC_SECTION, null, ConfigConstants.CONFIG_KEY_PRUNEEXPIRE);
 
298
                                if (pruneExpireStr == null)
 
299
                                {
 
300
                                        pruneExpireStr = PRUNE_EXPIRE_DEFAULT;
 
301
                                }
 
302
                                expire = GitDateParser.Parse(pruneExpireStr, null);
 
303
                                expireAgeMillis = -1;
 
304
                        }
 
305
                        if (expire != null)
 
306
                        {
 
307
                                expireDate = expire.Value.GetTime();
 
308
                        }
 
309
                        if (expireAgeMillis != -1)
 
310
                        {
 
311
                                expireDate = Runtime.CurrentTimeMillis() - expireAgeMillis;
 
312
                        }
 
313
                        // Collect all loose objects which are old enough, not referenced from
 
314
                        // the index and not in objectsToKeep
 
315
                        IDictionary<ObjectId, FilePath> deletionCandidates = new Dictionary<ObjectId, FilePath
 
316
                                >();
 
317
                        ICollection<ObjectId> indexObjects = null;
 
318
                        FilePath objects = repo.ObjectsDirectory;
 
319
                        string[] fanout = objects.List();
 
320
                        if (fanout != null && fanout.Length > 0)
 
321
                        {
 
322
                                pm.BeginTask(JGitText.Get().pruneLooseUnreferencedObjects, fanout.Length);
 
323
                                try
 
324
                                {
 
325
                                        foreach (string d in fanout)
 
326
                                        {
 
327
                                                pm.Update(1);
 
328
                                                if (d.Length != 2)
 
329
                                                {
 
330
                                                        continue;
 
331
                                                }
 
332
                                                FilePath[] entries = new FilePath(objects, d).ListFiles();
 
333
                                                if (entries == null)
 
334
                                                {
 
335
                                                        continue;
 
336
                                                }
 
337
                                                foreach (FilePath f in entries)
 
338
                                                {
 
339
                                                        string fName = f.GetName();
 
340
                                                        if (fName.Length != Constants.OBJECT_ID_STRING_LENGTH - 2)
 
341
                                                        {
 
342
                                                                continue;
 
343
                                                        }
 
344
                                                        if (f.LastModified() >= expireDate)
 
345
                                                        {
 
346
                                                                continue;
 
347
                                                        }
 
348
                                                        try
 
349
                                                        {
 
350
                                                                ObjectId id = ObjectId.FromString(d + fName);
 
351
                                                                if (objectsToKeep.Contains(id))
 
352
                                                                {
 
353
                                                                        continue;
 
354
                                                                }
 
355
                                                                if (indexObjects == null)
 
356
                                                                {
 
357
                                                                        indexObjects = ListNonHEADIndexObjects();
 
358
                                                                }
 
359
                                                                if (indexObjects.Contains(id))
 
360
                                                                {
 
361
                                                                        continue;
 
362
                                                                }
 
363
                                                                deletionCandidates.Put(id, f);
 
364
                                                        }
 
365
                                                        catch (ArgumentException)
 
366
                                                        {
 
367
                                                                // ignoring the file that does not represent loose
 
368
                                                                // object
 
369
                                                                continue;
 
370
                                                        }
 
371
                                                }
 
372
                                        }
 
373
                                }
 
374
                                finally
 
375
                                {
 
376
                                        pm.EndTask();
 
377
                                }
 
378
                        }
 
379
                        if (deletionCandidates.IsEmpty())
 
380
                        {
 
381
                                return;
 
382
                        }
 
383
                        // From the set of current refs remove all those which have been handled
 
384
                        // during last repack(). Only those refs will survive which have been
 
385
                        // added or modified since the last repack. Only these can save existing
 
386
                        // loose refs from being pruned.
 
387
                        IDictionary<string, Ref> newRefs;
 
388
                        if (lastPackedRefs == null || lastPackedRefs.IsEmpty())
 
389
                        {
 
390
                                newRefs = GetAllRefs();
 
391
                        }
 
392
                        else
 
393
                        {
 
394
                                newRefs = new Dictionary<string, Ref>();
 
395
                                for (Iterator<KeyValuePair<string, Ref>> i = GetAllRefs().EntrySet().Iterator(); 
 
396
                                        i.HasNext(); )
 
397
                                {
 
398
                                        KeyValuePair<string, Ref> newEntry = i.Next();
 
399
                                        Ref old = lastPackedRefs.Get(newEntry.Key);
 
400
                                        if (!Equals(newEntry.Value, old))
 
401
                                        {
 
402
                                                newRefs.Put(newEntry.Key, newEntry.Value);
 
403
                                        }
 
404
                                }
 
405
                        }
 
406
                        if (!newRefs.IsEmpty())
 
407
                        {
 
408
                                // There are new/modified refs! Check which loose objects are now
 
409
                                // referenced by these modified refs (or their reflogentries).
 
410
                                // Remove these loose objects
 
411
                                // from the deletionCandidates. When the last candidate is removed
 
412
                                // leave this method.
 
413
                                ObjectWalk w = new ObjectWalk(repo);
 
414
                                try
 
415
                                {
 
416
                                        foreach (Ref cr in newRefs.Values)
 
417
                                        {
 
418
                                                w.MarkStart(w.ParseAny(cr.GetObjectId()));
 
419
                                        }
 
420
                                        if (lastPackedRefs != null)
 
421
                                        {
 
422
                                                foreach (Ref lpr in lastPackedRefs.Values)
 
423
                                                {
 
424
                                                        w.MarkUninteresting(w.ParseAny(lpr.GetObjectId()));
 
425
                                                }
 
426
                                        }
 
427
                                        RemoveReferenced(deletionCandidates, w);
 
428
                                }
 
429
                                finally
 
430
                                {
 
431
                                        w.Dispose();
 
432
                                }
 
433
                        }
 
434
                        if (deletionCandidates.IsEmpty())
 
435
                        {
 
436
                                return;
 
437
                        }
 
438
                        // Since we have not left the method yet there are still
 
439
                        // deletionCandidates. Last chance for these objects not to be pruned is
 
440
                        // that they are referenced by reflog entries. Even refs which currently
 
441
                        // point to the same object as during last repack() may have
 
442
                        // additional reflog entries not handled during last repack()
 
443
                        ObjectWalk w_1 = new ObjectWalk(repo);
 
444
                        try
 
445
                        {
 
446
                                foreach (Ref ar in GetAllRefs().Values)
 
447
                                {
 
448
                                        foreach (ObjectId id in ListRefLogObjects(ar, lastRepackTime))
 
449
                                        {
 
450
                                                w_1.MarkStart(w_1.ParseAny(id));
 
451
                                        }
 
452
                                }
 
453
                                if (lastPackedRefs != null)
 
454
                                {
 
455
                                        foreach (Ref lpr in lastPackedRefs.Values)
 
456
                                        {
 
457
                                                w_1.MarkUninteresting(w_1.ParseAny(lpr.GetObjectId()));
 
458
                                        }
 
459
                                }
 
460
                                RemoveReferenced(deletionCandidates, w_1);
 
461
                        }
 
462
                        finally
 
463
                        {
 
464
                                w_1.Dispose();
 
465
                        }
 
466
                        if (deletionCandidates.IsEmpty())
 
467
                        {
 
468
                                return;
 
469
                        }
 
470
                        // delete all candidates which have survived: these are unreferenced
 
471
                        // loose objects
 
472
                        foreach (FilePath f_1 in deletionCandidates.Values)
 
473
                        {
 
474
                                f_1.Delete();
 
475
                        }
 
476
                        ((ObjectDirectory)repo.ObjectDatabase).Close();
 
477
                }
 
478
 
 
479
                /// <summary>
 
480
                /// Remove all entries from a map which key is the id of an object referenced
 
481
                /// by the given ObjectWalk
 
482
                /// </summary>
 
483
                /// <param name="id2File"></param>
 
484
                /// <param name="w"></param>
 
485
                /// <exception cref="NGit.Errors.MissingObjectException">NGit.Errors.MissingObjectException
 
486
                ///     </exception>
 
487
                /// <exception cref="NGit.Errors.IncorrectObjectTypeException">NGit.Errors.IncorrectObjectTypeException
 
488
                ///     </exception>
 
489
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
490
                private void RemoveReferenced(IDictionary<ObjectId, FilePath> id2File, ObjectWalk
 
491
                         w)
 
492
                {
 
493
                        RevObject ro = w.Next();
 
494
                        while (ro != null)
 
495
                        {
 
496
                                if (Sharpen.Collections.Remove(id2File, ro.Id) != null)
 
497
                                {
 
498
                                        if (id2File.IsEmpty())
 
499
                                        {
 
500
                                                return;
 
501
                                        }
 
502
                                }
 
503
                                ro = w.Next();
 
504
                        }
 
505
                        ro = w.NextObject();
 
506
                        while (ro != null)
 
507
                        {
 
508
                                if (Sharpen.Collections.Remove(id2File, ro.Id) != null)
 
509
                                {
 
510
                                        if (id2File.IsEmpty())
 
511
                                        {
 
512
                                                return;
 
513
                                        }
 
514
                                }
 
515
                                ro = w.NextObject();
 
516
                        }
 
517
                }
 
518
 
 
519
                private static bool Equals(Ref r1, Ref r2)
 
520
                {
 
521
                        if (r1 == null || r2 == null)
 
522
                        {
 
523
                                return false;
 
524
                        }
 
525
                        if (r1.IsSymbolic())
 
526
                        {
 
527
                                if (!r2.IsSymbolic())
 
528
                                {
 
529
                                        return false;
 
530
                                }
 
531
                                return r1.GetTarget().GetName().Equals(r2.GetTarget().GetName());
 
532
                        }
 
533
                        else
 
534
                        {
 
535
                                if (r2.IsSymbolic())
 
536
                                {
 
537
                                        return false;
 
538
                                }
 
539
                                return r1.GetObjectId().Equals(r2.GetObjectId());
 
540
                        }
 
541
                }
 
542
 
 
543
                /// <summary>Packs all non-symbolic, loose refs into packed-refs.</summary>
 
544
                /// <remarks>Packs all non-symbolic, loose refs into packed-refs.</remarks>
 
545
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
546
                public virtual void PackRefs()
 
547
                {
 
548
                        ICollection<Ref> refs = repo.GetAllRefs().Values;
 
549
                        IList<string> refsToBePacked = new AList<string>(refs.Count);
 
550
                        pm.BeginTask(JGitText.Get().packRefs, refs.Count);
 
551
                        try
 
552
                        {
 
553
                                foreach (Ref @ref in refs)
 
554
                                {
 
555
                                        if (!@ref.IsSymbolic() && @ref.GetStorage().IsLoose())
 
556
                                        {
 
557
                                                refsToBePacked.AddItem(@ref.GetName());
 
558
                                        }
 
559
                                        pm.Update(1);
 
560
                                }
 
561
                                ((RefDirectory)repo.RefDatabase).Pack(refsToBePacked);
 
562
                        }
 
563
                        finally
 
564
                        {
 
565
                                pm.EndTask();
 
566
                        }
 
567
                }
 
568
 
 
569
                /// <summary>
 
570
                /// Packs all objects which reachable from any of the heads into one pack
 
571
                /// file.
 
572
                /// </summary>
 
573
                /// <remarks>
 
574
                /// Packs all objects which reachable from any of the heads into one pack
 
575
                /// file. Additionally all objects which are not reachable from any head but
 
576
                /// which are reachable from any of the other refs (e.g. tags), special refs
 
577
                /// (e.g. FETCH_HEAD) or index are packed into a separate pack file. Objects
 
578
                /// included in pack files which have a .keep file associated are never
 
579
                /// repacked. All old pack files which existed before are deleted.
 
580
                /// </remarks>
 
581
                /// <returns>a collection of the newly created pack files</returns>
 
582
                /// <exception cref="System.IO.IOException">
 
583
                /// when during reading of refs, index, packfiles, objects,
 
584
                /// reflog-entries or during writing to the packfiles
 
585
                /// <see cref="System.IO.IOException">System.IO.IOException</see>
 
586
                /// occurs
 
587
                /// </exception>
 
588
                public virtual ICollection<PackFile> Repack()
 
589
                {
 
590
                        ICollection<PackFile> toBeDeleted = ((ObjectDirectory)repo.ObjectDatabase).GetPacks
 
591
                                ();
 
592
                        long time = Runtime.CurrentTimeMillis();
 
593
                        IDictionary<string, Ref> refsBefore = GetAllRefs();
 
594
                        ICollection<ObjectId> allHeads = new HashSet<ObjectId>();
 
595
                        ICollection<ObjectId> nonHeads = new HashSet<ObjectId>();
 
596
                        ICollection<ObjectId> tagTargets = new HashSet<ObjectId>();
 
597
                        ICollection<ObjectId> indexObjects = ListNonHEADIndexObjects();
 
598
                        foreach (Ref @ref in refsBefore.Values)
 
599
                        {
 
600
                                Sharpen.Collections.AddAll(nonHeads, ListRefLogObjects(@ref, 0));
 
601
                                if (@ref.IsSymbolic() || @ref.GetObjectId() == null)
 
602
                                {
 
603
                                        continue;
 
604
                                }
 
605
                                if (@ref.GetName().StartsWith(Constants.R_HEADS))
 
606
                                {
 
607
                                        allHeads.AddItem(@ref.GetObjectId());
 
608
                                }
 
609
                                else
 
610
                                {
 
611
                                        nonHeads.AddItem(@ref.GetObjectId());
 
612
                                }
 
613
                                if (@ref.GetPeeledObjectId() != null)
 
614
                                {
 
615
                                        tagTargets.AddItem(@ref.GetPeeledObjectId());
 
616
                                }
 
617
                        }
 
618
                        IList<PackIndex> excluded = new List<PackIndex>();
 
619
                        foreach (PackFile f in ((ObjectDirectory)repo.ObjectDatabase).GetPacks())
 
620
                        {
 
621
                                if (f.ShouldBeKept())
 
622
                                {
 
623
                                        excluded.AddItem(f.GetIndex());
 
624
                                }
 
625
                        }
 
626
                        Sharpen.Collections.AddAll(tagTargets, allHeads);
 
627
                        Sharpen.Collections.AddAll(nonHeads, indexObjects);
 
628
                        IList<PackFile> ret = new AList<PackFile>(2);
 
629
                        PackFile heads = null;
 
630
                        if (!allHeads.IsEmpty())
 
631
                        {
 
632
                                heads = WritePack(allHeads, Sharpen.Collections.EmptySet<ObjectId>(), tagTargets, 
 
633
                                        excluded);
 
634
                                if (heads != null)
 
635
                                {
 
636
                                        ret.AddItem(heads);
 
637
                                        excluded.Add(0, heads.GetIndex());
 
638
                                }
 
639
                        }
 
640
                        if (!nonHeads.IsEmpty())
 
641
                        {
 
642
                                PackFile rest = WritePack(nonHeads, allHeads, tagTargets, excluded);
 
643
                                if (rest != null)
 
644
                                {
 
645
                                        ret.AddItem(rest);
 
646
                                }
 
647
                        }
 
648
                        DeleteOldPacks(toBeDeleted, ret, true);
 
649
                        PrunePacked();
 
650
                        lastPackedRefs = refsBefore;
 
651
                        lastRepackTime = time;
 
652
                        return ret;
 
653
                }
 
654
 
 
655
                /// <param name="ref">the ref which log should be inspected</param>
 
656
                /// <param name="minTime">only reflog entries not older then this time are processed</param>
 
657
                /// <returns>
 
658
                /// the
 
659
                /// <see cref="NGit.ObjectId">NGit.ObjectId</see>
 
660
                /// s contained in the reflog
 
661
                /// </returns>
 
662
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
663
                private ICollection<ObjectId> ListRefLogObjects(Ref @ref, long minTime)
 
664
                {
 
665
                        IList<ReflogEntry> rlEntries = repo.GetReflogReader(@ref.GetName()).GetReverseEntries
 
666
                                ();
 
667
                        if (rlEntries == null || rlEntries.IsEmpty())
 
668
                        {
 
669
                                return Sharpen.Collections.EmptySet<ObjectId>();
 
670
                        }
 
671
                        ICollection<ObjectId> ret = new HashSet<ObjectId>();
 
672
                        foreach (ReflogEntry e in rlEntries)
 
673
                        {
 
674
                                if (e.GetWho().GetWhen().GetTime() < minTime)
 
675
                                {
 
676
                                        break;
 
677
                                }
 
678
                                ret.AddItem(e.GetNewId());
 
679
                                ObjectId oldId = e.GetOldId();
 
680
                                if (oldId != null && !ObjectId.ZeroId.Equals(oldId))
 
681
                                {
 
682
                                        ret.AddItem(oldId);
 
683
                                }
 
684
                        }
 
685
                        return ret;
 
686
                }
 
687
 
 
688
                /// <summary>Returns a map of all refs and additional refs (e.g.</summary>
 
689
                /// <remarks>
 
690
                /// Returns a map of all refs and additional refs (e.g. FETCH_HEAD,
 
691
                /// MERGE_HEAD, ...)
 
692
                /// </remarks>
 
693
                /// <returns>a map where names of refs point to ref objects</returns>
 
694
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
695
                private IDictionary<string, Ref> GetAllRefs()
 
696
                {
 
697
                        IDictionary<string, Ref> ret = repo.GetAllRefs();
 
698
                        foreach (Ref @ref in repo.RefDatabase.GetAdditionalRefs())
 
699
                        {
 
700
                                ret.Put(@ref.GetName(), @ref);
 
701
                        }
 
702
                        return ret;
 
703
                }
 
704
 
 
705
                /// <summary>
 
706
                /// Return a list of those objects in the index which differ from whats in
 
707
                /// HEAD
 
708
                /// </summary>
 
709
                /// <returns>a set of ObjectIds of changed objects in the index</returns>
 
710
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
711
                /// <exception cref="NGit.Errors.CorruptObjectException">NGit.Errors.CorruptObjectException
 
712
                ///     </exception>
 
713
                /// <exception cref="NGit.Errors.NoWorkTreeException">NGit.Errors.NoWorkTreeException
 
714
                ///     </exception>
 
715
                private ICollection<ObjectId> ListNonHEADIndexObjects()
 
716
                {
 
717
                        RevWalk revWalk = null;
 
718
                        try
 
719
                        {
 
720
                                if (repo.GetIndexFile() == null)
 
721
                                {
 
722
                                        return Sharpen.Collections.EmptySet<ObjectId>();
 
723
                                }
 
724
                        }
 
725
                        catch (NoWorkTreeException)
 
726
                        {
 
727
                                return Sharpen.Collections.EmptySet<ObjectId>();
 
728
                        }
 
729
                        TreeWalk treeWalk = new TreeWalk(repo);
 
730
                        try
 
731
                        {
 
732
                                treeWalk.AddTree(new DirCacheIterator(repo.ReadDirCache()));
 
733
                                ObjectId headID = repo.Resolve(Constants.HEAD);
 
734
                                if (headID != null)
 
735
                                {
 
736
                                        revWalk = new RevWalk(repo);
 
737
                                        treeWalk.AddTree(revWalk.ParseTree(headID));
 
738
                                        revWalk.Dispose();
 
739
                                        revWalk = null;
 
740
                                }
 
741
                                treeWalk.Filter = TreeFilter.ANY_DIFF;
 
742
                                treeWalk.Recursive = true;
 
743
                                ICollection<ObjectId> ret = new HashSet<ObjectId>();
 
744
                                while (treeWalk.Next())
 
745
                                {
 
746
                                        ObjectId objectId = treeWalk.GetObjectId(0);
 
747
                                        switch (treeWalk.GetRawMode(0) & FileMode.TYPE_MASK)
 
748
                                        {
 
749
                                                case FileMode.TYPE_MISSING:
 
750
                                                case FileMode.TYPE_GITLINK:
 
751
                                                {
 
752
                                                        continue;
 
753
                                                        goto case FileMode.TYPE_TREE;
 
754
                                                }
 
755
 
 
756
                                                case FileMode.TYPE_TREE:
 
757
                                                case FileMode.TYPE_FILE:
 
758
                                                case FileMode.TYPE_SYMLINK:
 
759
                                                {
 
760
                                                        ret.AddItem(objectId);
 
761
                                                        continue;
 
762
                                                        goto default;
 
763
                                                }
 
764
 
 
765
                                                default:
 
766
                                                {
 
767
                                                        throw new IOException(MessageFormat.Format(JGitText.Get().corruptObjectInvalidMode3
 
768
                                                                , string.Format("%o", Sharpen.Extensions.ValueOf(treeWalk.GetRawMode(0)), (objectId
 
769
                                                                 == null) ? "null" : objectId.Name, treeWalk.PathString, repo.GetIndexFile())));
 
770
                                                }
 
771
                                        }
 
772
                                }
 
773
                                return ret;
 
774
                        }
 
775
                        finally
 
776
                        {
 
777
                                if (revWalk != null)
 
778
                                {
 
779
                                        revWalk.Dispose();
 
780
                                }
 
781
                                treeWalk.Release();
 
782
                        }
 
783
                }
 
784
 
 
785
                /// <exception cref="System.IO.IOException"></exception>
 
786
                private PackFile WritePack<_T0, _T1>(ICollection<_T0> want, ICollection<_T1> have
 
787
                        , ICollection<ObjectId> tagTargets, IList<PackIndex> excludeObjects) where _T0:ObjectId
 
788
                         where _T1:ObjectId
 
789
                {
 
790
                        FilePath tmpPack = null;
 
791
                        FilePath tmpIdx = null;
 
792
                        PackWriter pw = new PackWriter(repo);
 
793
                        try
 
794
                        {
 
795
                                // prepare the PackWriter
 
796
                                pw.SetDeltaBaseAsOffset(true);
 
797
                                pw.SetReuseDeltaCommits(false);
 
798
                                if (tagTargets != null)
 
799
                                {
 
800
                                        pw.SetTagTargets(tagTargets);
 
801
                                }
 
802
                                if (excludeObjects != null)
 
803
                                {
 
804
                                        foreach (PackIndex idx in excludeObjects)
 
805
                                        {
 
806
                                                pw.ExcludeObjects(idx);
 
807
                                        }
 
808
                                }
 
809
                                pw.PreparePack(pm, want, have);
 
810
                                if (pw.GetObjectCount() == 0)
 
811
                                {
 
812
                                        return null;
 
813
                                }
 
814
                                // create temporary files
 
815
                                string id = pw.ComputeName().GetName();
 
816
                                FilePath packdir = new FilePath(repo.ObjectsDirectory, "pack");
 
817
                                tmpPack = FilePath.CreateTempFile("gc_", ".pack_tmp", packdir);
 
818
                                tmpIdx = new FilePath(packdir, Sharpen.Runtime.Substring(tmpPack.GetName(), 0, tmpPack
 
819
                                        .GetName().LastIndexOf('.')) + ".idx_tmp");
 
820
                                if (!tmpIdx.CreateNewFile())
 
821
                                {
 
822
                                        throw new IOException(MessageFormat.Format(JGitText.Get().cannotCreateIndexfile, 
 
823
                                                tmpIdx.GetPath()));
 
824
                                }
 
825
                                // write the packfile
 
826
                                FileChannel channel = new FileOutputStream(tmpPack).GetChannel();
 
827
                                OutputStream channelStream = Channels.NewOutputStream(channel);
 
828
                                try
 
829
                                {
 
830
                                        pw.WritePack(pm, pm, channelStream);
 
831
                                }
 
832
                                finally
 
833
                                {
 
834
                                        channel.Force(true);
 
835
                                        channelStream.Close();
 
836
                                        channel.Close();
 
837
                                }
 
838
                                // write the packindex
 
839
                                FileChannel idxChannel = new FileOutputStream(tmpIdx).GetChannel();
 
840
                                OutputStream idxStream = Channels.NewOutputStream(idxChannel);
 
841
                                try
 
842
                                {
 
843
                                        pw.WriteIndex(idxStream);
 
844
                                }
 
845
                                finally
 
846
                                {
 
847
                                        idxChannel.Force(true);
 
848
                                        idxStream.Close();
 
849
                                        idxChannel.Close();
 
850
                                }
 
851
                                // rename the temporary files to real files
 
852
                                FilePath realPack = NameFor(id, ".pack");
 
853
                                tmpPack.SetReadOnly();
 
854
                                FilePath realIdx = NameFor(id, ".idx");
 
855
                                realIdx.SetReadOnly();
 
856
                                bool delete = true;
 
857
                                try
 
858
                                {
 
859
                                        if (!tmpPack.RenameTo(realPack))
 
860
                                        {
 
861
                                                return null;
 
862
                                        }
 
863
                                        delete = false;
 
864
                                        if (!tmpIdx.RenameTo(realIdx))
 
865
                                        {
 
866
                                                FilePath newIdx = new FilePath(realIdx.GetParentFile(), realIdx.GetName() + ".new"
 
867
                                                        );
 
868
                                                if (!tmpIdx.RenameTo(newIdx))
 
869
                                                {
 
870
                                                        newIdx = tmpIdx;
 
871
                                                }
 
872
                                                throw new IOException(MessageFormat.Format(JGitText.Get().panicCantRenameIndexFile
 
873
                                                        , newIdx, realIdx));
 
874
                                        }
 
875
                                }
 
876
                                finally
 
877
                                {
 
878
                                        if (delete && tmpPack.Exists())
 
879
                                        {
 
880
                                                tmpPack.Delete();
 
881
                                        }
 
882
                                        if (delete && tmpIdx.Exists())
 
883
                                        {
 
884
                                                tmpIdx.Delete();
 
885
                                        }
 
886
                                }
 
887
                                return ((ObjectDirectory)repo.ObjectDatabase).OpenPack(realPack, realIdx);
 
888
                        }
 
889
                        finally
 
890
                        {
 
891
                                pw.Release();
 
892
                                if (tmpPack != null && tmpPack.Exists())
 
893
                                {
 
894
                                        tmpPack.Delete();
 
895
                                }
 
896
                                if (tmpIdx != null && tmpIdx.Exists())
 
897
                                {
 
898
                                        tmpIdx.Delete();
 
899
                                }
 
900
                        }
 
901
                }
 
902
 
 
903
                private FilePath NameFor(string name, string ext)
 
904
                {
 
905
                        FilePath packdir = new FilePath(repo.ObjectsDirectory, "pack");
 
906
                        return new FilePath(packdir, "pack-" + name + ext);
 
907
                }
 
908
 
 
909
                /// <summary>
 
910
                /// A class holding statistical data for a FileRepository regarding how many
 
911
                /// objects are stored as loose or packed objects
 
912
                /// </summary>
 
913
                public class RepoStatistics
 
914
                {
 
915
                        /// <summary>The number of objects stored in pack files.</summary>
 
916
                        /// <remarks>
 
917
                        /// The number of objects stored in pack files. If the same object is
 
918
                        /// stored in multiple pack files then it is counted as often as it
 
919
                        /// occurs in pack files.
 
920
                        /// </remarks>
 
921
                        public long numberOfPackedObjects;
 
922
 
 
923
                        /// <summary>The number of pack files</summary>
 
924
                        public long numberOfPackFiles;
 
925
 
 
926
                        /// <summary>The number of objects stored as loose objects.</summary>
 
927
                        /// <remarks>The number of objects stored as loose objects.</remarks>
 
928
                        public long numberOfLooseObjects;
 
929
 
 
930
                        /// <summary>The sum of the sizes of all files used to persist loose objects.</summary>
 
931
                        /// <remarks>The sum of the sizes of all files used to persist loose objects.</remarks>
 
932
                        public long sizeOfLooseObjects;
 
933
 
 
934
                        /// <summary>The sum of the sizes of all pack files.</summary>
 
935
                        /// <remarks>The sum of the sizes of all pack files.</remarks>
 
936
                        public long sizeOfPackedObjects;
 
937
 
 
938
                        /// <summary>The number of loose refs.</summary>
 
939
                        /// <remarks>The number of loose refs.</remarks>
 
940
                        public long numberOfLooseRefs;
 
941
 
 
942
                        /// <summary>The number of refs stored in pack files.</summary>
 
943
                        /// <remarks>The number of refs stored in pack files.</remarks>
 
944
                        public long numberOfPackedRefs;
 
945
 
 
946
                        internal RepoStatistics(GC _enclosing)
 
947
                        {
 
948
                                this._enclosing = _enclosing;
 
949
                        }
 
950
 
 
951
                        private readonly GC _enclosing;
 
952
                }
 
953
 
 
954
                /// <summary>Returns the number of objects stored in pack files.</summary>
 
955
                /// <remarks>
 
956
                /// Returns the number of objects stored in pack files. If an object is
 
957
                /// contained in multiple pack files it is counted as often as it occurs.
 
958
                /// </remarks>
 
959
                /// <returns>the number of objects stored in pack files</returns>
 
960
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
961
                public virtual GC.RepoStatistics GetStatistics()
 
962
                {
 
963
                        GC.RepoStatistics ret = new GC.RepoStatistics(this);
 
964
                        ICollection<PackFile> packs = ((ObjectDirectory)repo.ObjectDatabase).GetPacks();
 
965
                        foreach (PackFile f in packs)
 
966
                        {
 
967
                                ret.numberOfPackedObjects += f.GetIndex().GetObjectCount();
 
968
                                ret.numberOfPackFiles++;
 
969
                                ret.sizeOfPackedObjects += f.GetPackFile().Length();
 
970
                        }
 
971
                        FilePath objDir = repo.ObjectsDirectory;
 
972
                        string[] fanout = objDir.List();
 
973
                        if (fanout != null && fanout.Length > 0)
 
974
                        {
 
975
                                foreach (string d in fanout)
 
976
                                {
 
977
                                        if (d.Length != 2)
 
978
                                        {
 
979
                                                continue;
 
980
                                        }
 
981
                                        FilePath[] entries = new FilePath(objDir, d).ListFiles();
 
982
                                        if (entries == null)
 
983
                                        {
 
984
                                                continue;
 
985
                                        }
 
986
                                        foreach (FilePath f_1 in entries)
 
987
                                        {
 
988
                                                if (f_1.GetName().Length != Constants.OBJECT_ID_STRING_LENGTH - 2)
 
989
                                                {
 
990
                                                        continue;
 
991
                                                }
 
992
                                                ret.numberOfLooseObjects++;
 
993
                                                ret.sizeOfLooseObjects += f_1.Length();
 
994
                                        }
 
995
                                }
 
996
                        }
 
997
                        RefDatabase refDb = repo.RefDatabase;
 
998
                        foreach (Ref r in refDb.GetRefs(RefDatabase.ALL).Values)
 
999
                        {
 
1000
                                RefStorage storage = r.GetStorage();
 
1001
                                if (storage == RefStorage.LOOSE || storage == RefStorage.LOOSE_PACKED)
 
1002
                                {
 
1003
                                        ret.numberOfLooseRefs++;
 
1004
                                }
 
1005
                                if (storage == RefStorage.PACKED || storage == RefStorage.LOOSE_PACKED)
 
1006
                                {
 
1007
                                        ret.numberOfPackedRefs++;
 
1008
                                }
 
1009
                        }
 
1010
                        return ret;
 
1011
                }
 
1012
 
 
1013
                /// <summary>Set the progress monitor used for garbage collection methods.</summary>
 
1014
                /// <remarks>Set the progress monitor used for garbage collection methods.</remarks>
 
1015
                /// <param name="pm"></param>
 
1016
                /// <returns>this</returns>
 
1017
                public virtual GC SetProgressMonitor(ProgressMonitor pm)
 
1018
                {
 
1019
                        this.pm = (pm == null) ? NullProgressMonitor.INSTANCE : pm;
 
1020
                        return this;
 
1021
                }
 
1022
 
 
1023
                /// <summary>
 
1024
                /// During gc() or prune() each unreferenced, loose object which has been
 
1025
                /// created or modified in the last <code>expireAgeMillis</code> milliseconds
 
1026
                /// will not be pruned.
 
1027
                /// </summary>
 
1028
                /// <remarks>
 
1029
                /// During gc() or prune() each unreferenced, loose object which has been
 
1030
                /// created or modified in the last <code>expireAgeMillis</code> milliseconds
 
1031
                /// will not be pruned. Only older objects may be pruned. If set to 0 then
 
1032
                /// every object is a candidate for pruning.
 
1033
                /// </remarks>
 
1034
                /// <param name="expireAgeMillis">minimal age of objects to be pruned in milliseconds.
 
1035
                ///     </param>
 
1036
                public virtual void SetExpireAgeMillis(long expireAgeMillis)
 
1037
                {
 
1038
                        this.expireAgeMillis = expireAgeMillis;
 
1039
                        expire = null;
 
1040
                }
 
1041
 
 
1042
                /// <summary>
 
1043
                /// During gc() or prune() each unreferenced, loose object which has been
 
1044
                /// created or modified after or at <code>expire</code> will not be pruned.
 
1045
                /// </summary>
 
1046
                /// <remarks>
 
1047
                /// During gc() or prune() each unreferenced, loose object which has been
 
1048
                /// created or modified after or at <code>expire</code> will not be pruned.
 
1049
                /// Only older objects may be pruned. If set to null then every object is a
 
1050
                /// candidate for pruning.
 
1051
                /// </remarks>
 
1052
                /// <param name="expire">
 
1053
                /// instant in time which defines object expiration
 
1054
                /// objects with modification time before this instant are expired
 
1055
                /// objects with modification time newer or equal to this instant
 
1056
                /// are not expired
 
1057
                /// </param>
 
1058
                public virtual void SetExpire(DateTime expire)
 
1059
                {
 
1060
                        this.expire = expire;
 
1061
                        expireAgeMillis = -1;
 
1062
                }
 
1063
        }
 
1064
}