View Javadoc
1   /*
2    * Copyright (C) 2010-2012, Robin Stocker <robin@nibor.org> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.merge;
11  
12  import java.util.ArrayList;
13  import java.util.List;
14  
15  import org.eclipse.jgit.lib.Constants;
16  import org.eclipse.jgit.lib.ObjectId;
17  import org.eclipse.jgit.lib.Ref;
18  import org.eclipse.jgit.lib.Repository;
19  import org.eclipse.jgit.util.ChangeIdUtil;
20  import org.eclipse.jgit.util.StringUtils;
21  
22  /**
23   * Formatter for constructing the commit message for a merge commit.
24   * <p>
25   * The format should be the same as C Git does it, for compatibility.
26   */
27  public class MergeMessageFormatter {
28  	/**
29  	 * Construct the merge commit message.
30  	 *
31  	 * @param refsToMerge
32  	 *            the refs which will be merged
33  	 * @param target
34  	 *            the branch ref which will be merged into
35  	 * @return merge commit message
36  	 */
37  	public String format(List<Ref> refsToMerge, Ref target) {
38  		StringBuilder sb = new StringBuilder();
39  		sb.append("Merge "); //$NON-NLS-1$
40  
41  		List<String> branches = new ArrayList<>();
42  		List<String> remoteBranches = new ArrayList<>();
43  		List<String> tags = new ArrayList<>();
44  		List<String> commits = new ArrayList<>();
45  		List<String> others = new ArrayList<>();
46  		for (Ref ref : refsToMerge) {
47  			if (ref.getName().startsWith(Constants.R_HEADS)) {
48  				branches.add("'" + Repository.shortenRefName(ref.getName()) //$NON-NLS-1$
49  						+ "'"); //$NON-NLS-1$
50  			} else if (ref.getName().startsWith(Constants.R_REMOTES)) {
51  				remoteBranches.add("'" //$NON-NLS-1$
52  						+ Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$
53  			} else if (ref.getName().startsWith(Constants.R_TAGS)) {
54  				tags.add("'" + Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$ //$NON-NLS-2$
55  			} else {
56  				ObjectId objectId = ref.getObjectId();
57  				if (objectId != null && ref.getName().equals(objectId.getName())) {
58  					commits.add("'" + ref.getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$
59  				} else {
60  					others.add(ref.getName());
61  				}
62  			}
63  		}
64  
65  		List<String> listings = new ArrayList<>();
66  
67  		if (!branches.isEmpty())
68  			listings.add(joinNames(branches, "branch", "branches")); //$NON-NLS-1$//$NON-NLS-2$
69  
70  		if (!remoteBranches.isEmpty())
71  			listings.add(joinNames(remoteBranches, "remote-tracking branch", //$NON-NLS-1$
72  					"remote-tracking branches")); //$NON-NLS-1$
73  
74  		if (!tags.isEmpty())
75  			listings.add(joinNames(tags, "tag", "tags")); //$NON-NLS-1$ //$NON-NLS-2$
76  
77  		if (!commits.isEmpty())
78  			listings.add(joinNames(commits, "commit", "commits")); //$NON-NLS-1$ //$NON-NLS-2$
79  
80  		if (!others.isEmpty())
81  			listings.add(StringUtils.join(others, ", ", " and ")); //$NON-NLS-1$ //$NON-NLS-2$
82  
83  		sb.append(StringUtils.join(listings, ", ")); //$NON-NLS-1$
84  
85  		String targetName = target.getLeaf().getName();
86  		if (!targetName.equals(Constants.R_HEADS + Constants.MASTER)) {
87  			String targetShortName = Repository.shortenRefName(targetName);
88  			sb.append(" into " + targetShortName); //$NON-NLS-1$
89  		}
90  
91  		return sb.toString();
92  	}
93  
94  	/**
95  	 * Add section with conflicting paths to merge message.
96  	 *
97  	 * @param message
98  	 *            the original merge message
99  	 * @param conflictingPaths
100 	 *            the paths with conflicts
101 	 * @return merge message with conflicting paths added
102 	 */
103 	public String formatWithConflicts(String message,
104 			List<String> conflictingPaths) {
105 		StringBuilder sb = new StringBuilder();
106 		String[] lines = message.split("\n"); //$NON-NLS-1$
107 		int firstFooterLine = ChangeIdUtil.indexOfFirstFooterLine(lines);
108 		for (int i = 0; i < firstFooterLine; i++)
109 			sb.append(lines[i]).append('\n');
110 		if (firstFooterLine == lines.length && message.length() != 0)
111 			sb.append('\n');
112 		addConflictsMessage(conflictingPaths, sb);
113 		if (firstFooterLine < lines.length)
114 			sb.append('\n');
115 		for (int i = firstFooterLine; i < lines.length; i++)
116 			sb.append(lines[i]).append('\n');
117 		return sb.toString();
118 	}
119 
120 	private static void addConflictsMessage(List<String> conflictingPaths,
121 			StringBuilder sb) {
122 		sb.append("Conflicts:\n"); //$NON-NLS-1$
123 		for (String conflictingPath : conflictingPaths) {
124 			sb.append('\t').append(conflictingPath).append('\n');
125 		}
126 	}
127 
128 	private static String joinNames(List<String> names, String singular,
129 			String plural) {
130 		if (names.size() == 1) {
131 			return singular + " " + names.get(0); //$NON-NLS-1$
132 		}
133 		return plural + " " + StringUtils.join(names, ", ", " and "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
134 	}
135 }