|
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