mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-29 19:45:01 +02:00
Bug 302881 Referenced settings are lost on project import
This commit is contained in:
parent
c694ebe16c
commit
876cf6f4c2
5 changed files with 239 additions and 23 deletions
|
@ -51,7 +51,6 @@ public class AllCoreTests {
|
||||||
suite.addTest(MacroTests.suite());
|
suite.addTest(MacroTests.suite());
|
||||||
// suite.addTest(FailedMacroTests.suite());
|
// suite.addTest(FailedMacroTests.suite());
|
||||||
suite.addTest(CPathEntryTest.suite());
|
suite.addTest(CPathEntryTest.suite());
|
||||||
// suite.addTest(CConfigurationDescriptionReferenceTests.suite());
|
|
||||||
//the CProjectDescriptionTests now groups all New Project Model related tests
|
//the CProjectDescriptionTests now groups all New Project Model related tests
|
||||||
//which includes the CConfigurationDescriptionReferenceTests
|
//which includes the CConfigurationDescriptionReferenceTests
|
||||||
suite.addTest(AllCProjectDescriptionTests.suite());
|
suite.addTest(AllCProjectDescriptionTests.suite());
|
||||||
|
|
|
@ -24,6 +24,7 @@ public class AllCProjectDescriptionTests {
|
||||||
// Just add more test cases here as you create them for
|
// Just add more test cases here as you create them for
|
||||||
// each class being tested
|
// each class being tested
|
||||||
suite.addTest(CConfigurationDescriptionReferenceTests.suite());
|
suite.addTest(CConfigurationDescriptionReferenceTests.suite());
|
||||||
|
suite.addTest(CConfigurationDescriptionExportSettings.suite());
|
||||||
suite.addTest(ExternalSettingsProviderTests.suite());
|
suite.addTest(ExternalSettingsProviderTests.suite());
|
||||||
suite.addTest(CfgSettingsTests.suite());
|
suite.addTest(CfgSettingsTests.suite());
|
||||||
suite.addTest(CProjectDescriptionDeltaTests.suite());
|
suite.addTest(CProjectDescriptionDeltaTests.suite());
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2010 Broadcom Corporation and others.
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
*
|
||||||
|
* Contributors:
|
||||||
|
* James Blackburn (Broadcom Corp.)
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
package org.eclipse.cdt.core.settings.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import junit.framework.TestSuite;
|
||||||
|
|
||||||
|
import org.eclipse.cdt.core.internal.errorparsers.tests.ResourceHelper;
|
||||||
|
import org.eclipse.cdt.core.model.CoreModel;
|
||||||
|
import org.eclipse.cdt.core.testplugin.util.BaseTestCase;
|
||||||
|
import org.eclipse.core.resources.IProject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for testing exported settings and project references
|
||||||
|
*/
|
||||||
|
public class CConfigurationDescriptionExportSettings extends BaseTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void tearDown() throws Exception {
|
||||||
|
super.tearDown();
|
||||||
|
ResourceHelper.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TestSuite suite() {
|
||||||
|
return suite(CConfigurationDescriptionExportSettings.class, "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tests for simple reference propagation.
|
||||||
|
* It used to live in the Managedbuild testsuite in ProjectModelTests.java
|
||||||
|
* but is moved here as it doesn't test any managedbuilder specific functionality
|
||||||
|
*/
|
||||||
|
public void testReferences() throws Exception {
|
||||||
|
final String projectName4 = "test4";
|
||||||
|
final String projectName5 = "test5";
|
||||||
|
CoreModel coreModel = CoreModel.getDefault();
|
||||||
|
|
||||||
|
IProject project4 = ResourceHelper.createCDTProjectWithConfig(projectName4);
|
||||||
|
IProject project5 = ResourceHelper.createCDTProjectWithConfig(projectName5);
|
||||||
|
|
||||||
|
ICProjectDescription des4 = coreModel.getProjectDescription(project4);
|
||||||
|
ICProjectDescription des5 = coreModel.getProjectDescription(project5);
|
||||||
|
ICConfigurationDescription dess[] = des5.getConfigurations();
|
||||||
|
|
||||||
|
ICLanguageSettingEntry entries[] = new ICLanguageSettingEntry[]{
|
||||||
|
new CMacroEntry("a", "b", 0),
|
||||||
|
new CMacroEntry("c", "d", 0),
|
||||||
|
new CIncludePathEntry("a/b/c", 0),
|
||||||
|
new CIncludePathEntry("d/e/f", 0),
|
||||||
|
};
|
||||||
|
dess[0].createExternalSetting(null, null, null, entries);
|
||||||
|
dess[0].setActive();
|
||||||
|
|
||||||
|
ICExternalSetting extSettings[] = dess[0].getExternalSettings();
|
||||||
|
assertEquals(extSettings.length, 1);
|
||||||
|
|
||||||
|
checkEquivContents(extSettings[0].getEntries(), entries);
|
||||||
|
List<ICLanguageSettingEntry> list = new ArrayList<ICLanguageSettingEntry>(Arrays.asList(entries));
|
||||||
|
list.remove(3);
|
||||||
|
list.remove(2);
|
||||||
|
checkEquivContents(extSettings[0].getEntries(ICSettingEntry.MACRO), list.toArray());
|
||||||
|
list = new ArrayList<ICLanguageSettingEntry>(Arrays.asList(entries));
|
||||||
|
list.remove(0);
|
||||||
|
list.remove(0);
|
||||||
|
checkEquivContents(extSettings[0].getEntries(ICSettingEntry.INCLUDE_PATH), list.toArray());
|
||||||
|
coreModel.setProjectDescription(project5, des5);
|
||||||
|
|
||||||
|
extSettings = coreModel.getProjectDescription(project5).getActiveConfiguration().getExternalSettings();
|
||||||
|
assertEquals(extSettings.length, 1);
|
||||||
|
|
||||||
|
checkEquivContents(extSettings[0].getEntries(), entries);
|
||||||
|
list = new ArrayList<ICLanguageSettingEntry>(Arrays.asList(entries));
|
||||||
|
list.remove(3);
|
||||||
|
list.remove(2);
|
||||||
|
checkEquivContents(extSettings[0].getEntries(ICSettingEntry.MACRO), list.toArray());
|
||||||
|
list = new ArrayList<ICLanguageSettingEntry>(Arrays.asList(entries));
|
||||||
|
list.remove(0);
|
||||||
|
list.remove(0);
|
||||||
|
checkEquivContents(extSettings[0].getEntries(ICSettingEntry.INCLUDE_PATH), list.toArray());
|
||||||
|
|
||||||
|
dess = des4.getConfigurations();
|
||||||
|
ICLanguageSetting ls = dess[0].getRootFolderDescription().getLanguageSettingForFile("a.c");
|
||||||
|
ICLanguageSettingEntry macros[] = ls.getSettingEntries(ICSettingEntry.MACRO);
|
||||||
|
ICLanguageSettingEntry includes[] = ls.getSettingEntries(ICSettingEntry.INCLUDE_PATH);
|
||||||
|
assertFalse(Arrays.asList(macros).contains(entries[0]));
|
||||||
|
assertFalse(Arrays.asList(macros).contains(entries[1]));
|
||||||
|
assertFalse(Arrays.asList(includes).contains(entries[2]));
|
||||||
|
assertFalse(Arrays.asList(includes).contains(entries[3]));
|
||||||
|
Map<String, String> map = new HashMap<String, String>();
|
||||||
|
map.put(projectName5, "");
|
||||||
|
dess[0].setReferenceInfo(map);
|
||||||
|
ICLanguageSettingEntry updatedMacros[] = ls.getSettingEntries(ICSettingEntry.MACRO);
|
||||||
|
ICLanguageSettingEntry udatedIncludes[] = ls.getSettingEntries(ICSettingEntry.INCLUDE_PATH);
|
||||||
|
assertTrue(Arrays.asList(updatedMacros).contains(entries[0]));
|
||||||
|
assertTrue(Arrays.asList(updatedMacros).contains(entries[1]));
|
||||||
|
assertTrue(Arrays.asList(udatedIncludes).contains(entries[2]));
|
||||||
|
assertTrue(Arrays.asList(udatedIncludes).contains(entries[3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tests importing projects with references works even if the projects are
|
||||||
|
* imported in opposite order
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public void testProjectImport() throws Exception {
|
||||||
|
CoreModel coreModel = CoreModel.getDefault();
|
||||||
|
final IProject libProj = ResourceHelper.createCDTProjectWithConfig("libProj");
|
||||||
|
final IProject mainProj = ResourceHelper.createCDTProjectWithConfig("mainProj");
|
||||||
|
|
||||||
|
// Settings to set on the lib project
|
||||||
|
final ICLanguageSettingEntry entries[] = new ICLanguageSettingEntry[]{
|
||||||
|
new CMacroEntry("a", "b", 0),
|
||||||
|
new CMacroEntry("c", "d", 0),
|
||||||
|
new CIncludePathEntry("a/b/c", 0),
|
||||||
|
new CIncludePathEntry("d/e/f", 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
// set the settings on the lib config; reference it from the main config
|
||||||
|
{
|
||||||
|
ICProjectDescription desLib = coreModel.getProjectDescription(libProj);
|
||||||
|
ICProjectDescription desMain = coreModel.getProjectDescription(mainProj);
|
||||||
|
ICConfigurationDescription cfgLib = desLib.getActiveConfiguration();
|
||||||
|
ICConfigurationDescription cfgMain = desMain.getActiveConfiguration();
|
||||||
|
|
||||||
|
cfgLib.createExternalSetting(null, null, null, entries);
|
||||||
|
coreModel.setProjectDescription(libProj, desLib);
|
||||||
|
|
||||||
|
// Main Project references lib project
|
||||||
|
cfgMain.setReferenceInfo(new HashMap<String, String>() {{ put(libProj.getName(), ""); }});
|
||||||
|
coreModel.setProjectDescription(mainProj, desMain);
|
||||||
|
|
||||||
|
// Referenced settings have been picked up
|
||||||
|
for (ICLanguageSettingEntry e : entries) {
|
||||||
|
assertTrue(cfgMain.getRootFolderDescription().getLanguageSettingForFile("a.c").
|
||||||
|
getSettingEntriesList(e.getKind()).contains(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now delete the two projects, import main first
|
||||||
|
// then lib and check we're A-Ok
|
||||||
|
libProj.delete(false, false, null);
|
||||||
|
mainProj.delete(false, false, null);
|
||||||
|
|
||||||
|
// project description obviously no longer eixsts
|
||||||
|
assertNull(coreModel.getProjectDescription(mainProj));
|
||||||
|
assertNull(coreModel.getProjectDescription(libProj));
|
||||||
|
|
||||||
|
// Re-import the main project first
|
||||||
|
mainProj.create(null);
|
||||||
|
mainProj.open(null);
|
||||||
|
|
||||||
|
// Now re-open the lib project
|
||||||
|
assertFalse(libProj.exists());
|
||||||
|
libProj.create(null);
|
||||||
|
libProj.open(null);
|
||||||
|
|
||||||
|
// Referenced settings should still exist
|
||||||
|
ICConfigurationDescription cfgMain = coreModel.getProjectDescription(mainProj).getActiveConfiguration();
|
||||||
|
ICConfigurationDescription cfgLib = coreModel.getProjectDescription(libProj).getActiveConfiguration();
|
||||||
|
|
||||||
|
checkEquivContents(cfgLib.getExternalSettings()[0].getEntries(), entries);
|
||||||
|
for (ICLanguageSettingEntry e : entries) {
|
||||||
|
assertTrue(cfgMain.getRootFolderDescription().getLanguageSettingForFile("a.c").
|
||||||
|
getSettingEntriesList(e.getKind()).contains(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkEquivContents(Object[] a1, Object[] a2) {
|
||||||
|
if(a1 == null){
|
||||||
|
assertTrue(a2 == null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assertTrue(a2 != null);
|
||||||
|
assertEquals(a1.length, a2.length);
|
||||||
|
|
||||||
|
Set s1 = new HashSet(Arrays.asList(a1));
|
||||||
|
Set s2 = new HashSet(Arrays.asList(a2));
|
||||||
|
assertEquals(a1.length, s1.size());
|
||||||
|
assertEquals(s1, s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Copyright (c) 2007, 2009 Intel Corporation and others.
|
* Copyright (c) 2007, 2010 Intel Corporation and others.
|
||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the Eclipse Public License v1.0
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
* which accompanies this distribution, and is available at
|
* which accompanies this distribution, and is available at
|
||||||
|
@ -156,6 +156,7 @@ public class CExternalSettingsManager implements ICExternalSettingsListener, ICP
|
||||||
try {
|
try {
|
||||||
fContainer = fFactoryDr.getFactory().createContainer(containerId, project, cfgDes, previousSettings);
|
fContainer = fFactoryDr.getFactory().createContainer(containerId, project, cfgDes, previousSettings);
|
||||||
} catch (CoreException e) {
|
} catch (CoreException e) {
|
||||||
|
CCorePlugin.log(e);
|
||||||
}
|
}
|
||||||
if(fContainer == null)
|
if(fContainer == null)
|
||||||
fContainer = NullContainer.INSTANCE;
|
fContainer = NullContainer.INSTANCE;
|
||||||
|
@ -184,6 +185,9 @@ public class CExternalSettingsManager implements ICExternalSettingsListener, ICP
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dummy SettingsContainer with 0 CExternalSettings
|
||||||
|
*/
|
||||||
static class NullContainer extends CExternalSettingsContainer {
|
static class NullContainer extends CExternalSettingsContainer {
|
||||||
static final NullContainer INSTANCE = new NullContainer();
|
static final NullContainer INSTANCE = new NullContainer();
|
||||||
|
|
||||||
|
@ -515,7 +519,7 @@ public class CExternalSettingsManager implements ICExternalSettingsListener, ICP
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CProjectDescriptionManager.getInstance().runWspModification(r, new NullProgressMonitor());
|
CProjectDescriptionManager.runWspModification(r, new NullProgressMonitor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -696,7 +700,7 @@ public class CExternalSettingsManager implements ICExternalSettingsListener, ICP
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
CProjectDescriptionManager.getInstance().runWspModification(r, null);
|
CProjectDescriptionManager.runWspModification(r, null);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -786,12 +790,12 @@ public class CExternalSettingsManager implements ICExternalSettingsListener, ICP
|
||||||
return deltas;
|
return deltas;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExtSettingsDelta[] reconsile(IProject proj, ICConfigurationDescription cfgDes, boolean add, HolderContainer hCr, CContainerRef cr){
|
private ExtSettingsDelta[] reconsile(IProject proj, ICConfigurationDescription cfgDes, boolean addOrChange, HolderContainer hCr, CContainerRef cr){
|
||||||
// if(holder.isReconsiled())
|
// if(holder.isReconsiled())
|
||||||
// return;
|
// return;
|
||||||
CExternalSetting[] newSettings = null;
|
CExternalSetting[] newSettings = null;
|
||||||
CExternalSetting[] oldSettings = hCr.getHolder(false).getExternalSettings();
|
CExternalSetting[] oldSettings = hCr.getHolder(false).getExternalSettings();
|
||||||
if(add){
|
if (addOrChange) {
|
||||||
ContainerDescriptor cdr = createDescriptor(cr.getFactoryId(), cr.getContainerId(), proj, cfgDes, oldSettings);
|
ContainerDescriptor cdr = createDescriptor(cr.getFactoryId(), cr.getContainerId(), proj, cfgDes, oldSettings);
|
||||||
newSettings = cdr.getExternalSettings();
|
newSettings = cdr.getExternalSettings();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Copyright (c) 2007, 2009 Intel Corporation and others.
|
* Copyright (c) 2007, 2010 Intel Corporation and others.
|
||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the Eclipse Public License v1.0
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
* which accompanies this distribution, and is available at
|
* which accompanies this distribution, and is available at
|
||||||
|
@ -27,7 +27,6 @@ import org.eclipse.cdt.core.settings.model.ICExternalSetting;
|
||||||
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
|
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
|
||||||
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
|
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
|
||||||
import org.eclipse.cdt.internal.core.settings.model.CExternalSettingsManager.CContainerRef;
|
import org.eclipse.cdt.internal.core.settings.model.CExternalSettingsManager.CContainerRef;
|
||||||
import org.eclipse.cdt.internal.core.settings.model.CExternalSettingsManager.NullContainer;
|
|
||||||
import org.eclipse.core.resources.IProject;
|
import org.eclipse.core.resources.IProject;
|
||||||
import org.eclipse.core.resources.ResourcesPlugin;
|
import org.eclipse.core.resources.ResourcesPlugin;
|
||||||
import org.eclipse.core.runtime.CoreException;
|
import org.eclipse.core.runtime.CoreException;
|
||||||
|
@ -67,18 +66,24 @@ public class CfgExportSettingContainerFactory extends
|
||||||
CProjectDescriptionManager.getInstance().removeCProjectDescriptionListener(this);
|
CProjectDescriptionManager.getInstance().removeCProjectDescriptionListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ExternalSettingsContainer which returns the settings as
|
||||||
|
* exported by a referenced configuration in another project.
|
||||||
|
*/
|
||||||
private static class CfgRefContainer extends CExternalSettingsContainer {
|
private static class CfgRefContainer extends CExternalSettingsContainer {
|
||||||
private String fProjName, fCfgId;
|
final private String fProjName, fCfgId;
|
||||||
|
final private CExternalSetting[] prevSettings;
|
||||||
|
|
||||||
CfgRefContainer(String projName, String cfgId){
|
CfgRefContainer(String projName, String cfgId, CExternalSetting[] previousSettings){
|
||||||
fProjName = projName;
|
fProjName = projName;
|
||||||
fCfgId = cfgId;
|
fCfgId = cfgId;
|
||||||
|
prevSettings = previousSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CExternalSetting[] getExternalSettings() {
|
public CExternalSetting[] getExternalSettings() {
|
||||||
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(fProjName);
|
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(fProjName);
|
||||||
if(project.exists() && project.isOpen()){
|
if (project.isAccessible()) {
|
||||||
ICProjectDescription des = CProjectDescriptionManager.getInstance().getProjectDescription(project, false);
|
ICProjectDescription des = CProjectDescriptionManager.getInstance().getProjectDescription(project, false);
|
||||||
if(des != null){
|
if(des != null){
|
||||||
ICConfigurationDescription cfg = fCfgId.length() != 0 ?
|
ICConfigurationDescription cfg = fCfgId.length() != 0 ?
|
||||||
|
@ -93,22 +98,20 @@ public class CfgExportSettingContainerFactory extends
|
||||||
return es;
|
return es;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If project doesn't not open in this workspace, just return the previous settings
|
||||||
|
// for the moment. We'll update again when the referenced project reappears
|
||||||
|
return prevSettings;
|
||||||
}
|
}
|
||||||
return new CExternalSetting[0];
|
return new CExternalSetting[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CExternalSettingsContainer createContainer(String id,
|
public CExternalSettingsContainer createContainer(String id,
|
||||||
IProject project, ICConfigurationDescription cfgDes, CExternalSetting[] previousSettings) {
|
IProject project, ICConfigurationDescription cfgDes, CExternalSetting[] previousSettings) throws CoreException {
|
||||||
try {
|
String[] r = parseId(id);
|
||||||
String[] r = parseId(id);
|
return new CfgRefContainer(r[0], r[1], previousSettings);
|
||||||
return new CfgRefContainer(r[0], r[1]);
|
|
||||||
} catch (CoreException e) {
|
|
||||||
CCorePlugin.log(e);
|
|
||||||
}
|
|
||||||
return new NullContainer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void createReference(ICConfigurationDescription cfg, String projName, String cfgId){
|
private static void createReference(ICConfigurationDescription cfg, String projName, String cfgId){
|
||||||
|
@ -178,6 +181,12 @@ public class CfgExportSettingContainerFactory extends
|
||||||
private static String createId(String projName, String cfgId){
|
private static String createId(String projName, String cfgId){
|
||||||
return projName + DELIMITER + cfgId;
|
return projName + DELIMITER + cfgId;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Return a 2-element array String[]{projName, cfgId}
|
||||||
|
* @param id id to parse, must not be null
|
||||||
|
* @return String[]{projName, cfgId}
|
||||||
|
* @throws CoreException if prjName not valid
|
||||||
|
*/
|
||||||
private static String[] parseId(String id) throws CoreException {
|
private static String[] parseId(String id) throws CoreException {
|
||||||
if(id == null)
|
if(id == null)
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
|
|
Loading…
Add table
Reference in a new issue