forked from MaguilarHW/illeGITimate
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTreeBuilder.java
More file actions
193 lines (159 loc) · 7.4 KB
/
Copy pathTreeBuilder.java
File metadata and controls
193 lines (159 loc) · 7.4 KB
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
import java.io.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.util.*;
import org.apache.commons.codec.digest.*;
import components.*;
/**
* Milestones 3.0 + 3.1:
* - Build each tree object that rerpresent directories
* - Each tree entry is one line, ie: "blob <SHA1> <name>" or "tree <SHA1> <name>"
* - Recursvely generate trees bottom-up so that parent hashes update on adds
* - Maintain working list file that mirrors prefixed-index format
* - Stage all found files into Index (index should have BLOBS ONLY)
*/
public class TreeBuilder {
public File repoRoot;
public File objectsDir;
public File workingListFile;
public Index index;
public TreeBuilder(File repoRoot, Index index) throws IOException {
// use the parameter (correct name) to initialize the field
this.repoRoot = repoRoot.getCanonicalFile();
this.objectsDir = new File(this.repoRoot, "git" + File.separator + "objects");
this.workingListFile = new File(this.repoRoot, "git" + File.separator + "working_list");
this.index = index;
if (!objectsDir.isDirectory()) {
throw new IOException("Objects directory not found at " + objectsDir.getAbsolutePath());
}
// Make sure that the list file actually exists (3.1 req)
if (!workingListFile.exists()) workingListFile.createNewFile();
// Clear the working list file
try (BufferedWriter w = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(workingListFile, false), StandardCharsets.UTF_8))) {
// start empty
}
}
/**
* Milestone 3.0/3.1:
* - Adds directory, stages its files (BLOBS) into the Index, and then writes TREE objects for
* all subdirectories up to this dictionary. Returns just teh SHA1 of the top-level tree.
*/
public String addDirectory(String directoryPath) throws IOException {
Path dir = Paths.get(directoryPath).normalize().toAbsolutePath();
if (!Files.isDirectory(dir)) throw new IOException("Not a directory: " + dir);
// Build trees bottom-up and return the hash for the root dir
String topHash = buildTreeForDirectory(dir, dir);
return topHash;
}
/**
* Recursively build a tree object for currentDir.
*/
public String buildTreeForDirectory(Path currentDir, Path rootDir) throws IOException {
// Collect immediate children (files + subdirs)
List<Path> files = new ArrayList<>();
List<Path> dirs = new ArrayList<>();
Files.list(currentDir).forEach(p -> {
// Skip our repository metadata if pointing at the repo root
String name = p.getFileName().toString();
if (name.equals("git")) return; // never walk the repo metadata
if (name.equals(".git")) return; // (safety if naming differs)
if (Files.isDirectory(p)) {
dirs.add(p);
} else if (Files.isRegularFile(p)) {
files.add(p);
}
});
// For files: ensure blob objects exist + stage them in the index.
// Also produce the tree entries ("blob <sha> <basename>")
List<Entry> entries = new ArrayList<>();
for (Path f : files) {
String sha = hashBytes(Files.readAllBytes(f));
// Keep blob in objects if need be (named by SHA1)
writeObjectIfMissing(sha, Files.readAllBytes(f));
// Stage in index (BLOBS ONLY). Use Miles code
index.addFile(f.toFile());
String baseName = f.getFileName().toString();
entries.add(Entry.blob(sha, baseName));
// Working list wants relative path from the originally added directory
// Git stores paths inside tree obejcts with forward slashes NO MATTER WHAT OS
String relPath = unixify(rootDir.relativize(f).toString());
appendWorkingListLine("blob", sha, relPath);
}
// For directories: recurse, then we have a tree hash for each child
for (Path d : dirs) {
String childTreeSha = buildTreeForDirectory(d, rootDir);
String baseName = d.getFileName().toString();
entries.add(Entry.tree(childTreeSha, baseName));
// Working list wants relative path from the originally added directory
String relPath = unixify(rootDir.relativize(d).toString());
appendWorkingListLine("tree", childTreeSha, relPath);
}
// Sort based on:
// 1) blobs before trees (to match many examples),
// 2) then lexicographically by displayed name.
Collections.sort(entries, Comparator.comparing(Entry::typeOrder).thenComparing(Entry::name));
// Build the tree file content (NO trailing newline)
StringBuilder treeContent = new StringBuilder();
for (int i = 0; i < entries.size(); i++) {
Entry e = entries.get(i);
treeContent.append(e.type).append(' ').append(e.sha).append(' ').append(e.name); // Kinda cursed
if (i < entries.size() - 1) treeContent.append('\n');
}
byte[] bytes = treeContent.toString().getBytes(StandardCharsets.UTF_8);
String treeSha = hashBytes(bytes);
// Write the tree object into objects/ named by its SHA1 (idempotent)
writeObjectIfMissing(treeSha, bytes);
return treeSha;
}
/**
* From here are random helpers
*/
// --- helpers (paste these three methods exactly) ---
public static String unixify(String p) {
return p.replace(File.separatorChar, '/');
}
// Works with Miles' code dw
public String hashBytes(byte[] data) throws IOException {
return DigestUtils.sha1Hex(data);
}
public void writeObjectIfMissing(String sha, byte[] data) throws IOException {
File out = new File(objectsDir, sha);
if (out.exists()) return;
// Correct try-with-resources: one closing ')' and a block with { ... }
try (FileOutputStream fos = new FileOutputStream(out)) {
fos.write(data);
}
}
public void appendWorkingListLine(String type, String sha, String relPath) throws IOException {
// Append one line per spec. keep UNIX-style separators and NO trailing spaces.
try (BufferedWriter w = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(workingListFile, true), StandardCharsets.UTF_8))) {
// If file is empty we just write the line. Else always add a newline first
long len = workingListFile.length();
if (len > 0) w.newLine();
w.write(type);
w.write(' ');
w.write(sha);
w.write(' ');
w.write(relPath);
}
}
// Milestone 3.0/3.1:
// Tree entries are "blob <SHA1> <name>" or "tree <SHA1> <name>"
public static class Entry {
String type; // "blob" or "tree"
String sha;
String name; // display name in this directory (no path separators)
public Entry(String type, String sha, String name) {
this.type = type;
this.sha = sha;
this.name = name;
}
static Entry blob(String sha, String name) { return new Entry("blob", sha, name); }
static Entry tree(String sha, String name) { return new Entry("tree", sha, name); }
int typeOrder() { return "blob".equals(type) ? 0 : 1; }
String name() { return name; }
}
}
// PLEASE work