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