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