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.pgm.debug;
45
46 import static java.nio.charset.StandardCharsets.UTF_8;
47
48 import java.io.BufferedReader;
49 import java.io.File;
50 import java.io.FileInputStream;
51 import java.io.IOException;
52 import java.io.InputStreamReader;
53 import java.text.MessageFormat;
54 import java.util.ArrayList;
55 import java.util.Date;
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.ListIterator;
59 import java.util.Map;
60
61 import org.eclipse.jgit.errors.MissingObjectException;
62 import org.eclipse.jgit.errors.ObjectWritingException;
63 import org.eclipse.jgit.internal.storage.file.LockFile;
64 import org.eclipse.jgit.lib.CommitBuilder;
65 import org.eclipse.jgit.lib.Constants;
66 import org.eclipse.jgit.lib.ObjectId;
67 import org.eclipse.jgit.lib.ObjectIdRef;
68 import org.eclipse.jgit.lib.ObjectInserter;
69 import org.eclipse.jgit.lib.PersonIdent;
70 import org.eclipse.jgit.lib.ProgressMonitor;
71 import org.eclipse.jgit.lib.Ref;
72 import org.eclipse.jgit.lib.RefUpdate;
73 import org.eclipse.jgit.lib.RefWriter;
74 import org.eclipse.jgit.lib.TextProgressMonitor;
75 import org.eclipse.jgit.pgm.Command;
76 import org.eclipse.jgit.pgm.TextBuiltin;
77 import org.eclipse.jgit.pgm.internal.CLIText;
78 import org.eclipse.jgit.revwalk.RevWalk;
79 import org.kohsuke.args4j.Argument;
80 import org.kohsuke.args4j.Option;
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 @Command(usage = "usage_RebuildCommitGraph")
101 class RebuildCommitGraph extends TextBuiltin {
102 private static final String REALLY = "--destroy-this-repository";
103
104 @Option(name = REALLY, usage = "usage_approveDestructionOfRepository")
105 boolean really;
106
107 @Argument(index = 0, required = true, metaVar = "metaVar_refs", usage = "usage_forEachRefOutput")
108 File refList;
109
110 @Argument(index = 1, required = true, metaVar = "metaVar_refs", usage = "usage_logAllPretty")
111 File graph;
112
113 private final ProgressMonitor pm = new TextProgressMonitor(errw);
114
115 private Map<ObjectId, ObjectId> rewrites = new HashMap<>();
116
117
118 @Override
119 protected void run() throws Exception {
120 if (!really && db.getRefDatabase().hasRefs()) {
121 File directory = db.getDirectory();
122 String absolutePath = directory == null ? "null"
123 : directory.getAbsolutePath();
124 errw.println(
125 MessageFormat.format(CLIText.get().fatalThisProgramWillDestroyTheRepository
126 , absolutePath, REALLY));
127 throw die(CLIText.get().needApprovalToDestroyCurrentRepository);
128 }
129 if (!refList.isFile())
130 throw die(MessageFormat.format(CLIText.get().noSuchFile, refList.getPath()));
131 if (!graph.isFile())
132 throw die(MessageFormat.format(CLIText.get().noSuchFile, graph.getPath()));
133
134 recreateCommitGraph();
135 detachHead();
136 deleteAllRefs();
137 recreateRefs();
138 }
139
140 private void recreateCommitGraph() throws IOException {
141 final Map<ObjectId, ToRewrite> toRewrite = new HashMap<>();
142 List<ToRewrite> queue = new ArrayList<>();
143 try (RevWalk rw = new RevWalk(db);
144 final BufferedReader br = new BufferedReader(
145 new InputStreamReader(new FileInputStream(graph),
146 UTF_8))) {
147 String line;
148 while ((line = br.readLine()) != null) {
149 final String[] parts = line.split("[ \t]{1,}");
150 final ObjectId oldId = ObjectId.fromString(parts[0]);
151 try {
152 rw.parseCommit(oldId);
153
154 continue;
155 } catch (MissingObjectException mue) {
156
157 }
158
159 final long time = Long.parseLong(parts[1]) * 1000L;
160 final ObjectId[] parents = new ObjectId[parts.length - 2];
161 for (int i = 0; i < parents.length; i++) {
162 parents[i] = ObjectId.fromString(parts[2 + i]);
163 }
164
165 final ToRewrite t = new ToRewrite(oldId, time, parents);
166 toRewrite.put(oldId, t);
167 queue.add(t);
168 }
169 }
170
171 pm.beginTask("Rewriting commits", queue.size());
172 try (ObjectInserter oi = db.newObjectInserter()) {
173 final ObjectId emptyTree = oi.insert(Constants.OBJ_TREE,
174 new byte[] {});
175 final PersonIdent me = new PersonIdent("jgit rebuild-commitgraph",
176 "rebuild-commitgraph@localhost");
177 while (!queue.isEmpty()) {
178 final ListIterator<ToRewrite> itr = queue
179 .listIterator(queue.size());
180 queue = new ArrayList<>();
181 REWRITE: while (itr.hasPrevious()) {
182 final ToRewrite t = itr.previous();
183 final ObjectId[] newParents = new ObjectId[t.oldParents.length];
184 for (int k = 0; k < t.oldParents.length; k++) {
185 final ToRewrite p = toRewrite.get(t.oldParents[k]);
186 if (p != null) {
187 if (p.newId == null) {
188
189
190 queue.add(t);
191 continue REWRITE;
192 }
193 newParents[k] = p.newId;
194 } else {
195
196
197 newParents[k] = t.oldParents[k];
198 }
199 }
200
201 final CommitBuilder newc = new CommitBuilder();
202 newc.setTreeId(emptyTree);
203 newc.setAuthor(new PersonIdent(me, new Date(t.commitTime)));
204 newc.setCommitter(newc.getAuthor());
205 newc.setParentIds(newParents);
206 newc.setMessage("ORIGINAL " + t.oldId.name() + "\n");
207 t.newId = oi.insert(newc);
208 rewrites.put(t.oldId, t.newId);
209 pm.update(1);
210 }
211 }
212 oi.flush();
213 }
214 pm.endTask();
215 }
216
217 private static class ToRewrite {
218 final ObjectId oldId;
219
220 final long commitTime;
221
222 final ObjectId[] oldParents;
223
224 ObjectId newId;
225
226 ToRewrite(ObjectId o, long t, ObjectId[] p) {
227 oldId = o;
228 commitTime = t;
229 oldParents = p;
230 }
231 }
232
233 private void detachHead() throws IOException {
234 final String head = db.getFullBranch();
235 final ObjectId id = db.resolve(Constants.HEAD);
236 if (!ObjectId.isId(head) && id != null) {
237 final LockFile lf;
238 lf = new LockFile(new File(db.getDirectory(), Constants.HEAD));
239 if (!lf.lock())
240 throw new IOException(MessageFormat.format(CLIText.get().cannotLock, Constants.HEAD));
241 lf.write(id);
242 if (!lf.commit())
243 throw new IOException(CLIText.get().cannotDeatchHEAD);
244 }
245 }
246
247 private void deleteAllRefs() throws Exception {
248 final RevWalk rw = new RevWalk(db);
249 for (Ref r : db.getRefDatabase().getRefs()) {
250 if (Constants.HEAD.equals(r.getName()))
251 continue;
252 final RefUpdate u = db.updateRef(r.getName());
253 u.setForceUpdate(true);
254 u.delete(rw);
255 }
256 }
257
258 private void recreateRefs() throws Exception {
259 final Map<String, Ref> refs = computeNewRefs();
260 new RefWriter(refs.values()) {
261 @Override
262 protected void writeFile(String name, byte[] content)
263 throws IOException {
264 final File file = new File(db.getDirectory(), name);
265 final LockFile lck = new LockFile(file);
266 if (!lck.lock())
267 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
268 try {
269 lck.write(content);
270 } catch (IOException ioe) {
271 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
272 }
273 if (!lck.commit())
274 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
275 }
276 }.writePackedRefs();
277 }
278
279 private Map<String, Ref> computeNewRefs() throws IOException {
280 final Map<String, Ref> refs = new HashMap<>();
281 try (RevWalk rw = new RevWalk(db);
282 BufferedReader br = new BufferedReader(
283 new InputStreamReader(new FileInputStream(refList),
284 UTF_8))) {
285 String line;
286 while ((line = br.readLine()) != null) {
287 final String[] parts = line.split("[ \t]{1,}");
288 final ObjectId origId = ObjectId.fromString(parts[0]);
289 final String type = parts[1];
290 final String name = parts[2];
291
292 ObjectId id = rewrites.get(origId);
293 if (id == null)
294 id = origId;
295 try {
296 rw.parseAny(id);
297 } catch (MissingObjectException mue) {
298 if (!Constants.TYPE_COMMIT.equals(type)) {
299 errw.println(MessageFormat.format(CLIText.get().skippingObject, type, name));
300 continue;
301 }
302 throw new MissingObjectException(id, type);
303 }
304 refs.put(name, new ObjectIdRef.Unpeeled(Ref.Storage.PACKED,
305 name, id));
306 }
307 }
308 return refs;
309 }
310 }