1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.scm.provider.git.jgit.command;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Date;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.Set;
30
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.maven.scm.ScmFile;
33 import org.apache.maven.scm.ScmFileSet;
34 import org.apache.maven.scm.ScmFileStatus;
35 import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
36 import org.apache.maven.scm.util.FilenameUtils;
37 import org.eclipse.jgit.api.AddCommand;
38 import org.eclipse.jgit.api.Git;
39 import org.eclipse.jgit.api.PushCommand;
40 import org.eclipse.jgit.api.RmCommand;
41 import org.eclipse.jgit.api.Status;
42 import org.eclipse.jgit.api.TransportConfigCallback;
43 import org.eclipse.jgit.api.errors.GitAPIException;
44 import org.eclipse.jgit.diff.DiffEntry;
45 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
46 import org.eclipse.jgit.diff.DiffFormatter;
47 import org.eclipse.jgit.diff.RawTextComparator;
48 import org.eclipse.jgit.errors.CorruptObjectException;
49 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
50 import org.eclipse.jgit.errors.MissingObjectException;
51 import org.eclipse.jgit.errors.StopWalkException;
52 import org.eclipse.jgit.lib.Constants;
53 import org.eclipse.jgit.lib.ObjectId;
54 import org.eclipse.jgit.lib.ProgressMonitor;
55 import org.eclipse.jgit.lib.Ref;
56 import org.eclipse.jgit.lib.Repository;
57 import org.eclipse.jgit.lib.RepositoryBuilder;
58 import org.eclipse.jgit.lib.StoredConfig;
59 import org.eclipse.jgit.lib.TextProgressMonitor;
60 import org.eclipse.jgit.revwalk.RevCommit;
61 import org.eclipse.jgit.revwalk.RevFlag;
62 import org.eclipse.jgit.revwalk.RevSort;
63 import org.eclipse.jgit.revwalk.RevWalk;
64 import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
65 import org.eclipse.jgit.revwalk.filter.RevFilter;
66 import org.eclipse.jgit.transport.CredentialsProvider;
67 import org.eclipse.jgit.transport.PushResult;
68 import org.eclipse.jgit.transport.RefSpec;
69 import org.eclipse.jgit.transport.RemoteRefUpdate;
70 import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
71 import org.eclipse.jgit.util.io.DisabledOutputStream;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 import static org.eclipse.jgit.lib.Constants.R_TAGS;
76
77
78
79
80
81
82
83
84 public class JGitUtils {
85 private static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
86
87 private JGitUtils() {
88
89 }
90
91
92
93
94
95
96
97 public static Git openRepo(File basedir) throws IOException {
98 return new Git(new RepositoryBuilder()
99 .readEnvironment()
100 .findGitDir(basedir)
101 .setMustExist(true)
102 .build());
103 }
104
105
106
107
108
109
110 public static void closeRepo(Git git) {
111 if (git != null && git.getRepository() != null) {
112 git.getRepository().close();
113 }
114 }
115
116
117
118
119
120
121 public static ProgressMonitor getMonitor() {
122
123 return new TextProgressMonitor();
124 }
125
126
127
128
129
130
131
132
133
134
135
136 public static CredentialsProvider prepareSession(Git git, GitScmProviderRepository repository) {
137 StoredConfig config = git.getRepository().getConfig();
138 config.setString("remote", "origin", "url", repository.getFetchUrl());
139 config.setString("remote", "origin", "pushURL", repository.getPushUrl());
140
141
142 LOGGER.info("fetch url: " + repository.getFetchUrlWithMaskedPassword());
143 LOGGER.info("push url: " + repository.getPushUrlWithMaskedPassword());
144 return getCredentials(repository);
145 }
146
147
148
149
150
151
152
153
154
155
156
157 public static CredentialsProvider getCredentials(GitScmProviderRepository repository) {
158 if (StringUtils.isNotBlank(repository.getUser()) && StringUtils.isNotBlank(repository.getPassword())) {
159 return new UsernamePasswordCredentialsProvider(
160 repository.getUser().trim(), repository.getPassword().trim());
161 }
162
163 return null;
164 }
165
166 public static Iterable<PushResult> push(
167 Git git,
168 GitScmProviderRepository repo,
169 RefSpec refSpec,
170 Set<RemoteRefUpdate.Status> successfulStatuses,
171 Optional<TransportConfigCallback> transportConfigCallback)
172 throws PushException {
173 CredentialsProvider credentials = prepareSession(git, repo);
174 PushCommand command = git.push().setRefSpecs(refSpec).setCredentialsProvider(credentials);
175 transportConfigCallback.ifPresent(command::setTransportConfigCallback);
176
177 Iterable<PushResult> pushResultList;
178 try {
179 pushResultList = command.call();
180 } catch (GitAPIException e) {
181 throw new PushException(repo.getPushUrlWithMaskedPassword(), e);
182 }
183 for (PushResult pushResult : pushResultList) {
184 Collection<RemoteRefUpdate> ru = pushResult.getRemoteUpdates();
185 for (RemoteRefUpdate remoteRefUpdate : ru) {
186 if (!successfulStatuses.contains(remoteRefUpdate.getStatus())) {
187 throw new PushException(repo.getPushUrlWithMaskedPassword(), remoteRefUpdate);
188 }
189 LOGGER.debug("Push succeeded {}", remoteRefUpdate);
190 }
191 }
192 return pushResultList;
193 }
194
195
196
197
198
199
200
201 public static boolean hasCommits(Repository repo) {
202 if (repo != null && repo.getDirectory().exists()) {
203 return (new File(repo.getDirectory(), "objects").list().length > 2)
204 || (new File(repo.getDirectory(), "objects/pack").list().length > 0);
205 }
206 return false;
207 }
208
209
210
211
212
213
214
215
216
217
218
219
220 public static List<ScmFile> getFilesInCommit(Repository repository, RevCommit commit)
221 throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException {
222 return getFilesInCommit(repository, commit, null);
223 }
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238 public static List<ScmFile> getFilesInCommit(Repository repository, RevCommit commit, File baseDir)
239 throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException {
240 List<ScmFile> list = new ArrayList<>();
241 if (JGitUtils.hasCommits(repository)) {
242
243 try (RevWalk rw = new RevWalk(repository);
244 DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
245 RevCommit realParent = commit.getParentCount() > 0 ? commit.getParent(0) : commit;
246 RevCommit parent = rw.parseCommit(realParent.getId());
247 df.setRepository(repository);
248 df.setDiffComparator(RawTextComparator.DEFAULT);
249 df.setDetectRenames(true);
250 List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
251 for (DiffEntry diff : diffs) {
252 final String path;
253 if (baseDir != null) {
254 path = relativize(baseDir, new File(repository.getWorkTree(), diff.getNewPath()))
255 .getPath();
256 } else {
257 path = diff.getNewPath();
258 }
259 list.add(new ScmFile(path, ScmFileStatus.CHECKED_IN));
260 }
261 }
262 }
263 return list;
264 }
265
266
267
268
269
270
271
272 public static ScmFileStatus getScmFileStatus(ChangeType changeType) {
273 switch (changeType) {
274 case ADD:
275 return ScmFileStatus.ADDED;
276 case MODIFY:
277 return ScmFileStatus.MODIFIED;
278 case DELETE:
279 return ScmFileStatus.DELETED;
280 case RENAME:
281 return ScmFileStatus.RENAMED;
282 case COPY:
283 return ScmFileStatus.COPIED;
284 default:
285 return ScmFileStatus.UNKNOWN;
286 }
287 }
288
289
290
291
292
293
294
295
296
297
298 public static List<ScmFile> addAllFiles(Git git, ScmFileSet fileSet) throws GitAPIException {
299 File workingCopyRootDirectory = git.getRepository().getWorkTree();
300 AddCommand add = git.add();
301 getWorkingCopyRelativePaths(workingCopyRootDirectory, fileSet).stream()
302 .forEach(f -> add.addFilepattern(toNormalizedFilePath(f)));
303 add.call();
304
305 Status status = git.status().call();
306
307 Set<String> allInIndex = new HashSet<>();
308 allInIndex.addAll(status.getAdded());
309 allInIndex.addAll(status.getChanged());
310 return getScmFilesForAllFileSetFilesContainedInRepoPath(
311 workingCopyRootDirectory, fileSet, allInIndex, ScmFileStatus.ADDED);
312 }
313
314
315
316
317
318
319
320
321
322
323 public static List<ScmFile> removeAllFiles(Git git, ScmFileSet fileSet) throws GitAPIException {
324 File workingCopyRootDirectory = git.getRepository().getWorkTree();
325 RmCommand remove = git.rm();
326 getWorkingCopyRelativePaths(workingCopyRootDirectory, fileSet).stream()
327 .forEach(f -> remove.addFilepattern(toNormalizedFilePath(f)));
328 remove.call();
329
330 Status status = git.status().call();
331
332 Set<String> allInIndex = new HashSet<>(status.getRemoved());
333 return getScmFilesForAllFileSetFilesContainedInRepoPath(
334 workingCopyRootDirectory, fileSet, allInIndex, ScmFileStatus.DELETED);
335 }
336
337
338
339
340
341
342
343
344 public static List<File> getWorkingCopyRelativePaths(File workingCopyDirectory, ScmFileSet fileSet) {
345 List<File> repositoryRelativePaths = new ArrayList<>();
346 for (File path : fileSet.getFileList()) {
347 if (!path.isAbsolute()) {
348 path = new File(fileSet.getBasedir().getPath(), path.getPath());
349 }
350 File repositoryRelativePath = relativize(workingCopyDirectory, path);
351 repositoryRelativePaths.add(repositoryRelativePath);
352 }
353 return repositoryRelativePaths;
354 }
355
356
357
358
359
360
361
362 public static String toNormalizedFilePath(File file) {
363 return FilenameUtils.normalizeFilename(file);
364 }
365
366 private static List<ScmFile> getScmFilesForAllFileSetFilesContainedInRepoPath(
367 File workingCopyDirectory, ScmFileSet fileSet, Set<String> repoFilePaths, ScmFileStatus fileStatus) {
368 List<ScmFile> files = new ArrayList<>(repoFilePaths.size());
369 getWorkingCopyRelativePaths(workingCopyDirectory, fileSet).stream().forEach((relativeFile) -> {
370
371 if (repoFilePaths.contains(toNormalizedFilePath(relativeFile))) {
372
373 ScmFile scmfile = new ScmFile(
374 relativize(fileSet.getBasedir(), new File(workingCopyDirectory, relativeFile.getPath()))
375 .getPath(),
376 fileStatus);
377 files.add(scmfile);
378 }
379 });
380 return files;
381 }
382
383 private static File relativize(File baseDir, File file) {
384 if (file.isAbsolute()) {
385 return baseDir.toPath().relativize(file.toPath()).toFile();
386 }
387 return file;
388 }
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 public static List<RevCommit> getRevCommits(
406 Repository repo,
407 RevSort[] sortings,
408 String fromRev,
409 String toRev,
410 final Date fromDate,
411 final Date toDate,
412 int maxLines)
413 throws IOException, MissingObjectException, IncorrectObjectTypeException {
414
415 List<RevCommit> revs = new ArrayList<>();
416
417 ObjectId fromRevId = fromRev != null ? repo.resolve(fromRev) : null;
418 ObjectId toRevId = toRev != null ? repo.resolve(toRev) : null;
419
420 if (sortings == null || sortings.length == 0) {
421 sortings = new RevSort[] {RevSort.TOPO, RevSort.COMMIT_TIME_DESC};
422 }
423
424 try (RevWalk walk = new RevWalk(repo)) {
425 for (final RevSort s : sortings) {
426 walk.sort(s, true);
427 }
428
429 if (fromDate != null && toDate != null) {
430
431 walk.setRevFilter(new RevFilter() {
432 @Override
433 public boolean include(RevWalk walker, RevCommit cmit)
434 throws StopWalkException, MissingObjectException, IncorrectObjectTypeException,
435 IOException {
436 int cmtTime = cmit.getCommitTime();
437
438 return (cmtTime >= (fromDate.getTime() / 1000)) && (cmtTime <= (toDate.getTime() / 1000));
439 }
440
441 @Override
442 public RevFilter clone() {
443 return this;
444 }
445 });
446 } else {
447 if (fromDate != null) {
448 walk.setRevFilter(CommitTimeRevFilter.after(fromDate));
449 }
450 if (toDate != null) {
451 walk.setRevFilter(CommitTimeRevFilter.before(toDate));
452 }
453 }
454
455 if (fromRevId != null) {
456 RevCommit c = walk.parseCommit(fromRevId);
457 c.add(RevFlag.UNINTERESTING);
458 RevCommit real = walk.parseCommit(c);
459 walk.markUninteresting(real);
460 }
461
462 if (toRevId != null) {
463 RevCommit c = walk.parseCommit(toRevId);
464 c.remove(RevFlag.UNINTERESTING);
465 RevCommit real = walk.parseCommit(c);
466 walk.markStart(real);
467 } else {
468 final ObjectId head = repo.resolve(Constants.HEAD);
469 if (head == null) {
470 throw new IOException("Cannot resolve " + Constants.HEAD);
471 }
472 RevCommit real = walk.parseCommit(head);
473 walk.markStart(real);
474 }
475
476 int n = 0;
477 for (final RevCommit c : walk) {
478 n++;
479 if (maxLines != -1 && n > maxLines) {
480 break;
481 }
482
483 revs.add(c);
484 }
485 return revs;
486 }
487 }
488
489
490
491
492
493
494
495
496 public static List<String> getTags(Repository repo, RevCommit commit) throws IOException {
497 List<Ref> refList = repo.getRefDatabase().getRefsByPrefix(R_TAGS);
498
499 try (RevWalk revWalk = new RevWalk(repo)) {
500 ObjectId commitId = commit.getId();
501 List<String> result = new ArrayList<>();
502
503 for (Ref ref : refList) {
504 ObjectId tagId = ref.getObjectId();
505 RevCommit tagCommit = revWalk.parseCommit(tagId);
506 if (commitId.equals(tagCommit.getId())) {
507 result.add(ref.getName().substring(R_TAGS.length()));
508 }
509 }
510 return result;
511 }
512 }
513 }