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 (RevWalklk/RevWalk.html#RevWalk">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 ObjectIdhtml#ObjectId">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 PersonIdentnIdent.html#PersonIdent">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 ObjectIdl#ObjectId">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 } else {
193 newParents[k] = p.newId;
194 }
195 } else {
196
197
198 newParents[k] = t.oldParents[k];
199 }
200 }
201
202 final CommitBuilderlder.html#CommitBuilder">CommitBuilder newc = new CommitBuilder();
203 newc.setTreeId(emptyTree);
204 newc.setAuthor(new PersonIdent(me, new Date(t.commitTime)));
205 newc.setCommitter(newc.getAuthor());
206 newc.setParentIds(newParents);
207 newc.setMessage("ORIGINAL " + t.oldId.name() + "\n");
208 t.newId = oi.insert(newc);
209 rewrites.put(t.oldId, t.newId);
210 pm.update(1);
211 }
212 }
213 oi.flush();
214 }
215 pm.endTask();
216 }
217
218 private static class ToRewrite {
219 final ObjectId oldId;
220
221 final long commitTime;
222
223 final ObjectId[] oldParents;
224
225 ObjectId newId;
226
227 ToRewrite(ObjectIdctId.html#ObjectId">ObjectId o, long t, ObjectId[] p) {
228 oldId = o;
229 commitTime = t;
230 oldParents = p;
231 }
232 }
233
234 private void detachHead() throws IOException {
235 final String head = db.getFullBranch();
236 final ObjectId id = db.resolve(Constants.HEAD);
237 if (!ObjectId.isId(head) && id != null) {
238 final LockFile lf;
239 lf = new LockFile(new File(db.getDirectory(), Constants.HEAD));
240 if (!lf.lock())
241 throw new IOException(MessageFormat.format(CLIText.get().cannotLock, Constants.HEAD));
242 lf.write(id);
243 if (!lf.commit())
244 throw new IOException(CLIText.get().cannotDeatchHEAD);
245 }
246 }
247
248 private void deleteAllRefs() throws Exception {
249 final RevWalklk/RevWalk.html#RevWalk">RevWalk rw = new RevWalk(db);
250 for (Ref r : db.getRefDatabase().getRefs()) {
251 if (Constants.HEAD.equals(r.getName()))
252 continue;
253 final RefUpdate u = db.updateRef(r.getName());
254 u.setForceUpdate(true);
255 u.delete(rw);
256 }
257 }
258
259 private void recreateRefs() throws Exception {
260 final Map<String, Ref> refs = computeNewRefs();
261 new RefWriter(refs.values()) {
262 @Override
263 protected void writeFile(String name, byte[] content)
264 throws IOException {
265 final File file = new File(db.getDirectory(), name);
266 final LockFilel/storage/file/LockFile.html#LockFile">LockFile lck = new LockFile(file);
267 if (!lck.lock())
268 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
269 try {
270 lck.write(content);
271 } catch (IOException ioe) {
272 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
273 }
274 if (!lck.commit())
275 throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
276 }
277 }.writePackedRefs();
278 }
279
280 private Map<String, Ref> computeNewRefs() throws IOException {
281 final Map<String, Ref> refs = new HashMap<>();
282 try (RevWalklk/RevWalk.html#RevWalk">RevWalk rw = new RevWalk(db);
283 BufferedReader br = new BufferedReader(
284 new InputStreamReader(new FileInputStream(refList),
285 UTF_8))) {
286 String line;
287 while ((line = br.readLine()) != null) {
288 final String[] parts = line.split("[ \t]{1,}");
289 final ObjectId origId = ObjectId.fromString(parts[0]);
290 final String type = parts[1];
291 final String name = parts[2];
292
293 ObjectId id = rewrites.get(origId);
294 if (id == null)
295 id = origId;
296 try {
297 rw.parseAny(id);
298 } catch (MissingObjectException mue) {
299 if (!Constants.TYPE_COMMIT.equals(type)) {
300 errw.println(MessageFormat.format(CLIText.get().skippingObject, type, name));
301 continue;
302 }
303 throw new MissingObjectException(id, type);
304 }
305 refs.put(name, new ObjectIdRef.Unpeeled(Ref.Storage.PACKED,
306 name, id));
307 }
308 }
309 return refs;
310 }
311 }