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 package org.eclipse.jgit.internal.ketch;
45
46 import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
47
48 import java.io.IOException;
49 import java.util.ArrayList;
50 import java.util.HashSet;
51 import java.util.List;
52 import java.util.Set;
53
54 import org.eclipse.jgit.annotations.Nullable;
55 import org.eclipse.jgit.lib.AnyObjectId;
56 import org.eclipse.jgit.lib.CommitBuilder;
57 import org.eclipse.jgit.lib.ObjectId;
58 import org.eclipse.jgit.lib.ObjectInserter;
59 import org.eclipse.jgit.lib.PersonIdent;
60 import org.eclipse.jgit.lib.Repository;
61 import org.eclipse.jgit.revwalk.RevCommit;
62 import org.eclipse.jgit.revwalk.RevObject;
63 import org.eclipse.jgit.revwalk.RevWalk;
64 import org.eclipse.jgit.transport.ReceiveCommand;
65 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
66 import org.eclipse.jgit.treewalk.TreeWalk;
67 import org.eclipse.jgit.treewalk.filter.TreeFilter;
68
69
70
71
72 public class StageBuilder {
73
74
75
76
77
78
79 private static final int SMALL_BATCH_SIZE = 5;
80
81
82
83
84
85
86
87
88
89 private static final int TEMP_PARENT_BATCH_SIZE = 128;
90
91 private static final byte[] PEEL = { ' ', '^' };
92
93 private final String txnStage;
94 private final String txnId;
95
96
97
98
99
100
101
102
103
104
105 public StageBuilder(String txnStageNamespace, ObjectId txnId) {
106 this.txnStage = txnStageNamespace;
107 this.txnId = txnId.name();
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 public List<ReceiveCommand> makeStageList(Repository git, ObjectId oldTree,
137 ObjectId newTree) throws IOException {
138 try (RevWalklk/RevWalk.html#RevWalk">RevWalk rw = new RevWalk(git);
139 TreeWalk tw = new TreeWalk(rw.getObjectReader());
140 ObjectInserter ins = git.newObjectInserter()) {
141 if (AnyObjectId.equals(oldTree, ObjectId.zeroId())) {
142 tw.addTree(new EmptyTreeIterator());
143 } else {
144 tw.addTree(rw.parseTree(oldTree));
145 }
146 tw.addTree(rw.parseTree(newTree));
147 tw.setFilter(TreeFilter.ANY_DIFF);
148 tw.setRecursive(true);
149
150 Set<ObjectId> newObjs = new HashSet<>();
151 while (tw.next()) {
152 if (tw.getRawMode(1) == TYPE_GITLINK
153 && !tw.isPathSuffix(PEEL, 2)) {
154 newObjs.add(tw.getObjectId(1));
155 }
156 }
157
158 List<ReceiveCommand> cmds = makeStageList(newObjs, git, ins);
159 ins.flush();
160 return cmds;
161 }
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182 public List<ReceiveCommand> makeStageList(Set<ObjectId> newObjs,
183 @Nullable Repository git, @Nullable ObjectInserter inserter)
184 throws IOException {
185 if (git == null || newObjs.size() <= SMALL_BATCH_SIZE) {
186
187 List<ReceiveCommand> cmds = new ArrayList<>(newObjs.size());
188 for (ObjectId id : newObjs) {
189 stage(cmds, id);
190 }
191 return cmds;
192 }
193
194 List<ReceiveCommand> cmds = new ArrayList<>();
195 List<RevCommit> commits = new ArrayList<>();
196 reduceObjects(cmds, commits, git, newObjs);
197
198 if (inserter == null || commits.size() <= 1
199 || (cmds.size() + commits.size()) <= SMALL_BATCH_SIZE) {
200
201
202 for (RevCommit c : commits) {
203 stage(cmds, c.copy());
204 }
205 return cmds;
206 }
207
208
209
210
211 ObjectId tip = null;
212 for (int end = commits.size(); end > 0;) {
213 int start = Math.max(0, end - TEMP_PARENT_BATCH_SIZE);
214 List<RevCommit> batch = commits.subList(start, end);
215 List<ObjectId> parents = new ArrayList<>(1 + batch.size());
216 if (tip != null) {
217 parents.add(tip);
218 }
219 parents.addAll(batch);
220
221 CommitBuilder b = new CommitBuilder();
222 b.setTreeId(batch.get(0).getTree());
223 b.setParentIds(parents);
224 b.setAuthor(tmpAuthor(batch));
225 b.setCommitter(b.getAuthor());
226 tip = inserter.insert(b);
227 end = start;
228 }
229 stage(cmds, tip);
230 return cmds;
231 }
232
233 private static PersonIdent tmpAuthor(List<RevCommit> commits) {
234
235 int t = 0;
236 for (int i = 0; i < commits.size();) {
237 t = Math.max(t, commits.get(i).getCommitTime());
238 }
239 String name = "Ketch Stage";
240 String email = "tmp@tmp";
241 return new PersonIdent(name, email, t * 1000L, 0);
242 }
243
244 private void reduceObjects(List<ReceiveCommand> cmds,
245 List<RevCommit> commits, Repository git,
246 Set<ObjectId> newObjs) throws IOException {
247 try (RevWalklk/RevWalk.html#RevWalk">RevWalk rw = new RevWalk(git)) {
248 rw.setRetainBody(false);
249
250 for (ObjectId id : newObjs) {
251 RevObject obj = rw.parseAny(id);
252 if (obj instanceof RevCommit) {
253 rw.markStart((RevCommit) obj);
254 } else {
255 stage(cmds, id);
256 }
257 }
258
259 for (RevCommit c; (c = rw.next()) != null;) {
260 commits.add(c);
261 rw.markUninteresting(c);
262 }
263 }
264 }
265
266 private void stage(List<ReceiveCommand> cmds, ObjectId id) {
267 int estLen = txnStage.length() + txnId.length() + 5;
268 StringBuilder n = new StringBuilder(estLen);
269 n.append(txnStage).append(txnId).append('.');
270 n.append(Integer.toHexString(cmds.size()));
271 cmds.add(new ReceiveCommand(ObjectId.zeroId(), id, n.toString()));
272 }
273 }