Merge branch 'stable-3.6' into stable-3.7
* stable-3.6:
Update jgit to c6b0ee04e49c96e0beec4154196c416abcf2bcc9
config-mail.txt: note about necessary restart
Disallow javascript: protocol in comment links
Reintroduce the Change-Id footer in change screen
Limit the number of changes that can be submitted together
Update git submodules
Register a default kex handler with the SshDaemon
Fix HTTP 404 when browsing tags on Gitweb
Update git submodules
fixup! Add an option to periodically warm the project_list cache
Set version to 3.6.3-SNAPSHOT
GitwebServlet: Retrieve git path from FileRepository
Limit index query results in ChangeNotes#createCheckedUsingIndexLookup
Make delegate() method public
Support per-project enableSignedPush in computePushCertificateValidation
Internally handle refs updated in a BatchRefUpdate by a single event
Also adapt I4e7d9d35c to the new GrChangeView Lit component and
convert to Typescript, converting @property to @state and dropping
the leading '_' for consistency and improved readability.
Release-Notes: skip
Change-Id: I20501dca6ee94c1f7a3a1ab43aee405737a0a3c4
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index be6e140..661af0c 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1507,6 +1507,13 @@
Default is true.
+[[change.maxSubmittableAtOnce]]change.maxSubmittableAtOnce::
++
+Maximum number of changes that can be chained together in the same repository
+to be submitted at once.
++
+Default is 32767.
+
[[change.move]]change.move::
+
Whether the link:rest-api-changes.html#move-change[Move Change] REST
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index bc45956..8bd5dc7 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -20,6 +20,11 @@
example template to an equivalently named file without the `.example` extension
and modifying it will allow an administrator to customize the template.
+[NOTE]
+The content of the templates at `'$site_path'/etc/mail/.*\.soy` are cached at
+startup by Gerrit. If they are modified Gerrit needs to be restarted before the
+changes takes effect.
+
== Supported Mail Templates
Each mail that Gerrit sends out is controlled by at least one template. These
diff --git a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
index ba4d5f0..8b0023b 100644
--- a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
+++ b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
@@ -43,12 +43,13 @@
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.GitwebCgiConfig;
import com.google.gerrit.server.config.GitwebConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.DelegateRepository;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
@@ -85,6 +86,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
@@ -101,7 +103,7 @@
private final Set<String> deniedActions;
private final Path gitwebCgi;
private final URI gitwebUrl;
- private final LocalDiskRepositoryManager repoManager;
+ private final GitRepositoryManager repoManager;
private final ProjectCache projectCache;
private final PermissionBackend permissionBackend;
private final Provider<AnonymousUser> anonymousUserProvider;
@@ -119,12 +121,10 @@
SshInfo sshInfo,
Provider<AnonymousUser> anonymousUserProvider,
GitwebConfig gitwebConfig,
- GitwebCgiConfig gitwebCgiConfig)
+ GitwebCgiConfig gitwebCgiConfig,
+ AllProjectsName allProjects)
throws IOException {
- if (!(repoManager instanceof LocalDiskRepositoryManager)) {
- throw new ProvisionException("Gitweb can only be used with LocalDiskRepositoryManager");
- }
- this.repoManager = (LocalDiskRepositoryManager) repoManager;
+ this.repoManager = repoManager;
this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
this.anonymousUserProvider = anonymousUserProvider;
@@ -132,6 +132,9 @@
this.gitwebCgi = gitwebCgiConfig.getGitwebCgi();
this.deniedActions = new HashSet<>();
+ // ensure that Gitweb works on supported repository type by checking All-Projects project
+ getProjectRoot(allProjects);
+
final String url = gitwebConfig.getUrl();
if (url != null && !url.equals("gitweb")) {
URI uri = null;
@@ -537,7 +540,8 @@
}
}
- private String[] makeEnv(HttpServletRequest req, ProjectState projectState) {
+ private String[] makeEnv(HttpServletRequest req, ProjectState projectState)
+ throws RepositoryNotFoundException, IOException {
final EnvList env = new EnvList(_env);
final int contentLength = Math.max(0, req.getContentLength());
@@ -577,7 +581,7 @@
env.set("GERRIT_CONTEXT_PATH", req.getContextPath() + "/");
env.set("GERRIT_PROJECT_NAME", nameKey.get());
- env.set("GITWEB_PROJECTROOT", repoManager.getBasePath(nameKey).toAbsolutePath().toString());
+ env.set("GITWEB_PROJECTROOT", getProjectRoot(nameKey));
if (projectState.statePermitsRead()
&& permissionBackend
@@ -634,6 +638,25 @@
return env.getEnvArray();
}
+ private String getProjectRoot(Project.NameKey nameKey)
+ throws RepositoryNotFoundException, IOException {
+ try (Repository repo = repoManager.openRepository(nameKey)) {
+ return getProjectRoot(repo);
+ }
+ }
+
+ private String getProjectRoot(Repository repo) {
+ if (repo instanceof DelegateRepository) {
+ return getProjectRoot(((DelegateRepository) repo).delegate());
+ }
+
+ if (repo instanceof FileRepository) {
+ return repo.getDirectory().getAbsolutePath();
+ }
+
+ throw new ProvisionException("Gitweb can only be used with FileRepository");
+ }
+
private void copyContentToCGI(HttpServletRequest req, OutputStream dst) throws IOException {
final int contentLength = req.getContentLength();
final InputStream src = req.getInputStream();
diff --git a/java/com/google/gerrit/server/config/GitwebConfig.java b/java/com/google/gerrit/server/config/GitwebConfig.java
index c477bb5..99bd62d 100644
--- a/java/com/google/gerrit/server/config/GitwebConfig.java
+++ b/java/com/google/gerrit/server/config/GitwebConfig.java
@@ -144,7 +144,7 @@
type.setProject("?p=${project}.git;a=summary");
type.setRevision("?p=${project}.git;a=commit;h=${commit}");
type.setBranch("?p=${project}.git;a=shortlog;h=${branch}");
- type.setTag("?p=${project}.git;a=tag;h=${tag}");
+ type.setTag("?p=${project}.git;a=shortlog;h=${tag}");
type.setRootTree("?p=${project}.git;a=tree;hb=${commit}");
type.setFile("?p=${project}.git;hb=${commit};f=${file}");
type.setFileHistory("?p=${project}.git;a=history;hb=${branch};f=${file}");
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 31934d5..a342686 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -188,7 +188,7 @@
* com.google.gerrit.entities.Project.NameKey} and the numeric change ID are not available.
*/
public ChangeNotes createCheckedUsingIndexLookup(Change.Id changeId) {
- InternalChangeQuery query = queryProvider.get().noFields();
+ InternalChangeQuery query = queryProvider.get().setLimit(2).noFields();
List<ChangeData> changes = query.byLegacyChangeId(changeId);
if (changes.isEmpty()) {
throw new NoSuchChangeException(changeId);
diff --git a/java/com/google/gerrit/server/project/NullProjectCache.java b/java/com/google/gerrit/server/project/NullProjectCache.java
index d19a726..57976d3 100644
--- a/java/com/google/gerrit/server/project/NullProjectCache.java
+++ b/java/com/google/gerrit/server/project/NullProjectCache.java
@@ -62,6 +62,11 @@
}
@Override
+ public void refreshProjectList() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public Set<UUID> guessRelevantGroupUUIDs() {
throw new UnsupportedOperationException();
}
diff --git a/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java b/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
index caffb45..df2e1cf 100644
--- a/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
+++ b/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
@@ -94,7 +94,7 @@
@Override
public void run() {
logger.atFine().log("Loading project_list cache");
- cache.all();
+ cache.refreshProjectList();
logger.atFine().log("Finished loading project_list cache");
}
}
diff --git a/java/com/google/gerrit/server/project/ProjectCache.java b/java/com/google/gerrit/server/project/ProjectCache.java
index fb0a4ec..e0569b9 100644
--- a/java/com/google/gerrit/server/project/ProjectCache.java
+++ b/java/com/google/gerrit/server/project/ProjectCache.java
@@ -94,6 +94,9 @@
/** Returns sorted iteration of projects. */
ImmutableSortedSet<Project.NameKey> all();
+ /** Refreshes project list cache */
+ void refreshProjectList();
+
/**
* Returns estimated set of relevant groups extracted from hot project access rules. If the cache
* is cold or too small for the entire project set of the server, this set may be incomplete.
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 52a524f..67c031e 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -286,6 +286,11 @@
}
@Override
+ public void refreshProjectList() {
+ list.refresh(ListKey.ALL);
+ }
+
+ @Override
public Set<AccountGroup.UUID> guessRelevantGroupUUIDs() {
try (Timer0.Context ignored = guessRelevantGroupsLatency.start()) {
return all().stream()
diff --git a/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java b/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java
index 2a260e41..86d6c674 100644
--- a/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java
+++ b/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java
@@ -30,6 +30,7 @@
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeIsVisibleToPredicate;
@@ -48,6 +49,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
@@ -59,6 +61,8 @@
public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ public static final int MAX_SUBMITTABLE_CHANGES_AT_ONCE_DEFAULT = 1024;
+
public static class LocalMergeSuperSetComputationModule extends AbstractModule {
@Override
protected void configure() {
@@ -83,15 +87,20 @@
private final Map<QueryKey, ImmutableList<ChangeData>> queryCache;
private final Map<BranchNameKey, Optional<RevCommit>> heads;
private final ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory;
+ private final int maxSubmittableChangesAtOnce;
@Inject
LocalMergeSuperSetComputation(
Provider<InternalChangeQuery> queryProvider,
- ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory) {
+ ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory,
+ @GerritServerConfig Config gerritConfig) {
this.queryProvider = queryProvider;
this.queryCache = new HashMap<>();
this.heads = new HashMap<>();
this.changeIsVisibleToPredicateFactory = changeIsVisibleToPredicateFactory;
+ this.maxSubmittableChangesAtOnce =
+ gerritConfig.getInt(
+ "change", "maxSubmittableAtOnce", MAX_SUBMITTABLE_CHANGES_AT_ONCE_DEFAULT);
}
@Override
@@ -130,9 +139,15 @@
}
Set<String> visibleHashes =
- walkChangesByHashes(visibleCommits, Collections.emptySet(), or, branchNameKey);
+ walkChangesByHashes(
+ visibleCommits,
+ Collections.emptySet(),
+ or,
+ branchNameKey,
+ maxSubmittableChangesAtOnce);
Set<String> nonVisibleHashes =
- walkChangesByHashes(nonVisibleCommits, visibleHashes, or, branchNameKey);
+ walkChangesByHashes(
+ nonVisibleCommits, visibleHashes, or, branchNameKey, maxSubmittableChangesAtOnce);
ChangeSet partialSet =
byCommitsOnBranchNotMerged(or, branchNameKey, visibleHashes, nonVisibleHashes, user);
@@ -216,7 +231,11 @@
@UsedAt(UsedAt.Project.GOOGLE)
public Set<String> walkChangesByHashes(
- Collection<RevCommit> sourceCommits, Set<String> ignoreHashes, OpenRepo or, BranchNameKey b)
+ Collection<RevCommit> sourceCommits,
+ Set<String> ignoreHashes,
+ OpenRepo or,
+ BranchNameKey b,
+ int limit)
throws IOException {
Set<String> destHashes = new HashSet<>();
or.rw.reset();
@@ -226,7 +245,11 @@
if (ignoreHashes.contains(name)) {
continue;
}
- destHashes.add(name);
+ if (destHashes.size() < limit) {
+ destHashes.add(name);
+ } else {
+ break;
+ }
or.rw.markStart(c);
}
for (RevCommit c : or.rw) {
@@ -234,7 +257,11 @@
if (ignoreHashes.contains(name)) {
continue;
}
- destHashes.add(name);
+ if (destHashes.size() < limit) {
+ destHashes.add(name);
+ } else {
+ break;
+ }
}
return destHashes;
diff --git a/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java b/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
index 7e0bce9..5a4f073 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
@@ -20,7 +20,9 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
+import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.entities.Project;
@@ -191,6 +193,26 @@
}
@Test
+ @Sandboxed
+ @GerritConfig(name = "change.maxSubmittableAtOnce", value = "2")
+ public void submittedTogetherWithMaxChangesLimit() throws Exception {
+ String targetRef = "refs/for/master";
+
+ commitBuilder().add("a.txt", "1").message("subject: 1").create();
+ pushHead(testRepo, targetRef, false);
+
+ RevCommit c2_1 = commitBuilder().add("b.txt", "2").message("subject: 2").create();
+ String id2 = getChangeId(c2_1);
+ pushHead(testRepo, targetRef, false);
+
+ RevCommit c3_1 = commitBuilder().add("b.txt", "3").message("subject: 3").create();
+ String id3 = getChangeId(c3_1);
+ pushHead(testRepo, targetRef, false);
+
+ assertSubmittedTogether(id3, id3, id2);
+ }
+
+ @Test
public void respectTopicsOnAncestors() throws Exception {
RevCommit initialHead = projectOperations.project(project).getHead("master");
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index d6e1958..aaaf404 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -182,6 +182,13 @@
import {rootUrl} from '../../../utils/url-util';
import {createEditUrl} from '../../../models/views/edit';
+const CHANGE_ID_ERROR = {
+ MISMATCH: 'mismatch',
+ MISSING: 'missing',
+};
+const CHANGE_ID_REGEX_PATTERN =
+ /^(Change-Id:\s|Link:.*\/id\/)(I[0-9a-f]{8,40})/gm;
+
const MIN_LINES_FOR_COMMIT_COLLAPSE = 18;
const REVIEWERS_REGEX = /^(R|CC)=/gm;
@@ -385,6 +392,14 @@
@state()
selectedRevision?: RevisionInfo | EditRevisionInfo;
+ @state()
+ get changeIdCommitMessageError() {
+ return this.computeChangeIdCommitMessageError(
+ this.latestCommitMessage,
+ this.change
+ );
+ }
+
/**
* <gr-change-actions> populates this via two-way data binding.
* Private but used in tests.
@@ -946,6 +961,11 @@
background-color: var(--background-color-secondary);
padding-right: var(--spacing-m);
}
+ .changeId {
+ color: var(--deemphasized-text-color);
+ font-family: var(--font-family);
+ margin-top: var(--spacing-l);
+ }
section {
background-color: var(--view-background-color);
box-shadow: var(--elevation-level-1);
@@ -1469,6 +1489,19 @@
.markdown=${false}
></gr-formatted-text>
</gr-editable-content>
+ <div class="changeId" ?hidden=${!this.changeIdCommitMessageError}>
+ <hr />
+ Change-Id:
+ <span
+ class=${this.computeChangeIdClass(
+ this.changeIdCommitMessageError
+ )}
+ title=${this.computeTitleAttributeWarning(
+ this.changeIdCommitMessageError
+ )}
+ >${this.change?.change_id}</span
+ >
+ </div>
</div>
<h3 class="assistive-tech-only">Comments and Checks Summary</h3>
<gr-change-summary></gr-change-summary>
@@ -2425,6 +2458,60 @@
});
}
+ // private but used in test
+ computeChangeIdClass(displayChangeId?: string | null) {
+ if (displayChangeId) {
+ return displayChangeId === CHANGE_ID_ERROR.MISMATCH ? 'warning' : '';
+ }
+ return '';
+ }
+
+ computeTitleAttributeWarning(displayChangeId?: string | null) {
+ if (!displayChangeId) {
+ return undefined;
+ }
+ if (displayChangeId === CHANGE_ID_ERROR.MISMATCH) {
+ return 'Change-Id mismatch';
+ } else if (displayChangeId === CHANGE_ID_ERROR.MISSING) {
+ return 'No Change-Id in commit message';
+ }
+ return undefined;
+ }
+
+ computeChangeIdCommitMessageError(
+ commitMessage: string | null,
+ change?: ParsedChangeInfo
+ ) {
+ if (change === undefined) {
+ return undefined;
+ }
+
+ if (!commitMessage) {
+ return CHANGE_ID_ERROR.MISSING;
+ }
+
+ // Find the last match in the commit message:
+ let changeId;
+ let changeIdArr;
+
+ while ((changeIdArr = CHANGE_ID_REGEX_PATTERN.exec(commitMessage))) {
+ changeId = changeIdArr[2];
+ }
+
+ if (changeId) {
+ // A change-id is detected in the commit message.
+
+ if (changeId === change.change_id) {
+ // The change-id found matches the real change-id.
+ return null;
+ }
+ // The change-id found does not match the change-id.
+ return CHANGE_ID_ERROR.MISMATCH;
+ }
+ // There is no change-id in the commit message.
+ return CHANGE_ID_ERROR.MISSING;
+ }
+
// Private but used in tests.
computeReplyButtonLabel() {
if (this.diffDrafts === undefined) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index f3017f8..d86007a 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -435,6 +435,11 @@
>
<gr-formatted-text></gr-formatted-text>
</gr-editable-content>
+ <div class="changeId" hidden="">
+ <hr />
+ Change-Id:
+ <span class="" title=""></span>
+ </div>
</div>
<h3 class="assistive-tech-only">
Comments and Checks Summary
@@ -1773,6 +1778,103 @@
element.handleCommitMessageSave(mockEvent('\n\n\n\n\n\n\n\n'));
assert.equal(putStub.lastCall.args[1], '\n\n\n\n\n\n\n\n');
});
+ test('computeChangeIdCommitMessageError', () => {
+ let commitMessage = 'Change-Id: I4ce18b2395bca69d7a9aa48bf4554faa56282483';
+ let change: ParsedChangeInfo = {
+ ...createChangeViewChange(),
+ change_id: 'I4ce18b2395bca69d7a9aa48bf4554faa56282483' as ChangeId,
+ };
+ assert.equal(
+ element.computeChangeIdCommitMessageError(commitMessage, change),
+ null
+ );
+
+ change = {
+ ...createChangeViewChange(),
+ change_id: 'I4ce18b2395bca69d7a9aa48bf4554faa56282484' as ChangeId,
+ };
+ assert.equal(
+ element.computeChangeIdCommitMessageError(commitMessage, change),
+ 'mismatch'
+ );
+
+ commitMessage = 'This is the greatest change.';
+ assert.equal(
+ element.computeChangeIdCommitMessageError(commitMessage, change),
+ 'missing'
+ );
+ });
+
+ test('multiple change Ids in commit message picks last', () => {
+ const commitMessage = [
+ 'Change-Id: I4ce18b2395bca69d7a9aa48bf4554faa56282484',
+ 'Change-Id: I4ce18b2395bca69d7a9aa48bf4554faa56282483',
+ ].join('\n');
+ let change: ParsedChangeInfo = {
+ ...createChangeViewChange(),
+ change_id: 'I4ce18b2395bca69d7a9aa48bf4554faa56282483' as ChangeId,
+ };
+ assert.equal(
+ element.computeChangeIdCommitMessageError(commitMessage, change),
+ null
+ );
+ change = {
+ ...createChangeViewChange(),
+ change_id: 'I4ce18b2395bca69d7a9aa48bf4554faa56282484' as ChangeId,
+ };
+ assert.equal(
+ element.computeChangeIdCommitMessageError(commitMessage, change),
+ 'mismatch'
+ );
+ });
+
+ test('does not count change Id that starts mid line', () => {
+ const commitMessage = [
+ 'Change-Id: I4ce18b2395bca69d7a9aa48bf4554faa56282484',
+ 'Change-Id: I4ce18b2395bca69d7a9aa48bf4554faa56282483',
+ ].join(' and ');
+ let change: ParsedChangeInfo = {
+ ...createChangeViewChange(),
+ change_id: 'I4ce18b2395bca69d7a9aa48bf4554faa56282484' as ChangeId,
+ };
+ assert.equal(
+ element.computeChangeIdCommitMessageError(commitMessage, change),
+ null
+ );
+ change = {
+ ...createChangeViewChange(),
+ change_id: 'I4ce18b2395bca69d7a9aa48bf4554faa56282483' as ChangeId,
+ };
+ assert.equal(
+ element.computeChangeIdCommitMessageError(commitMessage, change),
+ 'mismatch'
+ );
+ });
+
+ test('computeTitleAttributeWarning', () => {
+ let changeIdCommitMessageError = 'missing';
+ assert.equal(
+ element.computeTitleAttributeWarning(changeIdCommitMessageError),
+ 'No Change-Id in commit message'
+ );
+
+ changeIdCommitMessageError = 'mismatch';
+ assert.equal(
+ element.computeTitleAttributeWarning(changeIdCommitMessageError),
+ 'Change-Id mismatch'
+ );
+ });
+
+ test('computeChangeIdClass', () => {
+ let changeIdCommitMessageError = 'missing';
+ assert.equal(element.computeChangeIdClass(changeIdCommitMessageError), '');
+
+ changeIdCommitMessageError = 'mismatch';
+ assert.equal(
+ element.computeChangeIdClass(changeIdCommitMessageError),
+ 'warning'
+ );
+ });
test('topic is coalesced to null', async () => {
sinon.stub(element, 'changeChanged');