|
24 | 24 | import org.eclipse.jgit.api.Git;
|
25 | 25 | import org.eclipse.jgit.api.errors.GitAPIException;
|
26 | 26 | import org.eclipse.jgit.api.errors.RefNotFoundException;
|
| 27 | +import org.eclipse.jgit.api.errors.CheckoutConflictException; |
27 | 28 | import org.eclipse.jgit.lib.ObjectId;
|
28 | 29 | import org.eclipse.jgit.lib.PersonIdent;
|
29 | 30 | import org.eclipse.jgit.revwalk.RevCommit;
|
|
50 | 51 | @Service
|
51 | 52 | public class GitService {
|
52 | 53 |
|
53 |
| - private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
54 |
| - |
55 |
| - // Location to check out git repositories into |
56 |
| - private Path gitStorage; |
57 |
| - |
58 |
| - // Whether submodules are also cloned |
59 |
| - private boolean cloneSubmodules; |
60 |
| - |
61 |
| - @Autowired |
62 |
| - public GitService(@Value("${gitStorage}") Path gitStorage, |
63 |
| - @Value("${gitAPI.cloneSubmodules}") boolean cloneSubmodules) { |
64 |
| - this.gitStorage = gitStorage; |
65 |
| - this.cloneSubmodules = cloneSubmodules; |
66 |
| - } |
67 |
| - |
68 |
| - /** |
69 |
| - * Gets a repository, cloning into a local directory or |
70 |
| - * @param gitDetails The details of the Git repository |
71 |
| - * @param reuseDir Whether the cached repository can be used |
72 |
| - * @return The git object for the repository |
73 |
| - */ |
74 |
| - public Git getRepository(GitDetails gitDetails, boolean reuseDir) |
75 |
| - throws GitAPIException, IOException { |
76 |
| - Git repo; |
77 |
| - if (reuseDir) { |
78 |
| - // Base dir from configuration, name from hash of repository URL |
79 |
| - String baseName = DigestUtils.sha1Hex(GitDetails.normaliseUrl(gitDetails.getRepoUrl())); |
80 |
| - |
81 |
| - // Check if folder already exists |
82 |
| - Path repoDir = gitStorage.resolve(baseName); |
83 |
| - if (Files.isReadable(repoDir) && Files.isDirectory(repoDir)) { |
84 |
| - repo = Git.open(repoDir.toFile()); |
85 |
| - repo.fetch().call(); |
86 |
| - } else { |
87 |
| - // Create a folder and clone repository into it |
88 |
| - Files.createDirectory(repoDir); |
89 |
| - repo = cloneRepo(gitDetails.getRepoUrl(), repoDir.toFile()); |
90 |
| - } |
91 |
| - } else { |
92 |
| - // Another thread is already using the existing folder |
93 |
| - // Must create another temporary one |
94 |
| - repo = cloneRepo(gitDetails.getRepoUrl(), createTempDir()); |
95 |
| - } |
96 |
| - |
97 |
| - // Checkout the specific branch or commit ID |
98 |
| - if (repo != null) { |
99 |
| - // Create a new local branch if it does not exist and not a commit ID |
100 |
| - String branchOrCommitId = gitDetails.getBranch(); |
101 |
| - final boolean isId = ObjectId.isId(branchOrCommitId); |
102 |
| - if (!isId) { |
103 |
| - branchOrCommitId = "refs/remotes/origin/" + branchOrCommitId; |
104 |
| - } |
105 |
| - try { |
106 |
| - repo.checkout() |
107 |
| - .setName(branchOrCommitId) |
108 |
| - .call(); |
109 |
| - } |
110 |
| - catch (Exception ex) { |
111 |
| - // Maybe it was a tag |
112 |
| - if (!isId && ex instanceof RefNotFoundException) { |
113 |
| - final String tag = gitDetails.getBranch(); |
114 |
| - try { |
115 |
| - repo.checkout() |
116 |
| - .setName(tag) |
117 |
| - .call(); |
118 |
| - } |
119 |
| - catch (Exception ex2) { |
120 |
| - // Throw the first exception, to keep the same behavior as before. |
121 |
| - throw ex; |
122 |
| - } |
123 |
| - } |
124 |
| - else { |
125 |
| - throw ex; |
126 |
| - } |
127 |
| - } |
128 |
| - } |
129 |
| - |
130 |
| - return repo; |
131 |
| - } |
132 |
| - |
133 |
| - /** |
134 |
| - * Gets the commit ID of the HEAD for the given repository |
135 |
| - * @param repo The Git repository |
136 |
| - * @return The commit ID of the HEAD for the repository |
137 |
| - * @throws IOException If the HEAD is detached |
138 |
| - */ |
139 |
| - public String getCurrentCommitID(Git repo) throws IOException { |
140 |
| - return repo.getRepository().findRef("HEAD").getObjectId().getName(); |
141 |
| - } |
142 |
| - |
143 |
| - /** |
144 |
| - * Gets a set of authors for a path in a given repository |
145 |
| - * @param repo The git repository |
146 |
| - * @param path The path to get commits for |
147 |
| - * @return An iterable of commits |
148 |
| - * @throws GitAPIException Any API errors which may occur |
149 |
| - * @throws URISyntaxException Error constructing mailto link |
150 |
| - */ |
151 |
| - public Set<HashableAgent> getAuthors(Git repo, String path) throws GitAPIException, URISyntaxException { |
152 |
| - Iterable<RevCommit> logs = repo.log().addPath(path).call(); |
153 |
| - Set<HashableAgent> fileAuthors = new HashSet<>(); |
154 |
| - for (RevCommit rev : logs) { |
155 |
| - // Use author first with backup of committer |
156 |
| - PersonIdent author = rev.getAuthorIdent(); |
157 |
| - if (author == null) { |
158 |
| - author = rev.getCommitterIdent(); |
159 |
| - } |
160 |
| - // Create a new agent and add as much detail as possible |
161 |
| - if (author != null) { |
162 |
| - HashableAgent newAgent = new HashableAgent(); |
163 |
| - String name = author.getName(); |
164 |
| - if (name != null && name.length() > 0) { |
165 |
| - newAgent.setName(author.getName()); |
166 |
| - } |
167 |
| - String email = author.getEmailAddress(); |
168 |
| - if (email != null && email.length() > 0) { |
169 |
| - newAgent.setUri(new URI("mailto:" + author.getEmailAddress())); |
170 |
| - } |
171 |
| - fileAuthors.add(newAgent); |
172 |
| - } |
173 |
| - } |
174 |
| - return fileAuthors; |
175 |
| - } |
176 |
| - |
177 |
| - /** |
178 |
| - * Transfers part of the path to the branch to fix / in branch names |
179 |
| - * @param githubInfo The current Github info possibly with |
180 |
| - * part of the branch name in the path |
181 |
| - * @return A potentially corrected set of Github details, |
182 |
| - * or null if there are no slashes in the path |
183 |
| - */ |
184 |
| - public GitDetails transferPathToBranch(GitDetails githubInfo) { |
185 |
| - String path = githubInfo.getPath(); |
186 |
| - String branch = githubInfo.getBranch(); |
187 |
| - |
188 |
| - int firstSlash = path.indexOf("/"); |
189 |
| - if (firstSlash > 0) { |
190 |
| - branch += "/" + path.substring(0, firstSlash); |
191 |
| - path = path.substring(firstSlash + 1); |
192 |
| - GitDetails newDetails = new GitDetails(githubInfo.getRepoUrl(), branch, path); |
193 |
| - newDetails.setPackedId(githubInfo.getPackedId()); |
194 |
| - return newDetails; |
195 |
| - } else { |
196 |
| - return null; |
197 |
| - } |
198 |
| - } |
199 |
| - |
200 |
| - /** |
201 |
| - * Clones a Git repository |
202 |
| - * @param repoUrl the url of the Git repository |
203 |
| - * @param directory the directory to clone the repo into |
204 |
| - * @return a Git instance |
205 |
| - * @throws GitAPIException if any error occurs cloning the repo |
206 |
| - */ |
207 |
| - protected Git cloneRepo(String repoUrl, File directory) throws GitAPIException { |
208 |
| - return Git.cloneRepository() |
209 |
| - .setCloneSubmodules(cloneSubmodules) |
210 |
| - .setURI(repoUrl) |
211 |
| - .setDirectory(directory) |
212 |
| - .setCloneAllBranches(true) |
213 |
| - .call(); |
214 |
| - } |
| 54 | + private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| 55 | + |
| 56 | + // Location to check out git repositories into |
| 57 | + private Path gitStorage; |
| 58 | + |
| 59 | + // Whether submodules are also cloned |
| 60 | + private boolean cloneSubmodules; |
| 61 | + |
| 62 | + @Autowired |
| 63 | + public GitService(@Value("${gitStorage}") Path gitStorage, |
| 64 | + @Value("${gitAPI.cloneSubmodules}") boolean cloneSubmodules) { |
| 65 | + this.gitStorage = gitStorage; |
| 66 | + this.cloneSubmodules = cloneSubmodules; |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * Gets a repository, cloning into a local directory or |
| 71 | + * |
| 72 | + * @param gitDetails The details of the Git repository |
| 73 | + * @param reuseDir Whether the cached repository can be used |
| 74 | + * @return The git object for the repository |
| 75 | + */ |
| 76 | + public Git getRepository(GitDetails gitDetails, boolean reuseDir) throws GitAPIException, IOException { |
| 77 | + Git repo; |
| 78 | + if (reuseDir) { |
| 79 | + // Base dir from configuration, name from hash of repository URL |
| 80 | + String baseName = DigestUtils.sha1Hex(GitDetails.normaliseUrl(gitDetails.getRepoUrl())); |
| 81 | + |
| 82 | + // Check if folder already exists |
| 83 | + Path repoDir = gitStorage.resolve(baseName); |
| 84 | + if (Files.isReadable(repoDir) && Files.isDirectory(repoDir)) { |
| 85 | + repo = Git.open(repoDir.toFile()); |
| 86 | + repo.fetch().call(); |
| 87 | + } else { |
| 88 | + // Create a folder and clone repository into it |
| 89 | + Files.createDirectory(repoDir); |
| 90 | + try { |
| 91 | + repo = cloneRepo(gitDetails.getRepoUrl(), repoDir.toFile()); |
| 92 | + } catch (CheckoutConflictException ex) { |
| 93 | + repo = cloneRepo(gitDetails.getRepoUrl(), createTempDir()); |
| 94 | + } |
| 95 | + } |
| 96 | + } else { |
| 97 | + // Another thread is already using the existing folder |
| 98 | + // Must create another temporary one |
| 99 | + repo = cloneRepo(gitDetails.getRepoUrl(), createTempDir()); |
| 100 | + } |
| 101 | + |
| 102 | + // Checkout the specific branch or commit ID |
| 103 | + if (repo != null) { |
| 104 | + // Create a new local branch if it does not exist and not a commit ID |
| 105 | + String branchOrCommitId = gitDetails.getBranch(); |
| 106 | + final boolean isId = ObjectId.isId(branchOrCommitId); |
| 107 | + if (!isId) { |
| 108 | + branchOrCommitId = "refs/remotes/origin/" + branchOrCommitId; |
| 109 | + } |
| 110 | + try { |
| 111 | + repo.checkout().setName(branchOrCommitId).call(); |
| 112 | + } catch (Exception ex) { |
| 113 | + // Maybe it was a tag |
| 114 | + if (!isId && ex instanceof RefNotFoundException) { |
| 115 | + final String tag = gitDetails.getBranch(); |
| 116 | + try { |
| 117 | + repo.checkout().setName(tag).call(); |
| 118 | + } catch (Exception ex2) { |
| 119 | + // Throw the first exception, to keep the same behavior as before. |
| 120 | + throw ex; |
| 121 | + } |
| 122 | + } else { |
| 123 | + throw ex; |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + return repo; |
| 129 | + } |
| 130 | + |
| 131 | + /** |
| 132 | + * Gets the commit ID of the HEAD for the given repository |
| 133 | + * |
| 134 | + * @param repo The Git repository |
| 135 | + * @return The commit ID of the HEAD for the repository |
| 136 | + * @throws IOException If the HEAD is detached |
| 137 | + */ |
| 138 | + public String getCurrentCommitID(Git repo) throws IOException { |
| 139 | + return repo.getRepository().findRef("HEAD").getObjectId().getName(); |
| 140 | + } |
| 141 | + |
| 142 | + /** |
| 143 | + * Gets a set of authors for a path in a given repository |
| 144 | + * |
| 145 | + * @param repo The git repository |
| 146 | + * @param path The path to get commits for |
| 147 | + * @return An iterable of commits |
| 148 | + * @throws GitAPIException Any API errors which may occur |
| 149 | + * @throws URISyntaxException Error constructing mailto link |
| 150 | + */ |
| 151 | + public Set<HashableAgent> getAuthors(Git repo, String path) throws GitAPIException, URISyntaxException { |
| 152 | + Iterable<RevCommit> logs = repo.log().addPath(path).call(); |
| 153 | + Set<HashableAgent> fileAuthors = new HashSet<>(); |
| 154 | + for (RevCommit rev : logs) { |
| 155 | + // Use author first with backup of committer |
| 156 | + PersonIdent author = rev.getAuthorIdent(); |
| 157 | + if (author == null) { |
| 158 | + author = rev.getCommitterIdent(); |
| 159 | + } |
| 160 | + // Create a new agent and add as much detail as possible |
| 161 | + if (author != null) { |
| 162 | + HashableAgent newAgent = new HashableAgent(); |
| 163 | + String name = author.getName(); |
| 164 | + if (name != null && name.length() > 0) { |
| 165 | + newAgent.setName(author.getName()); |
| 166 | + } |
| 167 | + String email = author.getEmailAddress(); |
| 168 | + if (email != null && email.length() > 0) { |
| 169 | + newAgent.setUri(new URI("mailto:" + author.getEmailAddress())); |
| 170 | + } |
| 171 | + fileAuthors.add(newAgent); |
| 172 | + } |
| 173 | + } |
| 174 | + return fileAuthors; |
| 175 | + } |
| 176 | + |
| 177 | + /** |
| 178 | + * Transfers part of the path to the branch to fix / in branch names |
| 179 | + * |
| 180 | + * @param githubInfo The current Github info possibly with part of the branch |
| 181 | + * name in the path |
| 182 | + * @return A potentially corrected set of Github details, or null if there are |
| 183 | + * no slashes in the path |
| 184 | + */ |
| 185 | + public GitDetails transferPathToBranch(GitDetails githubInfo) { |
| 186 | + String path = githubInfo.getPath(); |
| 187 | + String branch = githubInfo.getBranch(); |
| 188 | + |
| 189 | + int firstSlash = path.indexOf("/"); |
| 190 | + if (firstSlash > 0) { |
| 191 | + branch += "/" + path.substring(0, firstSlash); |
| 192 | + path = path.substring(firstSlash + 1); |
| 193 | + GitDetails newDetails = new GitDetails(githubInfo.getRepoUrl(), branch, path); |
| 194 | + newDetails.setPackedId(githubInfo.getPackedId()); |
| 195 | + return newDetails; |
| 196 | + } else { |
| 197 | + return null; |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + /** |
| 202 | + * Clones a Git repository |
| 203 | + * |
| 204 | + * @param repoUrl the url of the Git repository |
| 205 | + * @param directory the directory to clone the repo into |
| 206 | + * @return a Git instance |
| 207 | + * @throws GitAPIException if any error occurs cloning the repo |
| 208 | + */ |
| 209 | + protected Git cloneRepo(String repoUrl, File directory) throws GitAPIException { |
| 210 | + return Git.cloneRepository().setCloneSubmodules(cloneSubmodules).setURI(repoUrl).setDirectory(directory) |
| 211 | + .setCloneAllBranches(true).call(); |
| 212 | + } |
215 | 213 | }
|
0 commit comments