Welcome to mirror list, hosted at ThFree Co, Russian Federation.

HistoryRewriter.cs « Core « LibGit2Sharp - github.com/mono/libgit2sharp.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: d313bedbfcbfd5a2f01ef4c0f542736746ef69bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;

namespace LibGit2Sharp.Core
{
    internal class HistoryRewriter
    {
        private readonly IRepository repo;

        private readonly HashSet<Commit> targetedCommits;
        private readonly Dictionary<GitObject, GitObject> objectMap = new Dictionary<GitObject, GitObject>();
        private readonly Dictionary<Reference, Reference> refMap = new Dictionary<Reference, Reference>();
        private readonly Queue<Action> rollbackActions = new Queue<Action>();

        private readonly string backupRefsNamespace;
        private readonly RewriteHistoryOptions options;

        public HistoryRewriter(
            IRepository repo,
            IEnumerable<Commit> commitsToRewrite,
            RewriteHistoryOptions options)
        {
            this.repo = repo;
            this.options = options;
            targetedCommits = new HashSet<Commit>(commitsToRewrite);

            backupRefsNamespace = this.options.BackupRefsNamespace;

            if (!backupRefsNamespace.EndsWith("/", StringComparison.Ordinal))
            {
                backupRefsNamespace += "/";
            }
        }

        public void Execute()
        {
            var success = false;
            try
            {
                // Find out which refs lead to at least one the commits
                var refsToRewrite = repo.Refs.ReachableFrom(targetedCommits).ToList();

                var filter = new CommitFilter
                {
                    Since = refsToRewrite,
                    SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological
                };

                var commits = repo.Commits.QueryBy(filter);
                foreach (var commit in commits)
                {
                    RewriteCommit(commit);
                }

                // Ordering matters. In the case of `A -> B -> commit`, we need to make sure B is rewritten
                // before A.
                foreach (var reference in refsToRewrite.OrderBy(ReferenceDepth))
                {
                    // TODO: Rewrite refs/notes/* properly
                    if (reference.CanonicalName.StartsWith("refs/notes/"))
                    {
                        continue;
                    }

                    RewriteReference(reference);
                }

                success = true;
                if (options.OnSucceeding != null)
                {
                    options.OnSucceeding();
                }
            }
            catch (Exception ex)
            {
                try
                {
                    if (!success && options.OnError != null)
                    {
                        options.OnError(ex);
                    }
                }
                finally
                {
                    foreach (var action in rollbackActions)
                    {
                        action();
                    }
                }

                throw;
            }
            finally
            {
                rollbackActions.Clear();
            }
        }

        private Reference RewriteReference(Reference reference)
        {
            // Has this target already been rewritten?
            if (refMap.ContainsKey(reference))
            {
                return refMap[reference];
            }

            var sref = reference as SymbolicReference;
            if (sref != null)
            {
                return RewriteReference(
                    sref, old => old.Target, RewriteReference,
                    (refs, old, target, logMessage) => refs.UpdateTarget(old, target, logMessage));
            }

            var dref = reference as DirectReference;
            if (dref != null)
            {
                return RewriteReference(
                    dref, old => old.Target, RewriteTarget,
                    (refs, old, target, logMessage) => refs.UpdateTarget(old, target.Id, logMessage));
            }

            return reference;
        }

        private delegate Reference ReferenceUpdater<in TRef, in TTarget>(
            ReferenceCollection refs, TRef origRef, TTarget origTarget, string logMessage)
            where TRef : Reference
            where TTarget : class;

        private Reference RewriteReference<TRef, TTarget>(
            TRef oldRef, Func<TRef, TTarget> selectTarget,
            Func<TTarget, TTarget> rewriteTarget,
            ReferenceUpdater<TRef, TTarget> updateTarget)
            where TRef : Reference
            where TTarget : class
        {
            var oldRefTarget = selectTarget(oldRef);

            string newRefName = oldRef.CanonicalName;
            if (oldRef.IsTag && options.TagNameRewriter != null)
            {
                newRefName = Reference.TagPrefix +
                             options.TagNameRewriter(oldRef.CanonicalName.Substring(Reference.TagPrefix.Length),
                                                     false, oldRef.TargetIdentifier);
            }

            var newTarget = rewriteTarget(oldRefTarget);

            if (oldRefTarget.Equals(newTarget) && oldRef.CanonicalName == newRefName)
            {
                // The reference isn't rewritten
                return oldRef;
            }

            string backupName = backupRefsNamespace + oldRef.CanonicalName.Substring("refs/".Length);

            if (repo.Refs.Resolve<Reference>(backupName) != null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.InvariantCulture, "Can't back up reference '{0}' - '{1}' already exists",
                        oldRef.CanonicalName, backupName));
            }

            repo.Refs.Add(backupName, oldRef.TargetIdentifier, "filter-branch: backup");
            rollbackActions.Enqueue(() => repo.Refs.Remove(backupName));

            if (newTarget == null)
            {
                repo.Refs.Remove(oldRef);
                rollbackActions.Enqueue(() => repo.Refs.Add(oldRef.CanonicalName, oldRef, "filter-branch: abort", true));
                return refMap[oldRef] = null;
            }

            Reference newRef = updateTarget(repo.Refs, oldRef, newTarget, "filter-branch: rewrite");
            rollbackActions.Enqueue(() => updateTarget(repo.Refs, oldRef, oldRefTarget, "filter-branch: abort"));

            if (newRef.CanonicalName == newRefName)
            {
                return refMap[oldRef] = newRef;
            }

            var movedRef = repo.Refs.Rename(newRef, newRefName, false);
            rollbackActions.Enqueue(() => repo.Refs.Rename(newRef, oldRef.CanonicalName, false));
            return refMap[oldRef] = movedRef;
        }

        private void RewriteCommit(Commit commit)
        {
            var newHeader = CommitRewriteInfo.From(commit);
            var newTree = commit.Tree;

            // Find the new parents
            var newParents = commit.Parents;

            if (targetedCommits.Contains(commit))
            {
                // Get the new commit header
                if (options.CommitHeaderRewriter != null)
                {
                    newHeader = options.CommitHeaderRewriter(commit) ?? newHeader;
                }

                if (options.CommitTreeRewriter != null)
                {
                    // Get the new commit tree
                    var newTreeDefinition = options.CommitTreeRewriter(commit);
                    newTree = repo.ObjectDatabase.CreateTree(newTreeDefinition);
                }

                // Retrieve new parents
                if (options.CommitParentsRewriter != null)
                {
                    newParents = options.CommitParentsRewriter(commit);
                }
            }

            // Create the new commit
            var mappedNewParents = newParents
                .Select(oldParent =>
                        objectMap.ContainsKey(oldParent)
                            ? objectMap[oldParent] as Commit
                            : oldParent)
                .Where(newParent => newParent != null)
                .ToList();

            if (options.PruneEmptyCommits &&
                TryPruneEmptyCommit(commit, mappedNewParents, newTree))
            {
                return;
            }

            var newCommit = repo.ObjectDatabase.CreateCommit(newHeader.Author,
                                                             newHeader.Committer,
                                                             newHeader.Message,
                                                             newTree,
                                                             mappedNewParents,
                                                             true);

            // Record the rewrite
            objectMap[commit] = newCommit;
        }

        private bool TryPruneEmptyCommit(Commit commit, IList<Commit> mappedNewParents, Tree newTree)
        {
            var parent = mappedNewParents.Count > 0 ? mappedNewParents[0] : null;

            if (parent == null)
            {
                if (newTree.Count == 0)
                {
                    objectMap[commit] = null;
                    return true;
                }
            }
            else if (parent.Tree == newTree)
            {
                objectMap[commit] = parent;
                return true;
            }

            return false;
        }

        private GitObject RewriteTarget(GitObject oldTarget)
        {
            // Has this target already been rewritten?
            if (objectMap.ContainsKey(oldTarget))
            {
                return objectMap[oldTarget];
            }

            Debug.Assert((oldTarget as Commit) == null);

            var annotation = oldTarget as TagAnnotation;
            if (annotation == null)
            {
                //TODO: Probably a Tree or a Blob. This is not covered by any test
                return oldTarget;
            }

            // Recursively rewrite annotations if necessary
            var newTarget = RewriteTarget(annotation.Target);

            string newName = annotation.Name;

            if (options.TagNameRewriter != null)
            {
                newName = options.TagNameRewriter(annotation.Name, true, annotation.Target.Sha);
            }

            var newAnnotation = repo.ObjectDatabase.CreateTagAnnotation(newName, newTarget, annotation.Tagger,
                                                              annotation.Message);
            objectMap[annotation] = newAnnotation;
            return newAnnotation;
        }

        private int ReferenceDepth(Reference reference)
        {
            var dref = reference as DirectReference;
            return dref == null
                       ? 1 + ReferenceDepth(((SymbolicReference)reference).Target)
                       : 1;
        }
    }
}