Add 'dependency' to the ticket model and to the ui, with editing possibility with a separate relation editor panel
diff --git a/src/main/java/com/gitblit/models/TicketModel.java b/src/main/java/com/gitblit/models/TicketModel.java
index fd0b09e..f8813f0 100644
--- a/src/main/java/com/gitblit/models/TicketModel.java
+++ b/src/main/java/com/gitblit/models/TicketModel.java
@@ -233,6 +233,10 @@
 	public List<String> getLabels() {
 		return getList(Field.labels);
 	}
+	
+	public List<String> getDependencies() {
+		return getList(Field.dependency);
+	}
 
 	public boolean isResponsible(String username) {
 		return username.equals(responsible);
@@ -746,6 +750,26 @@
 				fields.put(field, value.toString());
 			}
 		}
+		
+		public void setDeltaField(Field field, List<String> base, List<String> newValues) {
+			List<String> result = new ArrayList<>();
+			for (String oldValue : base) {
+				if (!newValues.contains(oldValue)) {
+					result.add("-" + oldValue);
+				}
+			}
+			for (String newValue : newValues) {
+				if (!base.contains(newValue)) {
+					result.add("+" + newValue);
+				}
+			}
+			if (result.isEmpty()) {
+				// no change
+				remove(field);
+			} else {
+				setField(field, join(result, ","));
+			}
+		}
 
 		public void remove(Field field) {
 			if (fields != null) {
@@ -1195,7 +1219,7 @@
 
 	public static enum Field {
 		title, body, responsible, type, status, milestone, mergeSha, mergeTo,
-		topic, labels, watchers, reviewers, voters, mentions, priority, severity;
+		topic, labels, watchers, reviewers, voters, mentions, priority, severity, dependency;
 	}
 
 	public static enum Type {
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index 648ac2a..10bfe00 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -513,6 +513,9 @@
 gb.docsWelcome2 = Commit a README.md or a HOME.md file to get started.
 gb.createReadme = create a README
 gb.responsible = responsible
+gb.dependency = dependency
+gb.dependencies = dependencies
+gb.remove = remove
 gb.createdThisTicket = created this ticket
 gb.proposedThisChange = proposed this change
 gb.uploadedPatchsetN = uploaded patchset {0}
@@ -759,4 +762,4 @@
 gb.diffTruncated = Diff truncated after the above file
 gb.opacityAdjust = Adjust opacity
 gb.blinkComparator = Blink comparator
-gb.imgdiffSubtract = Subtract (black = identical)
\ No newline at end of file
+gb.imgdiffSubtract = Subtract (black = identical)
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
index b12d0c7..764adc5 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
@@ -43,6 +43,7 @@
             <tr wicket:id="priority"></tr>

 			<tr wicket:id="responsible"></tr>

 			<tr wicket:id="milestone"></tr>

+			<span wicket:id="dependencies"></span>

 			<tr wicket:id="mergeto"></tr>

 		</table>

 	</div>

diff --git a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
index 192b48c..61e1287 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
@@ -30,10 +30,13 @@
 import org.apache.wicket.markup.html.form.DropDownChoice;

 import org.apache.wicket.markup.html.form.Form;

 import org.apache.wicket.markup.html.form.TextField;

+import org.apache.wicket.markup.html.list.ListItem;

+import org.apache.wicket.markup.html.list.ListView;

 import org.apache.wicket.markup.html.panel.Fragment;

 import org.apache.wicket.model.IModel;

 import org.apache.wicket.model.Model;

 import org.eclipse.jgit.lib.Repository;

+import org.jsoup.helper.StringUtil;

 

 import com.gitblit.Constants;

 import com.gitblit.Constants.AccessPermission;

@@ -51,7 +54,10 @@
 import com.gitblit.utils.StringUtils;

 import com.gitblit.wicket.GitBlitWebSession;

 import com.gitblit.wicket.WicketUtils;

+import com.gitblit.wicket.panels.LinkPanel;

 import com.gitblit.wicket.panels.MarkdownTextArea;

+import com.gitblit.wicket.panels.SimpleAjaxLink;

+import com.gitblit.wicket.panels.TicketRelationEditorPanel;

 import com.google.common.base.Optional;

 

 /**

@@ -81,6 +87,8 @@
 	private IModel<TicketResponsible> responsibleModel;

 

 	private IModel<TicketMilestone> milestoneModel;

+	

+	private IModel<List<String>> dependenciesModel;

 

 	private Label descriptionPreview;

 

@@ -123,6 +131,7 @@
 		statusModel = Model.of(ticket.status);

 		priorityModel = Model.of(ticket.priority);

 		severityModel = Model.of(ticket.severity);

+		dependenciesModel = Model.ofList((List) editable(ticket.getDependencies()));

 

 		setStatelessHint(false);

 		setOutputMarkupId(true);

@@ -141,6 +150,10 @@
 		form.add(new TextField<String>("title", titleModel));

 		form.add(new TextField<String>("topic", topicModel));

 

+		form.setOutputMarkupId(true);

+

+		form.add(new TicketRelationEditorPanel("dependencies", dependenciesModel, getRepositoryModel()));

+

 		final IModel<String> markdownPreviewModel = Model.of(ticket.body == null ? "" : ticket.body);

 		descriptionPreview = new Label("descriptionPreview", markdownPreviewModel);

 		descriptionPreview.setEscapeModelStrings(false);

@@ -311,6 +324,10 @@
 					change.setField(Field.topic, topic);

 				}

 

+				List<String> newDependencies = (List<String>) dependenciesModel.getObject();

+				

+				change.setDeltaField(Field.dependency, ticket.getDependencies(), newDependencies);

+				

 				TicketResponsible responsible = responsibleModel == null ? null : responsibleModel.getObject();

 				if (responsible != null && !responsible.username.equals(ticket.responsible)) {

 					// responsible change

@@ -382,6 +399,11 @@
 		form.add(cancel);

 	}

 

+	private List<String> editable(List<String> list) {

+		// need to copy, if it's an Collection.emptyList 

+		return new ArrayList<String>(list);

+	}

+

 	@Override

 	protected String getPageName() {

 		return getString("gb.editTicket");

diff --git a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
index 9b5af02..d0b7378 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
@@ -43,6 +43,7 @@
             <tr wicket:id="priority"></tr>

 			<tr wicket:id="responsible"></tr>

 			<tr wicket:id="milestone"></tr>

+			<span wicket:id="dependencies"></span>

 			<tr wicket:id="mergeto"></tr>

 		</table>

 	</div>

diff --git a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
index 0c52505..3722be1 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
@@ -51,6 +51,7 @@
 import com.gitblit.wicket.GitBlitWebSession;

 import com.gitblit.wicket.WicketUtils;

 import com.gitblit.wicket.panels.MarkdownTextArea;

+import com.gitblit.wicket.panels.TicketRelationEditorPanel;

 

 /**

  * Page for creating a new ticket.

@@ -80,6 +81,8 @@
 

 	private IModel<TicketModel.Severity> severityModel;

 

+	private IModel<List<String>> dependenciesModel;

+

 	public NewTicketPage(PageParameters params) {

 		super(params);

 

@@ -101,6 +104,7 @@
 		milestoneModel = Model.of();

 		severityModel = Model.of(TicketModel.Severity.defaultSeverity);

 		priorityModel = Model.of(TicketModel.Priority.defaultPriority);

+		dependenciesModel = (IModel) Model.ofList(new ArrayList<String>());

 

 		setStatelessHint(false);

 		setOutputMarkupId(true);

@@ -122,6 +126,8 @@
 		descriptionEditor = new MarkdownTextArea("description", markdownPreviewModel, descriptionPreview);

 		descriptionEditor.setRepository(repositoryName);

 		form.add(descriptionEditor);

+		

+		form.add(new TicketRelationEditorPanel("dependencies", dependenciesModel, getRepositoryModel()));

 

 		if (currentUser.canAdmin(null, getRepositoryModel())) {

 			// responsible

@@ -244,6 +250,8 @@
 				if (!StringUtils.isEmpty(mergeTo)) {

 					change.setField(Field.mergeTo, mergeTo);

 				}

+				

+				change.setDeltaField(Field.dependency, Collections.<String>emptyList(), dependenciesModel.getObject());

 

 				TicketModel ticket = app().tickets().createTicket(getRepositoryModel(), 0L, change);

 				if (ticket != null) {

diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.html b/src/main/java/com/gitblit/wicket/pages/TicketPage.html
index 5ae005e..e3d143d 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.html
@@ -72,6 +72,7 @@
 						<tr><th><wicket:message key="gb.topic"></wicket:message></th><td><span wicket:id="ticketTopic">[topic]</span></td></tr>

 						<tr><th><wicket:message key="gb.responsible"></wicket:message></th><td><span wicket:id="responsible">[responsible]</span></td></tr>

 						<tr><th><wicket:message key="gb.milestone"></wicket:message></th><td><span wicket:id="milestone">[milestone]</span></td></tr>

+						<tr><th><wicket:message key="gb.dependencies"></wicket:message></th><td><span wicket:id="dependencies"><a wicket:id="dependencyLink">[link]</a></span></td></tr>

 						<tr><th><wicket:message key="gb.votes"></wicket:message></th><td><span wicket:id="votes" class="badge">1</span> <a style="padding-left:5px" wicket:id="voteLink" href="#">vote</a></td></tr>

 						<tr><th><wicket:message key="gb.watchers"></wicket:message></th><td><span wicket:id="watchers" class="badge">1</span> <a style="padding-left:5px" wicket:id="watchLink" href="#">watch</a></td></tr>

 						<tr><th><wicket:message key="gb.export"></wicket:message></th><td><a rel="nofollow" target="_blank" wicket:id="exportJson"></a></td></tr>

diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
index 4890874..74484b0 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
@@ -42,6 +42,8 @@
 import org.apache.wicket.markup.html.image.ContextImage;

 import org.apache.wicket.markup.html.link.BookmarkablePageLink;

 import org.apache.wicket.markup.html.link.ExternalLink;

+import org.apache.wicket.markup.html.list.ListItem;

+import org.apache.wicket.markup.html.list.ListView;

 import org.apache.wicket.markup.html.panel.Fragment;

 import org.apache.wicket.markup.repeater.Item;

 import org.apache.wicket.markup.repeater.data.DataView;

@@ -292,6 +294,18 @@
 		}

 		add(new Label("ticketDescription", desc).setEscapeModelStrings(false));

 

+		/*

+		 * DEPENDENCY

+		 */

+		List<String> dependencies = ticket.getDependencies();

+		add(new ListView<String>("dependencies", dependencies) {

+			@Override

+			protected void populateItem(ListItem<String> item) {

+				String ticketId= item.getModelObject();

+				PageParameters tp = WicketUtils.newObjectParameter(ticket.repository, ticketId);

+				item.add(new LinkPanel("dependencyLink", "list subject", "#"+ticketId, TicketsPage.class, tp));

+			}

+		});

 

 		/*

 		 * PARTICIPANTS (DISCUSSION TAB)

diff --git a/src/main/java/com/gitblit/wicket/panels/TicketRelationEditorPanel.html b/src/main/java/com/gitblit/wicket/panels/TicketRelationEditorPanel.html
new file mode 100644
index 0000000..79c381f
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/TicketRelationEditorPanel.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:panel>
+
+	<span wicket:id="dependencyList">
+		<tr>
+			<th><wicket:message key="gb.dependency"></wicket:message></th>
+			<td class="edit">
+				<span wicket:id="dependencyLink" >[ticket label]</span>
+				<button type="submit" class="btn" wicket:id="removeDependencyLink"><wicket:message key="gb.remove"/></button>
+			</td>
+		</tr>
+	</span>
+	<th><wicket:message key="gb.dependency"></wicket:message></th>
+	<td class="edit">
+		<input class="input-large" type="text" wicket:id="addDependencyText"></input>
+		<button class="btn btn-appmenu" wicket:id="addDependency"><wicket:message key="gb.add"/></button>
+	</td>
+
+
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketRelationEditorPanel.java b/src/main/java/com/gitblit/wicket/panels/TicketRelationEditorPanel.java
new file mode 100644
index 0000000..59cc909
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/TicketRelationEditorPanel.java
@@ -0,0 +1,82 @@
+package com.gitblit.wicket.panels;
+
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.jsoup.helper.StringUtil;
+
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.TicketsPage;
+
+public class TicketRelationEditorPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+	
+	private IModel<List<String>> dependenciesModel;
+	private IModel<String> addDependencyModel;
+
+	
+	public TicketRelationEditorPanel(String wicketId, IModel<List<String>> pdependenciesModel, final RepositoryModel repositoryModel) {
+		super(wicketId);
+		this.dependenciesModel = pdependenciesModel;
+		this.addDependencyModel = Model.of();
+		
+		
+		add(new ListView<String>("dependencyList", dependenciesModel) {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void populateItem(ListItem<String> item) {
+				final String ticketId = item.getModelObject();
+
+				PageParameters tp = WicketUtils.newObjectParameter(repositoryModel.name, ticketId);
+				item.add(new LinkPanel("dependencyLink", "list subject", "#"+ticketId, TicketsPage.class, tp));
+				
+				item.add(new AjaxButton("removeDependencyLink") {
+					private static final long serialVersionUID = 1L;
+					@Override
+					public void onSubmit(AjaxRequestTarget target, Form<?> form) {
+						List<String> list = dependenciesModel.getObject();
+						list.remove(ticketId);
+						dependenciesModel.setObject(list);
+						target.addComponent(form);
+					}
+				});
+			}
+		});
+		add(new TextField<String>("addDependencyText", addDependencyModel));
+		add(new AjaxButton("addDependency") {
+			private static final long serialVersionUID = 1L;
+			@Override
+			protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+				String ticketIdStr = addDependencyModel.getObject();
+				if (!StringUtil.isBlank(ticketIdStr)) {
+					try {
+						long ticketId = Long.parseLong(ticketIdStr);
+						if (app().tickets().hasTicket(repositoryModel, ticketId)) {
+							List<String> list = (List<String>) dependenciesModel.getObject();
+							list.add(String.valueOf(ticketId));
+							addDependencyModel.setObject("");
+						}
+					} catch (NumberFormatException e) {
+						// TODO : not allowed
+						
+					}
+				}
+				target.addComponent(form);
+			}
+		});
+		
+		
+	}
+
+}