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 public static Git openRepo(File basedir) throws IOException {
97 return new Git(new RepositoryBuilder()
98 .readEnvironment()
99 .findGitDir(basedir)
100 .setMustExist(true)
101 .build());
102 }
103
104
105
106
107
108 public static void closeRepo(Git git) {
109 if (git != null && git.getRepository() != null) {
110 git.getRepository().close();
111 }
112 }
113
114
115
116
117
118
119 public static ProgressMonitor getMonitor() {
120
121 return new TextProgressMonitor();
122 }
123
124
125
126
127
128
129
130
131
132
133
134
135 public static CredentialsProvider prepareSession(Git git, GitScmProviderRepository repository) {
136 StoredConfig config = git.getRepository().getConfig();
137 config.setString("remote", "origin", "url", repository.getFetchUrl());
138 config.setString("remote", "origin", "pushURL", repository.getPushUrl());
139
140
141 LOGGER.info("fetch url: " + repository.getFetchUrlWithMaskedPassword());
142 LOGGER.info("push url: " + repository.getPushUrlWithMaskedPassword());
143 return getCredentials(repository);
144 }
145
146
147
148
149
150
151
152
153
154
155
156 public static CredentialsProvider getCredentials(GitScmProviderRepository repository) {
157 if (StringUtils.isNotBlank(repository.getUser()) && StringUtils.isNotBlank(repository.getPassword())) {
158 return new UsernamePasswordCredentialsProvider(
159 repository.getUser().trim(), repository.getPassword().trim());
160 }
161
162 return null;
163 }
164
165 public static Iterable<PushResult> push(
166 Git git,
167 GitScmProviderRepository repo,
168 RefSpec refSpec,
169 Set<RemoteRefUpdate.Status> successfulStatuses,
170 Optional<TransportConfigCallback> transportConfigCallback)
171 throws PushException {
172 CredentialsProvider credentials = prepareSession(git, repo);
173 PushCommand command = git.push().setRefSpecs(refSpec).setCredentialsProvider(credentials);
174 transportConfigCallback.ifPresent(command::setTransportConfigCallback);
175
176 Iterable<PushResult> pushResultList;
177 try {
178 pushResultList = command.call();
179 } catch (GitAPIException e) {
180 throw new PushException(repo.getPushUrlWithMaskedPassword(), e);
181 }
182 for (PushResult pushResult : pushResultList) {
183 Collection<RemoteRefUpdate> ru = pushResult.getRemoteUpdates();
184 for (RemoteRefUpdate remoteRefUpdate : ru) {
185 if (!successfulStatuses.contains(remoteRefUpdate.getStatus())) {
186 throw new PushException(repo.getPushUrlWithMaskedPassword(), remoteRefUpdate);
187 }
188 LOGGER.debug("Push succeeded {}", remoteRefUpdate);
189 }
190 }
191 return pushResultList;
192 }
193
194
195
196
197
198
199
200 public static boolean hasCommits(Repository repo) {
201 if (repo != null && repo.getDirectory().exists()) {
202 return (new File(repo.getDirectory(), "objects").list().length > 2)
203 || (new File(repo.getDirectory(), "objects/pack").list().length > 0);
204 }
205 return false;
206 }
207
208
209
210
211
212
213
214
215
216
217
218
219 public static List<ScmFile> getFilesInCommit(Repository repository, RevCommit commit)
220 throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException {
221 return getFilesInCommit(repository, commit, null);
222 }
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 public static List<File> getWorkingCopyRelativePaths(File workingCopyDirectory, ScmFileSet fileSet) {
344 List<File> repositoryRelativePaths = new ArrayList<>();
345 for (File path : fileSet.getFileList()) {
346 if (!path.isAbsolute()) {
347 path = new File(fileSet.getBasedir().getPath(), path.getPath());
348 }
349 File repositoryRelativePath = relativize(workingCopyDirectory, path);
350 repositoryRelativePaths.add(repositoryRelativePath);
351 }
352 return repositoryRelativePaths;
353 }
354
355
356
357
358
359
360 public static String toNormalizedFilePath(File file) {
361 return FilenameUtils.normalizeFilename(file);
362 }
363
364 private static List<ScmFile> getScmFilesForAllFileSetFilesContainedInRepoPath(
365 File workingCopyDirectory, ScmFileSet fileSet, Set<String> repoFilePaths, ScmFileStatus fileStatus) {
366 List<ScmFile> files = new ArrayList<>(repoFilePaths.size());
367 getWorkingCopyRelativePaths(workingCopyDirectory, fileSet).stream().forEach((relativeFile) -> {
368
369 if (repoFilePaths.contains(toNormalizedFilePath(relativeFile))) {
370
371 ScmFile scmfile = new ScmFile(
372 relativize(fileSet.getBasedir(), new File(workingCopyDirectory, relativeFile.getPath()))
373 .getPath(),
374 fileStatus);
375 files.add(scmfile);
376 }
377 });
378 return files;
379 }
380
381 private static File relativize(File baseDir, File file) {
382 if (file.isAbsolute()) {
383 return baseDir.toPath().relativize(file.toPath()).toFile();
384 }
385 return file;
386 }
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403 public static List<RevCommit> getRevCommits(
404 Repository repo,
405 RevSort[] sortings,
406 String fromRev,
407 String toRev,
408 final Date fromDate,
409 final Date toDate,
410 int maxLines)
411 throws IOException, MissingObjectException, IncorrectObjectTypeException {
412
413 List<RevCommit> revs = new ArrayList<>();
414
415 ObjectId fromRevId = fromRev != null ? repo.resolve(fromRev) : null;
416 ObjectId toRevId = toRev != null ? repo.resolve(toRev) : null;
417
418 if (sortings == null || sortings.length == 0) {
419 sortings = new RevSort[] {RevSort.TOPO, RevSort.COMMIT_TIME_DESC};
420 }
421
422 try (RevWalk walk = new RevWalk(repo)) {
423 for (final RevSort s : sortings) {
424 walk.sort(s, true);
425 }
426
427 if (fromDate != null && toDate != null) {
428
429 walk.setRevFilter(new RevFilter() {
430 @Override
431 public boolean include(RevWalk walker, RevCommit cmit)
432 throws StopWalkException, MissingObjectException, IncorrectObjectTypeException,
433 IOException {
434 int cmtTime = cmit.getCommitTime();
435
436 return (cmtTime >= (fromDate.getTime() / 1000)) && (cmtTime <= (toDate.getTime() / 1000));
437 }
438
439 @Override
440 public RevFilter clone() {
441 return this;
442 }
443 });
444 } else {
445 if (fromDate != null) {
446 walk.setRevFilter(CommitTimeRevFilter.after(fromDate));
447 }
448 if (toDate != null) {
449 walk.setRevFilter(CommitTimeRevFilter.before(toDate));
450 }
451 }
452
453 if (fromRevId != null) {
454 RevCommit c = walk.parseCommit(fromRevId);
455 c.add(RevFlag.UNINTERESTING);
456 RevCommit real = walk.parseCommit(c);
457 walk.markUninteresting(real);
458 }
459
460 if (toRevId != null) {
461 RevCommit c = walk.parseCommit(toRevId);
462 c.remove(RevFlag.UNINTERESTING);
463 RevCommit real = walk.parseCommit(c);
464 walk.markStart(real);
465 } else {
466 final ObjectId head = repo.resolve(Constants.HEAD);
467 if (head == null) {
468 throw new RuntimeException("Cannot resolve " + Constants.HEAD);
469 }
470 RevCommit real = walk.parseCommit(head);
471 walk.markStart(real);
472 }
473
474 int n = 0;
475 for (final RevCommit c : walk) {
476 n++;
477 if (maxLines != -1 && n > maxLines) {
478 break;
479 }
480
481 revs.add(c);
482 }
483 return revs;
484 }
485 }
486
487
488
489
490
491
492
493
494 public static List<String> getTags(Repository repo, RevCommit commit) throws IOException {
495 List<Ref> refList = repo.getRefDatabase().getRefsByPrefix(R_TAGS);
496
497 try (RevWalk revWalk = new RevWalk(repo)) {
498 ObjectId commitId = commit.getId();
499 List<String> result = new ArrayList<>();
500
501 for (Ref ref : refList) {
502 ObjectId tagId = ref.getObjectId();
503 RevCommit tagCommit = revWalk.parseCommit(tagId);
504 if (commitId.equals(tagCommit.getId())) {
505 result.add(ref.getName().substring(R_TAGS.length()));
506 }
507 }
508 return result;
509 }
510 }
511 }