git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
@ 2008-08-17 20:43 Marek Zawirski
  2008-08-17 20:43 ` [EGIT PATCH 01/31] Fix Repository.mapObject() for missing objects Marek Zawirski
  2008-08-19 17:59 ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Shawn O. Pearce
  0 siblings, 2 replies; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

That's the final series for one of my core tasks for GSoC: GUI related
stuff. Little late, but it required lot of polishing.

Traditionally, it's also available at
http://repo.or.cz/w/egit/zawir.git?a=shortlog;h=refs/heads/push
But wait... this time it was pushed from Eclipse! ;)

The series consists of few parts:
1) Various small fixes and improvements to jgit.
2) Lot of refactor, fixes, improvements in Git Clone Wizard.
3) Push GUI with some general purpose components.
(more in commit messages)

Fetch GUI is not implemented yet, although with this
Eclipse-experience and new stuff available in .components, I suspect
it we'll be *much* much easier to do it than this push GUI, so I may
try to do it as well, especially if you enjoy this one. I'm just
starting working on that.

Changes in plugin were tested on Eclipse 3.4 at Linux, Windows XP and
partially at MacOS 10.4 (there are some issues that makes it looking
worse there, one very annoying bug is reported to Eclipse' bugzilla).
I'm now using mostly Eclipse 3.4, but I've tried to use up to 3.3 API.
Shawn also tried this code at Eclipse 3.3 on OS X 10.5, at least up to
some stage.

Comments regarding this GUI are welcome, I hope that yet another mind
can bring some ideas. Recently (few days ago), Shawn brought some
great idea of making Eclipse view for results of operations on remote
(fetch, push) instead of dialog. That would be cool, although
unfortunatelly it was too late for me to do it - to change project at
this stage.

I've got some problem with icons right now, as I'm pretty far from
stating that I'm good at doing that sort of art.
Robin, Tor, I know that you were already contributing some graphics to
egit. If someone of you would like to do some icon for push/fetch with
pleasure, you are welcome. Otherwise I'll have to do some crappy icon
instead ;) Another matter are checkboxes screenshots. I'm not sure
about legality status of including them. Any ideas if/how we co use
them or some another set that we can for sure?

Although that's last big portion of code during this GSoC from me,
I may send some smaller things in next 2 days or fix this stuff if
needed.

Have fun with push;>
Marek / zawir

Marek Zawirski (31):
  Fix Repository.mapObject() for missing objects
  Fix Repository isValidRefName() for empty names
  Fix Repository.resolve() to not throw runtime exceptions
  Document/fix Transport open method for specific case
  Fix RefSpec javadoc regarding spec expanding
  Make wildcard checking public in RefSpec
  Add openAll() and applyConfig() methods to Transport
  Add dryRun option to Transport and console push
  Extract Transport findRemoteRefUpdatesFor() as static method
  Improve javadoc of Transport push()
  Clean up exception issues in RemoteRefUpdate
  Add another RemoteRefUpdate constructor, useful for 2-stage push
  Add getAllRemoteConfigs() to RemoteConfig
  Add setFetchRefSpecs and setPushRefSpecs to RemoteConfig
  Add simple abbreviate() method to ObjectId
  Remove debug/test console output from GitIndex
  Fix typo in uitext.properties message
  Refactor/rewrite CloneSourcePage to universal RepositorySelectionPage
  Clone wizard and related: refactor, clean-up, fixes or improvements
  Move clone logic away from GitCloneWizard to CloneOperation
  Add canCreateSubdir() heuristic in CloneDestinationPage
  Set FileDialog selection appropriately in clone wizard
  Allow selecting empty dir in clone wizard
  Clone wizard: force dir to suggested path only if repo selection
    change
  Create ListRemoteOperation for listing remote repo branches
  Make Clone's SourceBranchPage more user-friendly
  Add few EPL Eclipse icons
  Checkbox images/screenshots
  Universal GUI for specifications edition: RefSpecPanel and related
  Add PushOperation to plugin
  Push GUI

 .../src/org/spearce/egit/core/CoreText.java        |   18 +
 .../src/org/spearce/egit/core/coretext.properties  |    8 +
 .../org/spearce/egit/core/op/CloneOperation.java   |  129 ++-
 .../spearce/egit/core/op/ListRemoteOperation.java  |  104 ++
 .../org/spearce/egit/core/op/PushOperation.java    |  148 ++
 .../spearce/egit/core/op/PushOperationResult.java  |  273 +++
 .../egit/core/op/PushOperationSpecification.java   |   82 +
 .../icons/checkboxes/disabled_checked.gif          |  Bin 0 -> 166 bytes
 .../icons/checkboxes/disabled_unchecked.gif        |  Bin 0 -> 125 bytes
 .../icons/checkboxes/enabled_checked.gif           |  Bin 0 -> 166 bytes
 .../icons/checkboxes/enabled_unchecked.gif         |  Bin 0 -> 157 bytes
 org.spearce.egit.ui/icons/elcl16/add.gif           |  Bin 0 -> 318 bytes
 org.spearce.egit.ui/icons/elcl16/clear.gif         |  Bin 0 -> 595 bytes
 org.spearce.egit.ui/icons/elcl16/delete.gif        |  Bin 0 -> 351 bytes
 org.spearce.egit.ui/icons/elcl16/trash.gif         |  Bin 0 -> 590 bytes
 org.spearce.egit.ui/plugin.properties              |    3 +
 org.spearce.egit.ui/plugin.xml                     |   15 +
 .../src/org/spearce/egit/ui/UIIcons.java           |   25 +
 .../src/org/spearce/egit/ui/UIText.java            |  398 ++++-
 .../egit/ui/internal/actions/PushAction.java       |   47 +
 .../ui/internal/clone/BranchChangeListener.java    |   13 -
 .../ui/internal/clone/CloneDestinationPage.java    |  165 ++-
 .../egit/ui/internal/clone/CloneSourcePage.java    |  460 -----
 .../egit/ui/internal/clone/GitCloneWizard.java     |   92 +-
 .../egit/ui/internal/clone/SourceBranchPage.java   |  327 ++--
 .../ui/internal/components/BaseWizardPage.java     |   58 +
 .../components/CenteredImageLabelProvider.java     |   52 +
 .../internal/components/CheckboxLabelProvider.java |  138 ++
 .../internal/components/ClickableCellEditor.java   |   68 +
 .../internal/components/ComboLabelingSupport.java  |   77 +
 .../ui/internal/components/RefContentProposal.java |  140 ++
 .../egit/ui/internal/components/RefSpecPage.java   |  237 +++
 .../egit/ui/internal/components/RefSpecPanel.java  | 1823 ++++++++++++++++++++
 .../internal/components/RepositorySelection.java   |  133 ++
 .../components/RepositorySelectionPage.java        |  663 +++++++
 .../SelectionChangeListener.java}                  |   19 +-
 .../egit/ui/internal/push/ConfirmationPage.java    |  211 +++
 .../egit/ui/internal/push/PushResultTable.java     |  327 ++++
 .../spearce/egit/ui/internal/push/PushWizard.java  |  250 +++
 .../ui/internal/push/RefUpdateContentProvider.java |   62 +
 .../egit/ui/internal/push/RefUpdateElement.java    |   67 +
 .../egit/ui/internal/push/ResultDialog.java        |   65 +
 .../src/org/spearce/egit/ui/uitext.properties      |  163 ++-
 .../src/org/spearce/jgit/pgm/Fetch.java            |   10 +-
 .../src/org/spearce/jgit/pgm/Push.java             |   55 +-
 .../src/org/spearce/jgit/pgm/TextBuiltin.java      |    4 -
 .../src/org/spearce/jgit/lib/AnyObjectId.java      |   15 +
 .../src/org/spearce/jgit/lib/GitIndex.java         |    3 -
 .../src/org/spearce/jgit/lib/Repository.java       |   64 +-
 .../src/org/spearce/jgit/lib/RepositoryConfig.java |   21 +
 .../org/spearce/jgit/transport/PushProcess.java    |   18 +-
 .../src/org/spearce/jgit/transport/RefSpec.java    |   29 +-
 .../org/spearce/jgit/transport/RemoteConfig.java   |   50 +
 .../spearce/jgit/transport/RemoteRefUpdate.java    |   40 +-
 .../src/org/spearce/jgit/transport/Transport.java  |  283 +++-
 55 files changed, 6471 insertions(+), 981 deletions(-)
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/op/ListRemoteOperation.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperation.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationResult.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationSpecification.java
 create mode 100644 org.spearce.egit.ui/icons/checkboxes/disabled_checked.gif
 create mode 100644 org.spearce.egit.ui/icons/checkboxes/disabled_unchecked.gif
 create mode 100644 org.spearce.egit.ui/icons/checkboxes/enabled_checked.gif
 create mode 100644 org.spearce.egit.ui/icons/checkboxes/enabled_unchecked.gif
 create mode 100644 org.spearce.egit.ui/icons/elcl16/add.gif
 create mode 100644 org.spearce.egit.ui/icons/elcl16/clear.gif
 create mode 100644 org.spearce.egit.ui/icons/elcl16/delete.gif
 create mode 100644 org.spearce.egit.ui/icons/elcl16/trash.gif
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/PushAction.java
 delete mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/BranchChangeListener.java
 delete mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneSourcePage.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/BaseWizardPage.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CenteredImageLabelProvider.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CheckboxLabelProvider.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ClickableCellEditor.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ComboLabelingSupport.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefContentProposal.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPanel.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelection.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionPage.java
 rename org.spearce.egit.ui/src/org/spearce/egit/ui/internal/{clone/URIishChangeListener.java => components/SelectionChangeListener.java} (51%)
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ConfirmationPage.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushResultTable.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushWizard.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateContentProvider.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateElement.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ResultDialog.java

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [EGIT PATCH 01/31] Fix Repository.mapObject() for missing objects
  2008-08-17 20:43 [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Marek Zawirski
@ 2008-08-17 20:43 ` Marek Zawirski
  2008-08-17 20:43   ` [EGIT PATCH 02/31] Fix Repository isValidRefName() for empty names Marek Zawirski
  2008-08-19 17:59 ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Shawn O. Pearce
  1 sibling, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

When object doesn't exist, instead of returning null as stated in javadoc
this method was throwing NullPointerException. Now it returns null.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/lib/Repository.java       |    5 ++++-
 1 files changed, 4 insertions(+), 1 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index 7679e53..a8591cc 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -385,6 +385,8 @@ public class Repository {
 	 */
 	public Object mapObject(final ObjectId id, final String refName) throws IOException {
 		final ObjectLoader or = openObject(id);
+		if (or == null)
+			return null;
 		final byte[] raw = or.getBytes();
 		if (or.getType() == Constants.OBJ_TREE)
 			return makeTree(id, raw);
@@ -394,7 +396,8 @@ public class Repository {
 			return makeTag(id, refName, raw);
 		if (or.getType() == Constants.OBJ_BLOB)
 			return raw;
-		return null;
+		throw new IncorrectObjectTypeException(id,
+				"COMMIT nor TREE nor BLOB nor TAG");
 	}
 
 	/**
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 02/31] Fix Repository isValidRefName() for empty names
  2008-08-17 20:43 ` [EGIT PATCH 01/31] Fix Repository.mapObject() for missing objects Marek Zawirski
@ 2008-08-17 20:43   ` Marek Zawirski
  2008-08-17 20:43     ` [EGIT PATCH 03/31] Fix Repository.resolve() to not throw runtime exceptions Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Empty name is obviously invalid - method should return false.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/lib/Repository.java       |    3 +++
 1 files changed, 3 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index a8591cc..17cdb40 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -986,6 +986,9 @@ public class Repository {
 	 */
 	public static boolean isValidRefName(final String refName) {
 		final int len = refName.length();
+		if (len == 0)
+			return false;
+		
 		char p = '\0';
 		for (int i=0; i<len; ++i) {
 			char c = refName.charAt(i);
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 03/31] Fix Repository.resolve() to not throw runtime exceptions
  2008-08-17 20:43   ` [EGIT PATCH 02/31] Fix Repository isValidRefName() for empty names Marek Zawirski
@ 2008-08-17 20:43     ` Marek Zawirski
  2008-08-17 20:43       ` [EGIT PATCH 04/31] Document/fix Transport open method for specific case Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

resolve() was throwing undocumented runtime exceptions.
ArrayIndexOutOfBoundsException was thrown when it couldn't find commit's
parents and NumberFormatException when it couldn't parse parents number.

Now it returns null when it can't find appropriate commit's parents
and (already checked) RevisionSyntaxException when it can't parse number.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/lib/Repository.java       |   56 ++++++++++++++++----
 1 files changed, 45 insertions(+), 11 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index 17cdb40..756e3b9 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -569,9 +569,22 @@ public class Repository {
 								break;
 						}
 						String parentnum = new String(rev, i+1, j-i-1);
-						int pnum = Integer.parseInt(parentnum);
-						if (pnum != 0)
-							refId = ((Commit)ref).getParentIds()[pnum - 1];
+						int pnum;
+						try {
+							pnum = Integer.parseInt(parentnum);
+						} catch (NumberFormatException e) {
+							throw new RevisionSyntaxException(
+									"Invalid commit parent number",
+									revstr);
+						}
+						if (pnum != 0) {
+							final ObjectId parents[] = ((Commit) ref)
+									.getParentIds();
+							if (pnum > parents.length)
+								refId = null;
+							else
+								refId = parents[pnum - 1];
+						}
 						i = j - 1;
 						break;
 					case '{':
@@ -632,17 +645,27 @@ public class Repository {
 						break;
 					default:
 						ref = mapObject(refId, null);
-						if (ref instanceof Commit)
-							refId = ((Commit)ref).getParentIds()[0];
-						else
+						if (ref instanceof Commit) {
+							final ObjectId parents[] = ((Commit) ref)
+									.getParentIds();
+							if (parents.length == 0)
+								refId = null;
+							else
+								refId = parents[0];
+						} else
 							throw new IncorrectObjectTypeException(refId,  Constants.TYPE_COMMIT);
 						
 					}
 				} else {
 					ref = mapObject(refId, null);
-					if (ref instanceof Commit)
-						refId = ((Commit)ref).getParentIds()[0];
-					else
+					if (ref instanceof Commit) {
+						final ObjectId parents[] = ((Commit) ref)
+								.getParentIds();
+						if (parents.length == 0)
+							refId = null;
+						else
+							refId = parents[0];
+					} else
 						throw new IncorrectObjectTypeException(refId,  Constants.TYPE_COMMIT);
 				}
 				break;
@@ -658,9 +681,20 @@ public class Repository {
 						break;
 				}
 				String distnum = new String(rev, i+1, l-i-1);
-				int dist = Integer.parseInt(distnum);
+				int dist;
+				try {
+					dist = Integer.parseInt(distnum);
+				} catch (NumberFormatException e) {
+					throw new RevisionSyntaxException(
+							"Invalid ancestry length", revstr);
+				}
 				while (dist >= 0) {
-					refId = ((Commit)ref).getParentIds()[0];
+					final ObjectId[] parents = ((Commit) ref).getParentIds();
+					if (parents.length == 0) {
+						refId = null;
+						break;
+					}
+					refId = parents[0];
 					ref = mapCommit(refId);
 					--dist;
 				}
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 04/31] Document/fix Transport open method for specific case
  2008-08-17 20:43     ` [EGIT PATCH 03/31] Fix Repository.resolve() to not throw runtime exceptions Marek Zawirski
@ 2008-08-17 20:43       ` Marek Zawirski
  2008-08-17 20:43         ` [EGIT PATCH 05/31] Fix RefSpec javadoc regarding spec expanding Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

RemoteConfig may have empty URIs list.
It would be nicer to throw documented exception than
ArrayIndexOutOfBoundsException in this case.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/transport/Transport.java  |    7 +++++++
 1 files changed, 7 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
index 5bec4d2..30175e3 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
@@ -103,9 +103,16 @@ public abstract class Transport {
 	 * @return the new transport instance. Never null.
 	 * @throws NotSupportedException
 	 *             the protocol specified is not supported.
+	 * @throws IllegalArgumentException
+	 *             if provided remote configuration doesn't have any URI
+	 *             associated.
 	 */
 	public static Transport open(final Repository local, final RemoteConfig cfg)
 			throws NotSupportedException {
+		if (cfg.getURIs().isEmpty())
+			throw new IllegalArgumentException(
+					"Remote config \""
+					+ cfg.getName() + "\" has no URIs associated");
 		final Transport tn = open(local, cfg.getURIs().get(0));
 		tn.setOptionUploadPack(cfg.getUploadPack());
 		tn.fetch = cfg.getFetchRefSpecs();
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 05/31] Fix RefSpec javadoc regarding spec expanding
  2008-08-17 20:43       ` [EGIT PATCH 04/31] Document/fix Transport open method for specific case Marek Zawirski
@ 2008-08-17 20:43         ` Marek Zawirski
  2008-08-17 20:43           ` [EGIT PATCH 06/31] Make wildcard checking public in RefSpec Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

We can give expandSource() a wildcard as argument, so it will produce
new wildcard spec, which is still correct.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/transport/RefSpec.java    |   14 ++++++++++----
 1 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RefSpec.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RefSpec.java
index 25d5977..e489233 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RefSpec.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RefSpec.java
@@ -331,8 +331,11 @@ public class RefSpec {
 	 * otherwise expansion results may be unpredictable.
 	 *
 	 * @param r
-	 *            a ref name that matched our source specification.
-	 * @return a new specification that is not a wildcard.
+	 *            a ref name that matched our source specification. Could be a
+	 *            wildcard also.
+	 * @return a new specification expanded from provided ref name. Result
+	 *         specification is wildcard if and only if provided ref name is
+	 *         wildcard.
 	 */
 	public RefSpec expandFromSource(final String r) {
 		return isWildcard() ? new RefSpec(this, r) : this;
@@ -345,8 +348,11 @@ public class RefSpec {
 	 * otherwise expansion results may be unpredictable.
 	 * 
 	 * @param r
-	 *            a ref that matched our source specification.
-	 * @return a new specification that is not a wildcard.
+	 *            a ref that matched our source specification. Could be a
+	 *            wildcard also.
+	 * @return a new specification expanded from provided ref name. Result
+	 *         specification is wildcard if and only if provided ref name is
+	 *         wildcard.
 	 */
 	public RefSpec expandFromSource(final Ref r) {
 		return isWildcard() ? new RefSpec(this, r.getName()) : this;
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 06/31] Make wildcard checking public in RefSpec
  2008-08-17 20:43         ` [EGIT PATCH 05/31] Fix RefSpec javadoc regarding spec expanding Marek Zawirski
@ 2008-08-17 20:43           ` Marek Zawirski
  2008-08-17 20:43             ` [EGIT PATCH 07/31] Add openAll() and applyConfig() methods to Transport Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

This constant and method can/should be reused in GUI.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/transport/RefSpec.java    |   15 +++++++++++++--
 1 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RefSpec.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RefSpec.java
index e489233..9ec5847 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RefSpec.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RefSpec.java
@@ -47,9 +47,20 @@ import org.spearce.jgit.lib.Ref;
  * reference in one repository to another reference in another repository.
  */
 public class RefSpec {
-	private static final String WILDCARD_SUFFIX = "/*";
+	/**
+	 * Suffix for wildcard ref spec component, that indicate matching all refs
+	 * with specified prefix.
+	 */
+	public static final String WILDCARD_SUFFIX = "/*";
 
-	private static boolean isWildcard(final String s) {
+	/**
+	 * Check whether provided string is a wildcard ref spec component.
+	 * 
+	 * @param s
+	 *            ref spec component - string to test. Can be null.
+	 * @return true if provided string is a wildcard ref spec component.
+	 */
+	public static boolean isWildcard(final String s) {
 		return s != null && s.endsWith(WILDCARD_SUFFIX);
 	}
 
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 07/31] Add openAll() and applyConfig() methods to Transport
  2008-08-17 20:43           ` [EGIT PATCH 06/31] Make wildcard checking public in RefSpec Marek Zawirski
@ 2008-08-17 20:43             ` Marek Zawirski
  2008-08-17 20:43               ` [EGIT PATCH 08/31] Add dryRun option to Transport and console push Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

openAll() method honours many URIs in remote configuration when opening
transports. Old open() calls remained, they still open only 1 transport
at time.

openAll() is used during push operation - pgm.Push implementation was
fixed to use it.

applyConfig() is used internally here, but could be interesting for
clients and it's safe to call it.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/pgm/Push.java             |   44 ++++++-----
 .../src/org/spearce/jgit/transport/Transport.java  |   83 ++++++++++++++++++--
 2 files changed, 99 insertions(+), 28 deletions(-)

diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
index 6b35ab8..a952309 100644
--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
@@ -86,7 +86,7 @@ class Push extends TextBuiltin {
 	@Option(name = "--receive-pack", metaVar = "path")
 	private String receivePack;
 
-	private boolean first = true;
+	private boolean shownURI;
 
 	@Override
 	protected void run() throws Exception {
@@ -97,27 +97,31 @@ class Push extends TextBuiltin {
 				refSpecs.add(spec.setForceUpdate(true));
 		}
 
-		final Transport transport = Transport.open(db, remote);
-		transport.setPushThin(thin);
-		if (receivePack != null)
-			transport.setOptionReceivePack(receivePack);
-		final Collection<RemoteRefUpdate> toPush = transport
-				.findRemoteRefUpdatesFor(refSpecs);
+		final List<Transport> transports = Transport.openAll(db, remote);
+		for (final Transport transport : transports) {
+			transport.setPushThin(thin);
+			if (receivePack != null)
+				transport.setOptionReceivePack(receivePack);
 
-		final PushResult result = transport.push(new TextProgressMonitor(),
-				toPush);
-		transport.close();
+			final Collection<RemoteRefUpdate> toPush = transport
+					.findRemoteRefUpdatesFor(refSpecs);
 
-		printPushResult(result);
+			final PushResult result = transport.push(new TextProgressMonitor(),
+					toPush);
+			printPushResult(transport, result);
+		}
 	}
 
-	private void printPushResult(final PushResult result) {
+	private void printPushResult(final Transport transport,
+			final PushResult result) {
+		shownURI = false;
 		boolean everythingUpToDate = true;
+
 		// at first, print up-to-date ones...
 		for (final RemoteRefUpdate rru : result.getRemoteUpdates()) {
 			if (rru.getStatus() == Status.UP_TO_DATE) {
 				if (verbose)
-					printRefUpdateResult(result, rru);
+					printRefUpdateResult(transport, result, rru);
 			} else
 				everythingUpToDate = false;
 		}
@@ -125,25 +129,25 @@ class Push extends TextBuiltin {
 		for (final RemoteRefUpdate rru : result.getRemoteUpdates()) {
 			// ...then successful updates...
 			if (rru.getStatus() == Status.OK)
-				printRefUpdateResult(result, rru);
+				printRefUpdateResult(transport, result, rru);
 		}
 
 		for (final RemoteRefUpdate rru : result.getRemoteUpdates()) {
 			// ...finally, others (problematic)
 			if (rru.getStatus() != Status.OK
 					&& rru.getStatus() != Status.UP_TO_DATE)
-				printRefUpdateResult(result, rru);
+				printRefUpdateResult(transport, result, rru);
 		}
 
 		if (everythingUpToDate)
 			out.println("Everything up-to-date");
 	}
 
-	private void printRefUpdateResult(final PushResult result,
-			final RemoteRefUpdate rru) {
-		if (first) {
-			first = false;
-			out.format("To %s\n", result.getURI());
+	private void printRefUpdateResult(final Transport transport,
+			final PushResult result, final RemoteRefUpdate rru) {
+		if (!shownURI) {
+			shownURI = true;
+			out.format("To %s\n", transport.getURI());
 		}
 
 		final String remoteName = rru.getRemoteName();
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
index 30175e3..73aa771 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
@@ -75,8 +75,10 @@ public abstract class Transport {
 	 * @param local
 	 *            existing local repository.
 	 * @param remote
-	 *            location of the remote repository.
-	 * @return the new transport instance. Never null.
+	 *            location of the remote repository - may be URI or remote
+	 *            configuration name.
+	 * @return the new transport instance. Never null. In case of multiple URIs
+	 *         in remote configuration, only the first is chosen.
 	 * @throws URISyntaxException
 	 *             the location is not a remote defined in the configuration
 	 *             file and is not a well-formed URL.
@@ -93,6 +95,35 @@ public abstract class Transport {
 	}
 
 	/**
+	 * Open new transport instances to connect two repositories.
+	 * 
+	 * @param local
+	 *            existing local repository.
+	 * @param remote
+	 *            location of the remote repository - may be URI or remote
+	 *            configuration name.
+	 * @return the list of new transport instances for every URI in remote
+	 *         configuration.
+	 * @throws URISyntaxException
+	 *             the location is not a remote defined in the configuration
+	 *             file and is not a well-formed URL.
+	 * @throws NotSupportedException
+	 *             the protocol specified is not supported.
+	 */
+	public static List<Transport> openAll(final Repository local,
+			final String remote) throws NotSupportedException,
+			URISyntaxException {
+		final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote);
+		final List<URIish> uris = cfg.getURIs();
+		if (uris.size() == 0) {
+			final ArrayList<Transport> transports = new ArrayList<Transport>(1);
+			transports.add(open(local, new URIish(remote)));
+			return transports;
+		}
+		return openAll(local, cfg);
+	}
+
+	/**
 	 * Open a new transport instance to connect two repositories.
 	 * 
 	 * @param local
@@ -100,7 +131,8 @@ public abstract class Transport {
 	 * @param cfg
 	 *            configuration describing how to connect to the remote
 	 *            repository.
-	 * @return the new transport instance. Never null.
+	 * @return the new transport instance. Never null. In case of multiple URIs
+	 *         in remote configuration, only the first is chosen.
 	 * @throws NotSupportedException
 	 *             the protocol specified is not supported.
 	 * @throws IllegalArgumentException
@@ -114,15 +146,36 @@ public abstract class Transport {
 					"Remote config \""
 					+ cfg.getName() + "\" has no URIs associated");
 		final Transport tn = open(local, cfg.getURIs().get(0));
-		tn.setOptionUploadPack(cfg.getUploadPack());
-		tn.fetch = cfg.getFetchRefSpecs();
-		tn.tagopt = cfg.getTagOpt();
-		tn.setOptionReceivePack(cfg.getReceivePack());
-		tn.push = cfg.getPushRefSpecs();
+		tn.applyConfig(cfg);
 		return tn;
 	}
 
 	/**
+	 * Open new transport instances to connect two repositories.
+	 * 
+	 * @param local
+	 *            existing local repository.
+	 * @param cfg
+	 *            configuration describing how to connect to the remote
+	 *            repository.
+	 * @return the list of new transport instances for every URI in remote
+	 *         configuration.
+	 * @throws NotSupportedException
+	 *             the protocol specified is not supported.
+	 */
+	public static List<Transport> openAll(final Repository local,
+			final RemoteConfig cfg) throws NotSupportedException {
+		final List<URIish> uris = cfg.getURIs();
+		final List<Transport> transports = new ArrayList<Transport>(uris.size());
+		for (final URIish uri : uris) {
+			final Transport tn = open(local, uri);
+			tn.applyConfig(cfg);
+			transports.add(tn);
+		}
+		return transports;
+	}
+
+	/**
 	 * Open a new transport instance to connect two repositories.
 	 * 
 	 * @param local
@@ -357,6 +410,20 @@ public abstract class Transport {
 	public void setPushThin(final boolean pushThin) {
 		this.pushThin = pushThin;
 	}
+	
+	/**
+	 * Apply provided remote configuration on this transport.
+	 * 
+	 * @param cfg
+	 *            configuration to apply on this transport.
+	 */
+	public void applyConfig(final RemoteConfig cfg) {
+		setOptionUploadPack(cfg.getUploadPack());
+		fetch = cfg.getFetchRefSpecs();
+		setTagOpt(cfg.getTagOpt());
+		optionReceivePack = cfg.getReceivePack();
+		push = cfg.getPushRefSpecs();
+	}
 
 	/**
 	 * Fetch objects and refs from the remote repository to the local one.
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 08/31] Add dryRun option to Transport and console push
  2008-08-17 20:43             ` [EGIT PATCH 07/31] Add openAll() and applyConfig() methods to Transport Marek Zawirski
@ 2008-08-17 20:43               ` Marek Zawirski
  2008-08-17 20:43                 ` [EGIT PATCH 09/31] Extract Transport findRemoteRefUpdatesFor() as static method Marek Zawirski
  2008-08-19 16:28                 ` [EGIT PATCH 08/31] Add dryRun option to Transport and console push Shawn O. Pearce
  0 siblings, 2 replies; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Implementation of C Git --dry-run behavior for push operation.
It allows investigating possible push result, while not performing real
push operation - not updating remote refs.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/pgm/Push.java             |    4 ++
 .../org/spearce/jgit/transport/PushProcess.java    |   18 +++++++-
 .../src/org/spearce/jgit/transport/Transport.java  |   40 ++++++++++++++++++--
 3 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
index a952309..f5b24c6 100644
--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
@@ -85,6 +85,9 @@ class Push extends TextBuiltin {
 
 	@Option(name = "--receive-pack", metaVar = "path")
 	private String receivePack;
+	
+	@Option(name = "--dry-run")
+	private boolean dryRun = Transport.DEFAULT_DRY_RUN;
 
 	private boolean shownURI;
 
@@ -102,6 +105,7 @@ class Push extends TextBuiltin {
 			transport.setPushThin(thin);
 			if (receivePack != null)
 				transport.setOptionReceivePack(receivePack);
+			transport.setDryRun(dryRun);
 
 			final Collection<RemoteRefUpdate> toPush = transport
 					.findRemoteRefUpdatesFor(refSpecs);
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/PushProcess.java b/org.spearce.jgit/src/org/spearce/jgit/transport/PushProcess.java
index 6a2176f..cafec05 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/PushProcess.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/PushProcess.java
@@ -100,7 +100,10 @@ class PushProcess {
 	/**
 	 * Perform push operation between local and remote repository - set remote
 	 * refs appropriately, send needed objects and update local tracking refs.
-	 *
+	 * <p>
+	 * When {@link Transport#isDryRun()} is true, result of this operation is
+	 * just estimation of real operation result, no real action is performed.
+	 * 
 	 * @param monitor
 	 *            progress monitor used for feedback about operation.
 	 * @return result of push operation with complete status description.
@@ -118,12 +121,15 @@ class PushProcess {
 			monitor.endTask();
 
 			final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
-			if (!preprocessed.isEmpty())
+			if (transport.isDryRun())
+				modifyUpdatesForDryRun();
+			else if (!preprocessed.isEmpty())
 				connection.push(monitor, preprocessed);
 		} finally {
 			connection.close();
 		}
-		updateTrackingRefs();
+		if (!transport.isDryRun())
+			updateTrackingRefs();
 		return prepareOperationResult();
 	}
 
@@ -191,6 +197,12 @@ class PushProcess {
 		return result;
 	}
 
+	private void modifyUpdatesForDryRun() {
+		for (final RemoteRefUpdate rru : toPush.values())
+			if (rru.getStatus() == Status.NOT_ATTEMPTED)
+				rru.setStatus(Status.OK);
+	}
+
 	private void updateTrackingRefs() {
 		for (final RemoteRefUpdate rru : toPush.values()) {
 			final Status status = rru.getStatus();
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
index 73aa771..98853e6 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
@@ -236,6 +236,11 @@ public abstract class Transport {
 	public static final RefSpec REFSPEC_PUSH_ALL = new RefSpec(
 			"refs/heads/*:refs/heads/*");
 
+	/**
+	 * Default setting for {@link #dryRun} option.
+	 */
+	public static final boolean DEFAULT_DRY_RUN = false;
+
 	/** The repository this transport fetches into, or pushes out of. */
 	protected final Repository local;
 
@@ -271,6 +276,9 @@ public abstract class Transport {
 	/** Should push produce thin-pack when sending objects to remote repository. */
 	private boolean pushThin = DEFAULT_PUSH_THIN;
 
+	/** Should push just check for operation result, not really push. */
+	private boolean dryRun = DEFAULT_DRY_RUN;
+
 	/**
 	 * Create a new transport instance.
 	 * 
@@ -426,6 +434,27 @@ public abstract class Transport {
 	}
 
 	/**
+	 * @return true if push operation should just check for possible result and
+	 *         not really update remote refs, false otherwise - when push should
+	 *         act normally.
+	 */
+	public boolean isDryRun() {
+		return dryRun;
+	}
+
+	/**
+	 * Set dry run option for push operation.
+	 * 
+	 * @param dryRun
+	 *            true if push operation should just check for possible result
+	 *            and not really update remote refs, false otherwise - when push
+	 *            should act normally.
+	 */
+	public void setDryRun(final boolean dryRun) {
+		this.dryRun = dryRun;
+	}
+
+	/**
 	 * Fetch objects and refs from the remote repository to the local one.
 	 * <p>
 	 * This is a utility function providing standard fetch behavior. Local
@@ -495,10 +524,13 @@ public abstract class Transport {
 	 * operation result is provided after execution.
 	 * <p>
 	 * For setting up remote ref update specification from ref spec, see helper
-	 * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs ({@link #REFSPEC_TAGS},
-	 * {@link #REFSPEC_PUSH_ALL}) or consider using directly
-	 * {@link RemoteRefUpdate} for more possibilities.
-	 *
+	 * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs
+	 * ({@link #REFSPEC_TAGS}, {@link #REFSPEC_PUSH_ALL}) or consider using
+	 * directly {@link RemoteRefUpdate} for more possibilities.
+	 * <p>
+	 * When {@link #isDryRun()} is true, result of this operation is just
+	 * estimation of real operation result, no real action is performed.
+	 * 
 	 * @see RemoteRefUpdate
 	 *
 	 * @param monitor
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 09/31] Extract Transport findRemoteRefUpdatesFor() as static method
  2008-08-17 20:43               ` [EGIT PATCH 08/31] Add dryRun option to Transport and console push Marek Zawirski
@ 2008-08-17 20:43                 ` Marek Zawirski
  2008-08-17 20:43                   ` [EGIT PATCH 10/31] Improve javadoc of Transport push() Marek Zawirski
  2008-08-19 16:28                 ` [EGIT PATCH 08/31] Add dryRun option to Transport and console push Shawn O. Pearce
  1 sibling, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

This method could be used outside of specific URI scope, so let it be
static. Otherwise, if someone want to generate remote ref updates from
refspecs he/she may have to create some dummy transport just for that.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/transport/Transport.java  |  147 ++++++++++++--------
 1 files changed, 90 insertions(+), 57 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
index 98853e6..e986e48 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
@@ -211,7 +211,92 @@ public abstract class Transport {
 
 		throw new NotSupportedException("URI not supported: " + remote);
 	}
+	
+	/**
+	 * Convert push remote refs update specification from {@link RefSpec} form
+	 * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
+	 * source part to local refs. expectedOldObjectId in RemoteRefUpdate is
+	 * always set as null. Tracking branch is configured if RefSpec destination
+	 * matches source of any fetch ref spec for this transport remote
+	 * configuration.
+	 * 
+	 * @param db
+	 *            local database.
+	 * @param specs
+	 *            collection of RefSpec to convert.
+	 * @param fetchSpecs
+	 *            fetch specifications used for finding localtracking refs. May
+	 *            be null or empty collection.
+	 * @return collection of set up {@link RemoteRefUpdate}.
+	 * @throws TransportException
+	 *             when problem occurred during conversion or specification set
+	 *             up: most probably, missing objects or refs.
+	 */
+	public static Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
+			final Repository db, final Collection<RefSpec> specs,
+			Collection<RefSpec> fetchSpecs) throws TransportException {
+		if (fetchSpecs == null)
+			fetchSpecs = Collections.emptyList();
+		final List<RemoteRefUpdate> result = new LinkedList<RemoteRefUpdate>();
+		final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
+
+		for (final RefSpec spec : procRefs) {
+			try {
+				final String srcRef = spec.getSource();
+				// null destination (no-colon in ref-spec) is a special case
+				final String remoteName = (spec.getDestination() == null ? spec
+						.getSource() : spec.getDestination());
+				final boolean forceUpdate = spec.isForceUpdate();
+				final String localName = findTrackingRefName(remoteName,
+						fetchSpecs);
+
+				final RemoteRefUpdate rru = new RemoteRefUpdate(db, srcRef,
+						remoteName, forceUpdate, localName, null);
+				result.add(rru);
+			} catch (TransportException x) {
+				throw x;
+			} catch (Exception x) {
+				throw new TransportException(
+						"Problem with resolving push ref spec \"" + spec
+								+ "\" locally: " + x.getMessage(), x);
+			}
+		}
+		return result;
+	}
+
+	private static Collection<RefSpec> expandPushWildcardsFor(
+			final Repository db, final Collection<RefSpec> specs) {
+		final Map<String, Ref> localRefs = db.getAllRefs();
+		final Collection<RefSpec> procRefs = new HashSet<RefSpec>();
 
+		for (final RefSpec spec : specs) {
+			if (spec.isWildcard()) {
+				for (final Ref localRef : localRefs.values()) {
+					if (spec.matchSource(localRef))
+						procRefs.add(spec.expandFromSource(localRef));
+				}
+			} else {
+				procRefs.add(spec);
+			}
+		}
+		return procRefs;
+	}
+
+	private static String findTrackingRefName(final String remoteName,
+			final Collection<RefSpec> fetchSpecs) {
+		// try to find matching tracking refs
+		for (final RefSpec fetchSpec : fetchSpecs) {
+			if (fetchSpec.matchSource(remoteName)) {
+				if (fetchSpec.isWildcard())
+					return fetchSpec.expandFromSource(remoteName)
+							.getDestination();
+				else
+					return fetchSpec.getDestination();
+			}
+		}
+		return null;
+	}
+	
 	/**
 	 * Default setting for {@link #fetchThin} option.
 	 */
@@ -573,7 +658,10 @@ public abstract class Transport {
 	 * always set as null. Tracking branch is configured if RefSpec destination
 	 * matches source of any fetch ref spec for this transport remote
 	 * configuration.
-	 *
+	 * <p>
+	 * Conversion is performed for context of this transport (database, fetch
+	 * specifications).
+	 * 
 	 * @param specs
 	 *            collection of RefSpec to convert.
 	 * @return collection of set up {@link RemoteRefUpdate}.
@@ -583,30 +671,7 @@ public abstract class Transport {
 	 */
 	public Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
 			final Collection<RefSpec> specs) throws TransportException {
-		final List<RemoteRefUpdate> result = new LinkedList<RemoteRefUpdate>();
-		final Collection<RefSpec> procRefs = expandPushWildcardsFor(specs);
-
-		for (final RefSpec spec : procRefs) {
-			try {
-				final String srcRef = spec.getSource();
-				// null destination (no-colon in ref-spec) is a special case
-				final String remoteName = (spec.getDestination() == null ? spec
-						.getSource() : spec.getDestination());
-				final boolean forceUpdate = spec.isForceUpdate();
-				final String localName = findTrackingRefName(remoteName);
-
-				final RemoteRefUpdate rru = new RemoteRefUpdate(local, srcRef,
-						remoteName, forceUpdate, localName, null);
-				result.add(rru);
-			} catch (TransportException x) {
-				throw x;
-			} catch (Exception x) {
-				throw new TransportException(
-						"Problem with resolving push ref spec \"" + spec
-								+ "\" locally: " + x.getMessage(), x);
-			}
-		}
-		return result;
+		return findRemoteRefUpdatesFor(local, specs, fetch);
 	}
 
 	/**
@@ -642,36 +707,4 @@ public abstract class Transport {
 	 * any open file handles used to read the "remote" repository.
 	 */
 	public abstract void close();
-
-	private Collection<RefSpec> expandPushWildcardsFor(
-			final Collection<RefSpec> specs) {
-		final Map<String, Ref> localRefs = local.getAllRefs();
-		final Collection<RefSpec> procRefs = new HashSet<RefSpec>();
-
-		for (final RefSpec spec : specs) {
-			if (spec.isWildcard()) {
-				for (final Ref localRef : localRefs.values()) {
-					if (spec.matchSource(localRef))
-						procRefs.add(spec.expandFromSource(localRef));
-				}
-			} else {
-				procRefs.add(spec);
-			}
-		}
-		return procRefs;
-	}
-
-	private String findTrackingRefName(final String remoteName) {
-		// try to find matching tracking refs
-		for (final RefSpec fetchSpec : fetch) {
-			if (fetchSpec.matchSource(remoteName)) {
-				if (fetchSpec.isWildcard())
-					return fetchSpec.expandFromSource(remoteName)
-							.getDestination();
-				else
-					return fetchSpec.getDestination();
-			}
-		}
-		return null;
-	}
 }
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 10/31] Improve javadoc of Transport push()
  2008-08-17 20:43                 ` [EGIT PATCH 09/31] Extract Transport findRemoteRefUpdatesFor() as static method Marek Zawirski
@ 2008-08-17 20:43                   ` Marek Zawirski
  2008-08-17 20:43                     ` [EGIT PATCH 11/31] Clean up exception issues in RemoteRefUpdate Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Let's state explicitly that remote ref updates are modified during this
call.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/transport/Transport.java  |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
index e986e48..8e42516 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
@@ -627,7 +627,7 @@ public abstract class Transport {
 	 *            collection to use the specifications from the RemoteConfig
 	 *            converted by {@link #findRemoteRefUpdatesFor(Collection)}. No
 	 *            more than 1 RemoteRefUpdate with the same remoteName is
-	 *            allowed.
+	 *            allowed. These objects are modified during this call.
 	 * @return information about results of remote refs updates, tracking refs
 	 *         updates and refs advertised by remote repository.
 	 * @throws NotSupportedException
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 11/31] Clean up exception issues in RemoteRefUpdate
  2008-08-17 20:43                   ` [EGIT PATCH 10/31] Improve javadoc of Transport push() Marek Zawirski
@ 2008-08-17 20:43                     ` Marek Zawirski
  2008-08-17 20:43                       ` [EGIT PATCH 12/31] Add another RemoteRefUpdate constructor, useful for 2-stage push Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

IllegalArgumentException was probably a wrong choice for exception
thrown in RemoteRefUpdate when src object can't be resolved - it should
be checked one.

Now it's IOException, which makes call more safe for external clients.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../spearce/jgit/transport/RemoteRefUpdate.java    |   12 +++---
 .../src/org/spearce/jgit/transport/Transport.java  |   48 +++++++++----------
 2 files changed, 29 insertions(+), 31 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java
index 7db6c55..5afb8a4 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java
@@ -166,23 +166,23 @@ public class RemoteRefUpdate {
 	 *            remote ref with this name.
 	 * @throws IOException
 	 *             when I/O error occurred during creating
-	 *             {@link TrackingRefUpdate} for local tracking branch.
+	 *             {@link TrackingRefUpdate} for local tracking branch or srcRef
+	 *             can't be resolved to any object.
 	 * @throws IllegalArgumentException
-	 *             if some required parameter was null or srcRef can't be
-	 *             resolved to any object.
+	 *             if some required parameter was null
 	 */
 	public RemoteRefUpdate(final Repository db, final String srcRef,
 			final String remoteName, final boolean forceUpdate,
 			final String localName, final ObjectId expectedOldObjectId)
 			throws IOException {
 		if (remoteName == null)
-			throw new IllegalArgumentException("remote name can't be null");
+			throw new IllegalArgumentException("Remote name can't be null.");
 		this.srcRef = srcRef;
 		this.newObjectId = (srcRef == null ? ObjectId.zeroId() : db
 				.resolve(srcRef));
 		if (newObjectId == null)
-			throw new IllegalArgumentException(
-					"source ref doesn't resolve to any object");
+			throw new IOException("Source ref " + srcRef
+					+ " doesn't resolve to any object.");
 		this.remoteName = remoteName;
 		this.forceUpdate = forceUpdate;
 		if (localName != null && db != null)
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
index 8e42516..939347e 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
@@ -39,6 +39,7 @@
 
 package org.spearce.jgit.transport;
 
+import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -211,7 +212,7 @@ public abstract class Transport {
 
 		throw new NotSupportedException("URI not supported: " + remote);
 	}
-	
+
 	/**
 	 * Convert push remote refs update specification from {@link RefSpec} form
 	 * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
@@ -228,38 +229,29 @@ public abstract class Transport {
 	 *            fetch specifications used for finding localtracking refs. May
 	 *            be null or empty collection.
 	 * @return collection of set up {@link RemoteRefUpdate}.
-	 * @throws TransportException
+	 * @throws IOException
 	 *             when problem occurred during conversion or specification set
 	 *             up: most probably, missing objects or refs.
 	 */
 	public static Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
 			final Repository db, final Collection<RefSpec> specs,
-			Collection<RefSpec> fetchSpecs) throws TransportException {
+			Collection<RefSpec> fetchSpecs) throws IOException {
 		if (fetchSpecs == null)
 			fetchSpecs = Collections.emptyList();
 		final List<RemoteRefUpdate> result = new LinkedList<RemoteRefUpdate>();
 		final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
 
 		for (final RefSpec spec : procRefs) {
-			try {
-				final String srcRef = spec.getSource();
-				// null destination (no-colon in ref-spec) is a special case
-				final String remoteName = (spec.getDestination() == null ? spec
-						.getSource() : spec.getDestination());
-				final boolean forceUpdate = spec.isForceUpdate();
-				final String localName = findTrackingRefName(remoteName,
-						fetchSpecs);
-
-				final RemoteRefUpdate rru = new RemoteRefUpdate(db, srcRef,
-						remoteName, forceUpdate, localName, null);
-				result.add(rru);
-			} catch (TransportException x) {
-				throw x;
-			} catch (Exception x) {
-				throw new TransportException(
-						"Problem with resolving push ref spec \"" + spec
-								+ "\" locally: " + x.getMessage(), x);
-			}
+			final String srcRef = spec.getSource();
+			// null destination (no-colon in ref-spec) is a special case
+			final String remoteName = (spec.getDestination() == null ? spec
+					.getSource() : spec.getDestination());
+			final boolean forceUpdate = spec.isForceUpdate();
+			final String localName = findTrackingRefName(remoteName, fetchSpecs);
+
+			final RemoteRefUpdate rru = new RemoteRefUpdate(db, srcRef,
+					remoteName, forceUpdate, localName, null);
+			result.add(rru);
 		}
 		return result;
 	}
@@ -643,7 +635,13 @@ public abstract class Transport {
 			TransportException {
 		if (toPush == null || toPush.isEmpty()) {
 			// If the caller did not ask for anything use the defaults.
-			toPush = findRemoteRefUpdatesFor(push);
+			try {
+				toPush = findRemoteRefUpdatesFor(push);
+			} catch (final IOException e) {
+				throw new TransportException(
+						"Problem with resolving push ref specs locally: "
+								+ e.getMessage(), e);
+			}
 			if (toPush.isEmpty())
 				throw new TransportException("Nothing to push.");
 		}
@@ -665,12 +663,12 @@ public abstract class Transport {
 	 * @param specs
 	 *            collection of RefSpec to convert.
 	 * @return collection of set up {@link RemoteRefUpdate}.
-	 * @throws TransportException
+	 * @throws IOException
 	 *             when problem occurred during conversion or specification set
 	 *             up: most probably, missing objects or refs.
 	 */
 	public Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
-			final Collection<RefSpec> specs) throws TransportException {
+			final Collection<RefSpec> specs) throws IOException {
 		return findRemoteRefUpdatesFor(local, specs, fetch);
 	}
 
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 12/31] Add another RemoteRefUpdate constructor, useful for 2-stage push
  2008-08-17 20:43                     ` [EGIT PATCH 11/31] Clean up exception issues in RemoteRefUpdate Marek Zawirski
@ 2008-08-17 20:43                       ` Marek Zawirski
  2008-08-17 20:43                         ` [EGIT PATCH 13/31] Add getAllRemoteConfigs() to RemoteConfig Marek Zawirski
  2008-08-19 16:45                         ` [EGIT PATCH 12/31] Add another RemoteRefUpdate constructor, useful for 2-stage push Shawn O. Pearce
  0 siblings, 2 replies; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

New constructor base on existing RemoteRefUpdate instance, providing
deep copy of object, but allowing change of expectedOldObjectId.

It may be useful for copying ref updates during one-to-many push or
2-stage push, with first 1st step being dry run, 2nd being actual push.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../spearce/jgit/transport/RemoteRefUpdate.java    |   28 +++++++++++++++++++-
 1 files changed, 27 insertions(+), 1 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java
index 5afb8a4..42588c1 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java
@@ -123,7 +123,7 @@ public class RemoteRefUpdate {
 
 	private final TrackingRefUpdate trackingRefUpdate;
 
-	private String srcRef;
+	private final String srcRef;
 
 	private final boolean forceUpdate;
 
@@ -133,6 +133,8 @@ public class RemoteRefUpdate {
 
 	private String message;
 
+	private final Repository db;
+
 	/**
 	 * Construct remote ref update request by providing an update specification.
 	 * Object is created with default {@link Status#NOT_ATTEMPTED} status and no
@@ -190,11 +192,35 @@ public class RemoteRefUpdate {
 					remoteName, forceUpdate, newObjectId, "push");
 		else
 			trackingRefUpdate = null;
+		this.db = db;
 		this.expectedOldObjectId = expectedOldObjectId;
 		this.status = Status.NOT_ATTEMPTED;
 	}
 
 	/**
+	 * Create a new instance of this object basing on existing instance for
+	 * configuration. State (like {@link #getMessage()}, {@link #getStatus()})
+	 * of base object is not shared. Expected old object id is set up from
+	 * scratch, as this constructor may be used for 2-stage push: first one
+	 * being dry run, second one being actual push.
+	 * 
+	 * @param base
+	 *            configuration base.
+	 * @param newExpectedOldObjectId
+	 *            new expected object id value.
+	 * @throws IOException
+	 *             when I/O error occurred during creating
+	 *             {@link TrackingRefUpdate} for local tracking branch or srcRef
+	 *             of base object no longer can be resolved to any object.
+	 */
+	public RemoteRefUpdate(final RemoteRefUpdate base,
+			final ObjectId newExpectedOldObjectId) throws IOException {
+		this(base.db, base.srcRef, base.remoteName, base.forceUpdate,
+				(base.trackingRefUpdate == null ? null : base.trackingRefUpdate
+						.getLocalName()), newExpectedOldObjectId);
+	}
+
+	/**
 	 * @return expectedOldObjectId required to be advertised by remote side, as
 	 *         set in constructor; may be null.
 	 */
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 13/31] Add getAllRemoteConfigs() to RemoteConfig
  2008-08-17 20:43                       ` [EGIT PATCH 12/31] Add another RemoteRefUpdate constructor, useful for 2-stage push Marek Zawirski
@ 2008-08-17 20:43                         ` Marek Zawirski
  2008-08-17 20:43                           ` [EGIT PATCH 14/31] Add setFetchRefSpecs and setPushRefSpecs " Marek Zawirski
  2008-08-19 16:45                         ` [EGIT PATCH 12/31] Add another RemoteRefUpdate constructor, useful for 2-stage push Shawn O. Pearce
  1 sibling, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Introduce the method allowing us to parse all configured remotes.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/lib/RepositoryConfig.java |   21 ++++++++++++++++
 .../org/spearce/jgit/transport/RemoteConfig.java   |   26 ++++++++++++++++++++
 2 files changed, 47 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
index 048940d..397c294 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
@@ -53,9 +53,11 @@ import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.spearce.jgit.util.FS;
 
@@ -277,6 +279,25 @@ public class RepositoryConfig {
 			return baseConfig.getStringList(section, subsection, name);
 		return new String[0];
 	}
+	
+	/**
+	 * @param section
+	 *            section to search for.
+	 * @return set of all subsections of specified section within this
+	 *         configuration and its base configuration; may be empty if no
+	 *         subsection exists.
+	 */
+	public Set<String> getSubsections(final String section) {
+		final Set<String> result = new HashSet<String>();
+
+		for (final Entry e : entries) {
+			if (section.equals(e.base) && e.extendedBase != null)
+				result.add(e.extendedBase);
+		}
+		if (baseConfig != null)
+			result.addAll(baseConfig.getSubsections(section));
+		return result;
+	}
 
 	private String getRawString(final String section, final String subsection,
 			final String name) {
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java
index bb21511..cde5d43 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java
@@ -73,6 +73,32 @@ public class RemoteConfig {
 
 	/** Default value for {@link #getReceivePack()} if not specified. */
 	public static final String DEFAULT_RECEIVE_PACK = "git-receive-pack";
+	
+	/**
+	 * Parse all remote blocks in an existing configuration file, looking for
+	 * remotes configuration.
+	 * 
+	 * @param rc
+	 *            the existing configuration to get the remote settings from.
+	 *            The configuration must already be loaded into memory.
+	 * @return all remotes configurations existing in provided repository
+	 *         configuration. Returned configurations are ordered
+	 *         lexicographically by names.
+	 * @throws URISyntaxException
+	 *             one of the URIs within the remote's configuration is invalid.
+	 */
+	public static List<RemoteConfig> getAllRemoteConfigs(
+			final RepositoryConfig rc) throws URISyntaxException {
+		final List<String> names = new ArrayList<String>(rc
+				.getSubsections(SECTION));
+		Collections.sort(names);
+
+		final List<RemoteConfig> result = new ArrayList<RemoteConfig>(names
+				.size());
+		for (final String name : names)
+			result.add(new RemoteConfig(rc, name));
+		return result;
+	}
 
 	private String name;
 
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 14/31] Add setFetchRefSpecs and setPushRefSpecs to RemoteConfig
  2008-08-17 20:43                         ` [EGIT PATCH 13/31] Add getAllRemoteConfigs() to RemoteConfig Marek Zawirski
@ 2008-08-17 20:43                           ` Marek Zawirski
  2008-08-17 20:43                             ` [EGIT PATCH 15/31] Add simple abbreviate() method to ObjectId Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

These methods allows us overriding whole collections of specifications,
saving unnecessary external code.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../org/spearce/jgit/transport/RemoteConfig.java   |   24 ++++++++++++++++++++
 1 files changed, 24 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java
index cde5d43..5b5e4d1 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java
@@ -267,6 +267,30 @@ public class RemoteConfig {
 	}
 
 	/**
+	 * Override existing fetch specifications with new ones.
+	 * 
+	 * @param specs
+	 *            list of fetch specifications to set. List is copied, it can be
+	 *            modified after this call.
+	 */
+	public void setFetchRefSpecs(final List<RefSpec> specs) {
+		fetch.clear();
+		fetch.addAll(specs);
+	}
+
+	/**
+	 * Override existing push specifications with new ones.
+	 * 
+	 * @param specs
+	 *            list of push specifications to set. List is copied, it can be
+	 *            modified after this call.
+	 */
+	public void setPushRefSpecs(final List<RefSpec> specs) {
+		push.clear();
+		push.addAll(specs);
+	}
+
+	/**
 	 * Remove a fetch RefSpec from this remote.
 	 * 
 	 * @param s
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 15/31] Add simple abbreviate() method to ObjectId
  2008-08-17 20:43                           ` [EGIT PATCH 14/31] Add setFetchRefSpecs and setPushRefSpecs " Marek Zawirski
@ 2008-08-17 20:43                             ` Marek Zawirski
  2008-08-17 20:43                               ` [EGIT PATCH 16/31] Remove debug/test console output from GitIndex Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

It's common usage to search for short, but unique abbreviation of
object's SHA-1, so let's have a method for that.

This is a really workaround implementation, but it has been already used
in other places. Let's have other usages pointing here, so we can easily
change only this implementation later.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/pgm/Fetch.java            |   10 +++++-----
 .../src/org/spearce/jgit/pgm/Push.java             |    7 +++----
 .../src/org/spearce/jgit/pgm/TextBuiltin.java      |    4 ----
 .../src/org/spearce/jgit/lib/AnyObjectId.java      |   15 +++++++++++++++
 4 files changed, 23 insertions(+), 13 deletions(-)

diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java
index 4eff32b..99ed101 100644
--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java
@@ -93,7 +93,7 @@ class Fetch extends TextBuiltin {
 		}
 	}
 
-	private static String longTypeOf(final TrackingRefUpdate u) {
+	private String longTypeOf(final TrackingRefUpdate u) {
 		final RefUpdate.Result r = u.getResult();
 		if (r == RefUpdate.Result.LOCK_FAILURE)
 			return "[lock fail]";
@@ -110,14 +110,14 @@ class Fetch extends TextBuiltin {
 		}
 
 		if (r == RefUpdate.Result.FORCED) {
-			final String aOld = abbreviateObject(u.getOldObjectId());
-			final String aNew = abbreviateObject(u.getNewObjectId());
+			final String aOld = u.getOldObjectId().abbreviate(db);
+			final String aNew = u.getNewObjectId().abbreviate(db);
 			return aOld + "..." + aNew;
 		}
 
 		if (r == RefUpdate.Result.FAST_FORWARD) {
-			final String aOld = abbreviateObject(u.getOldObjectId());
-			final String aNew = abbreviateObject(u.getNewObjectId());
+			final String aOld = u.getOldObjectId().abbreviate(db);
+			final String aNew = u.getNewObjectId().abbreviate(db);
 			return aOld + ".." + aNew;
 		}
 
diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
index f5b24c6..b61071c 100644
--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
@@ -173,10 +173,9 @@ class Push extends TextBuiltin {
 				} else {
 					boolean fastForward = rru.isFastForward();
 					final char flag = fastForward ? ' ' : '+';
-					final String summary = abbreviateObject(oldRef
-							.getObjectId())
+					final String summary = oldRef.getObjectId().abbreviate(db)
 							+ (fastForward ? ".." : "...")
-							+ abbreviateObject(rru.getNewObjectId());
+							+ rru.getNewObjectId().abbreviate(db);
 					final String message = fastForward ? null : "forced update";
 					printUpdateLine(flag, summary, srcRef, remoteName, message);
 				}
@@ -199,7 +198,7 @@ class Push extends TextBuiltin {
 
 		case REJECTED_REMOTE_CHANGED:
 			final String message = "remote ref object changed - is not expected one "
-					+ abbreviateObject(rru.getExpectedOldObjectId());
+					+ rru.getExpectedOldObjectId().abbreviate(db);
 			printUpdateLine('!', "[rejected]", srcRef, remoteName, message);
 			break;
 
diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/TextBuiltin.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/TextBuiltin.java
index 5c066cb..23ab92b 100644
--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/TextBuiltin.java
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/TextBuiltin.java
@@ -179,10 +179,6 @@ public abstract class TextBuiltin {
 		return new Die(why);
 	}
 
-	protected static String abbreviateObject(final ObjectId id) {
-		return id.toString().substring(0, 7);
-	}
-
 	protected String abbreviateRef(String dst, boolean abbreviateRemote) {
 		if (dst.startsWith(REFS_HEADS))
 			dst = dst.substring(REFS_HEADS.length());
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/AnyObjectId.java b/org.spearce.jgit/src/org/spearce/jgit/lib/AnyObjectId.java
index 7357e57..2c5518a 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/AnyObjectId.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/AnyObjectId.java
@@ -411,6 +411,21 @@ public abstract class AnyObjectId implements Comparable {
 	}
 
 	/**
+	 * Return unique abbreviation (prefix) of this object SHA-1.
+	 * <p>
+	 * Current implementation is not guaranteeing uniqueness, it just returns
+	 * fixed-length prefix of SHA-1 string.
+	 * 
+	 * @param repo
+	 *            repository for checking uniqueness within.
+	 * @return SHA-1 abbreviation.
+	 */
+	public String abbreviate(final Repository repo) {
+		// TODO implement checking for uniqueness
+		return toString().substring(0, 7);
+	}
+
+	/**
 	 * Obtain an immutable copy of this current object name value.
 	 * <p>
 	 * Only returns <code>this</code> if this instance is an unsubclassed
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 16/31] Remove debug/test console output from GitIndex
  2008-08-17 20:43                             ` [EGIT PATCH 15/31] Add simple abbreviate() method to ObjectId Marek Zawirski
@ 2008-08-17 20:43                               ` Marek Zawirski
  2008-08-17 20:43                                 ` [EGIT PATCH 17/31] Fix typo in uitext.properties message Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Typical Eclipse users (read: me) usually doesn't like unnecessary
output on Eclipse stdout.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/lib/GitIndex.java         |    3 ---
 1 files changed, 0 insertions(+), 3 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
index c7a4402..2255c1a 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
@@ -190,7 +190,6 @@ public class GitIndex {
 	 * @throws IOException
 	 */
 	public void read() throws IOException {
-		long t0 = System.currentTimeMillis();
 		changed = false;
 		statDirty = false;
 		if (!cacheFile.exists()) {
@@ -214,9 +213,7 @@ public class GitIndex {
 				Entry entry = new Entry(buffer);
 				entries.put(entry.name, entry);
 			}
-			long t1 = System.currentTimeMillis();
 			lastCacheTime = cacheFile.lastModified();
-			System.out.println("Read index "+cacheFile+" in "+((t1-t0)/1000.0)+"s");
 		} finally {
 			cache.close();
 		}
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 17/31] Fix typo in uitext.properties message
  2008-08-17 20:43                               ` [EGIT PATCH 16/31] Remove debug/test console output from GitIndex Marek Zawirski
@ 2008-08-17 20:43                                 ` Marek Zawirski
  2008-08-17 20:43                                   ` [EGIT PATCH 18/31] Refactor/rewrite CloneSourcePage to universal RepositorySelectionPage Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/egit/ui/uitext.properties      |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index 9516aa0..55b1348 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -16,7 +16,7 @@
 ##
 
 SharingWizard_windowTitle=Configure Git Repository
-SharingWizard_failed=Failed to initalize Git team provider.
+SharingWizard_failed=Failed to initialize Git team provider.
 
 GenericOperationFailed={0} Failed
 
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 18/31] Refactor/rewrite CloneSourcePage to universal RepositorySelectionPage
  2008-08-17 20:43                                 ` [EGIT PATCH 17/31] Fix typo in uitext.properties message Marek Zawirski
@ 2008-08-17 20:43                                   ` Marek Zawirski
  2008-08-17 20:44                                     ` [EGIT PATCH 19/31] Clone wizard and related: refactor, clean-up, fixes or improvements Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:43 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

CloneSourcePage is refactored to serve as generic wizard page for
repository selection. Selection can be performed now as custom URI or
(optionally - configurable) from preconfigured remote repositories. That
allows us to this wizard page for selection of both source and destination
repository in wizards.

As there was need for code rewrite in few places, some things get fixed or
improved by the way:
- Controls are disabled/enabled recursively, so user get better feedback
  what he/she can type.
- URI panel behaved strange when bad URI was typed, now it's probably more
  obvious what's going on.
- Distinction is introduced between internal URI/RemoteConfig selection
  (possibly invalid) and exposed one - for other pages. Hence, clients of
  this class don't have to handle internal validation issues.
- isPageComplete() logic is moved to checkPage(), as it seems to be
  strange (and hard to follow?) pattern to mix setPageComplete() and
  custom isPageComplete().
- possibly minor forgotten issues.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/egit/ui/UIText.java            |   47 +-
 .../ui/internal/clone/CloneDestinationPage.java    |   24 +-
 .../egit/ui/internal/clone/CloneSourcePage.java    |  460 -------------
 .../egit/ui/internal/clone/GitCloneWizard.java     |   13 +-
 .../egit/ui/internal/clone/SourceBranchPage.java   |   37 +-
 .../ui/internal/clone/URIishChangeListener.java    |   20 -
 .../components/RepositorySelectionListener.java    |   32 +
 .../components/RepositorySelectionPage.java        |  680 ++++++++++++++++++++
 .../src/org/spearce/egit/ui/uitext.properties      |   37 +-
 9 files changed, 798 insertions(+), 552 deletions(-)
 delete mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneSourcePage.java
 delete mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/URIishChangeListener.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionListener.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionPage.java

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 4adb99c..0d39440 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -53,52 +53,67 @@ public class UIText extends NLS {
 	public static String GitCloneWizard_failed;
 
 	/** */
-	public static String CloneSourcePage_title;
+	public static String RepositorySelectionPage_sourceSelectionTitle;
 
 	/** */
-	public static String CloneSourcePage_description;
+	public static String RepositorySelectionPage_sourceSelectionDescription;
 
 	/** */
-	public static String CloneSourcePage_groupLocation;
+	public static String RepositorySelectionPage_destinationSelectionTitle;
 
 	/** */
-	public static String CloneSourcePage_groupAuthentication;
+	public static String RepositorySelectionPage_destinationSelectionDescription;
 
 	/** */
-	public static String CloneSourcePage_groupConnection;
+	public static String RepositorySelectionPage_groupLocation;
 
 	/** */
-	public static String CloneSourcePage_promptURI;
+	public static String RepositorySelectionPage_groupAuthentication;
 
 	/** */
-	public static String CloneSourcePage_promptHost;
+	public static String RepositorySelectionPage_groupConnection;
 
 	/** */
-	public static String CloneSourcePage_promptPath;
+	public static String RepositorySelectionPage_promptURI;
 
 	/** */
-	public static String CloneSourcePage_promptUser;
+	public static String RepositorySelectionPage_promptHost;
 
 	/** */
-	public static String CloneSourcePage_promptPassword;
+	public static String RepositorySelectionPage_promptPath;
 
 	/** */
-	public static String CloneSourcePage_promptScheme;
+	public static String RepositorySelectionPage_promptUser;
 
 	/** */
-	public static String CloneSourcePage_promptPort;
+	public static String RepositorySelectionPage_promptPassword;
 
 	/** */
-	public static String CloneSourcePage_fieldRequired;
+	public static String RepositorySelectionPage_promptScheme;
 
 	/** */
-	public static String CloneSourcePage_fieldNotSupported;
+	public static String RepositorySelectionPage_promptPort;
 
 	/** */
-	public static String CloneSourcePage_fileNotFound;
+	public static String RepositorySelectionPage_fieldRequired;
 
 	/** */
-	public static String CloneSourcePage_internalError;
+	public static String RepositorySelectionPage_fieldNotSupported;
+
+	/** */
+	public static String RepositorySelectionPage_fileNotFound;
+
+	/** */
+	public static String RepositorySelectionPage_internalError;
+
+	/** */
+	public static String RepositorySelectionPage_configuredRemoteChoice;
+
+	/** */
+	public static String RepositorySelectionPage_uriChoice;
+
+	/** */
+	public static String SourceBranchPage_title;
 
 	/** */
 	public static String SourceBranchPage_branchList;
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
index 5d5a8ee..249bfd3 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
@@ -9,7 +9,6 @@
 package org.spearce.egit.ui.internal.clone;
 
 import java.io.File;
-import java.net.URISyntaxException;
 
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.jface.wizard.WizardPage;
@@ -28,10 +27,12 @@ import org.eclipse.swt.widgets.FileDialog;
 import org.eclipse.swt.widgets.Group;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Text;
-import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIText;
+import org.spearce.egit.ui.internal.components.RepositorySelectionListener;
+import org.spearce.egit.ui.internal.components.RepositorySelectionPage;
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.transport.RemoteConfig;
 import org.spearce.jgit.transport.URIish;
 
 /**
@@ -39,7 +40,7 @@ import org.spearce.jgit.transport.URIish;
  * cloned.
  */
 class CloneDestinationPage extends WizardPage {
-	private final CloneSourcePage sourcePage;
+	private final RepositorySelectionPage sourcePage;
 
 	private final SourceBranchPage branchPage;
 
@@ -51,16 +52,17 @@ class CloneDestinationPage extends WizardPage {
 
 	private Text remoteText;
 
-	CloneDestinationPage(final CloneSourcePage sp, final SourceBranchPage bp) {
+	CloneDestinationPage(final RepositorySelectionPage sp,
+			final SourceBranchPage bp) {
 		super(CloneDestinationPage.class.getName());
 		sourcePage = sp;
 		branchPage = bp;
 
 		setTitle(UIText.CloneDestinationPage_title);
-		setImageDescriptor(UIIcons.WIZBAN_IMPORT_REPO);
 
-		sourcePage.addURIishChangeListener(new URIishChangeListener() {
-			public void uriishChanged(final URIish newURI) {
+		sourcePage.addRepositorySelectionListener(new RepositorySelectionListener() {
+			public void selectionChanged(final URIish newURI,
+					final RemoteConfig newConfig) {
 				if (newURI == null || !newURI.equals(validated))
 					setPageComplete(false);
 			}
@@ -221,12 +223,8 @@ class CloneDestinationPage extends WizardPage {
 
 	private void revalidate() {
 		URIish newURI = null;
-		try {
-			newURI = sourcePage.getURI();
-			validated = newURI;
-		} catch (URISyntaxException e) {
-			validated = null;
-		}
+		newURI = sourcePage.getURI();
+		validated = newURI;
 
 		if (newURI == null || !newURI.equals(validated)) {
 			final String n = getSuggestedName();
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneSourcePage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneSourcePage.java
deleted file mode 100644
index 4bdd6af..0000000
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneSourcePage.java
+++ /dev/null
@@ -1,460 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- *
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * See LICENSE for the full license text, also available.
- *******************************************************************************/
-package org.spearce.egit.ui.internal.clone;
-
-import java.io.File;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-
-import org.eclipse.jface.wizard.WizardPage;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.events.VerifyEvent;
-import org.eclipse.swt.events.VerifyListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Combo;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Group;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-import org.spearce.egit.ui.Activator;
-import org.spearce.egit.ui.UIIcons;
-import org.spearce.egit.ui.UIText;
-import org.spearce.jgit.transport.URIish;
-import org.spearce.jgit.util.FS;
-
-/**
- * Wizard page that allows the user entering the location of a repository to be
- * cloned.
- */
-class CloneSourcePage extends WizardPage {
-	private static final int S_GIT = 0;
-
-	private static final int S_SSH = 1;
-
-	private static final int S_SFTP = 2;
-
-	private static final int S_HTTP = 3;
-
-	private static final int S_HTTPS = 4;
-
-	private static final int S_FTP = 5;
-
-	private static final int S_FILE = 6;
-
-	private static final String[] DEFAULT_SCHEMES;
-	static {
-		DEFAULT_SCHEMES = new String[7];
-		DEFAULT_SCHEMES[S_GIT] = "git";
-		DEFAULT_SCHEMES[S_SSH] = "git+ssh";
-		DEFAULT_SCHEMES[S_SFTP] = "sftp";
-		DEFAULT_SCHEMES[S_HTTP] = "http";
-		DEFAULT_SCHEMES[S_HTTPS] = "https";
-		DEFAULT_SCHEMES[S_FTP] = "ftp";
-		DEFAULT_SCHEMES[S_FILE] = "file";
-	}
-
-	private final List<URIishChangeListener> uriishChangeListeners;
-
-	private Group authGroup;
-
-	private Text uriText;
-
-	private Text hostText;
-
-	private Text pathText;
-
-	private Text userText;
-
-	private Text passText;
-
-	private Combo scheme;
-
-	private Text portText;
-
-	private int eventDepth;
-
-	private URIish uri = new URIish();
-
-	CloneSourcePage() {
-		super(CloneSourcePage.class.getName());
-		setTitle(UIText.CloneSourcePage_title);
-		setDescription(UIText.CloneSourcePage_description);
-		setImageDescriptor(UIIcons.WIZBAN_IMPORT_REPO);
-		uriishChangeListeners = new ArrayList<URIishChangeListener>(4);
-	}
-
-	void addURIishChangeListener(final URIishChangeListener l) {
-		uriishChangeListeners.add(l);
-	}
-
-	public void createControl(final Composite parent) {
-		final Composite panel = new Composite(parent, SWT.NULL);
-		final GridLayout layout = new GridLayout();
-		layout.numColumns = 1;
-		panel.setLayout(layout);
-
-		createLocationGroup(panel);
-		createConnectionGroup(panel);
-		authGroup = createAuthenticationGroup(panel);
-
-		updateAuthGroup();
-		setControl(panel);
-		setPageComplete(false);
-	}
-
-	private void createLocationGroup(final Composite parent) {
-		final Group g = createGroup(parent,
-				UIText.CloneSourcePage_groupLocation);
-
-		newLabel(g, UIText.CloneSourcePage_promptURI + ":");
-		uriText = new Text(g, SWT.BORDER);
-		uriText.setLayoutData(createFieldGridData());
-		uriText.addModifyListener(new ModifyListener() {
-			public void modifyText(final ModifyEvent e) {
-				try {
-					eventDepth++;
-					if (eventDepth != 1)
-						return;
-
-					final URIish u = new URIish(uriText.getText());
-					safeSet(hostText, u.getHost());
-					safeSet(pathText, u.getPath());
-					safeSet(userText, u.getUser());
-					safeSet(passText, u.getPass());
-
-					if (u.getPort() > 0)
-						portText.setText(Integer.toString(u.getPort()));
-					else
-						portText.setText("");
-
-					if (isFile(u))
-						scheme.select(S_FILE);
-					else if (isSSH(u))
-						scheme.select(S_SSH);
-					else {
-						for (int i = 0; i < DEFAULT_SCHEMES.length; i++) {
-							if (DEFAULT_SCHEMES[i].equals(u.getScheme())) {
-								scheme.select(i);
-								break;
-							}
-						}
-					}
-
-					updateAuthGroup();
-					uri = u;
-					for (final URIishChangeListener l : uriishChangeListeners)
-						l.uriishChanged(u);
-					setPageComplete(isPageComplete());
-				} catch (URISyntaxException err) {
-					uriInvalid();
-					setErrorMessage(err.getMessage());
-					setPageComplete(false);
-				} finally {
-					eventDepth--;
-				}
-			}
-		});
-
-		newLabel(g, UIText.CloneSourcePage_promptHost + ":");
-		hostText = new Text(g, SWT.BORDER);
-		hostText.setLayoutData(createFieldGridData());
-		hostText.addModifyListener(new ModifyListener() {
-			public void modifyText(final ModifyEvent e) {
-				setURI(uri.setHost(nullString(hostText.getText())));
-			}
-		});
-
-		newLabel(g, UIText.CloneSourcePage_promptPath + ":");
-		pathText = new Text(g, SWT.BORDER);
-		pathText.setLayoutData(createFieldGridData());
-		pathText.addModifyListener(new ModifyListener() {
-			public void modifyText(final ModifyEvent e) {
-				setURI(uri.setPath(nullString(pathText.getText())));
-			}
-		});
-	}
-
-	private Group createAuthenticationGroup(final Composite parent) {
-		final Group g = createGroup(parent,
-				UIText.CloneSourcePage_groupAuthentication);
-
-		newLabel(g, UIText.CloneSourcePage_promptUser + ":");
-		userText = new Text(g, SWT.BORDER);
-		userText.setLayoutData(createFieldGridData());
-		userText.addModifyListener(new ModifyListener() {
-			public void modifyText(final ModifyEvent e) {
-				setURI(uri.setUser(nullString(userText.getText())));
-			}
-		});
-
-		newLabel(g, UIText.CloneSourcePage_promptPassword + ":");
-		passText = new Text(g, SWT.BORDER | SWT.PASSWORD);
-		passText.setLayoutData(createFieldGridData());
-		return g;
-	}
-
-	private void createConnectionGroup(final Composite parent) {
-		final Group g = createGroup(parent,
-				UIText.CloneSourcePage_groupConnection);
-
-		newLabel(g, UIText.CloneSourcePage_promptScheme + ":");
-		scheme = new Combo(g, SWT.DROP_DOWN | SWT.READ_ONLY);
-		scheme.setItems(DEFAULT_SCHEMES);
-		scheme.addSelectionListener(new SelectionListener() {
-			public void widgetDefaultSelected(final SelectionEvent e) {
-				// Nothing
-			}
-
-			public void widgetSelected(final SelectionEvent e) {
-				final int idx = scheme.getSelectionIndex();
-				if (idx < 0)
-					setURI(uri.setScheme(null));
-				else
-					setURI(uri.setScheme(nullString(scheme.getItem(idx))));
-				updateAuthGroup();
-			}
-		});
-
-		newLabel(g, UIText.CloneSourcePage_promptPort + ":");
-		portText = new Text(g, SWT.BORDER);
-		portText.addVerifyListener(new VerifyListener() {
-			final Pattern p = Pattern.compile("^(?:[1-9][0-9]*)?$");
-
-			public void verifyText(final VerifyEvent e) {
-				final String v = portText.getText();
-				e.doit = p.matcher(
-						v.substring(0, e.start) + e.text + v.substring(e.end))
-						.matches();
-			}
-		});
-		portText.addModifyListener(new ModifyListener() {
-			public void modifyText(final ModifyEvent e) {
-				final String val = nullString(portText.getText());
-				if (val == null)
-					setURI(uri.setPort(-1));
-				else {
-					try {
-						setURI(uri.setPort(Integer.parseInt(val)));
-					} catch (NumberFormatException err) {
-						// Ignore it for now.
-						uriInvalid();
-					}
-				}
-			}
-		});
-	}
-
-	private static Group createGroup(final Composite parent, final String text) {
-		final Group g = new Group(parent, SWT.NONE);
-		final GridLayout layout = new GridLayout();
-		layout.numColumns = 2;
-		g.setLayout(layout);
-		g.setText(text);
-		final GridData gd = new GridData();
-		gd.grabExcessHorizontalSpace = true;
-		gd.horizontalAlignment = SWT.FILL;
-		g.setLayoutData(gd);
-		return g;
-	}
-
-	private static void newLabel(final Group g, final String text) {
-		new Label(g, SWT.NULL).setText(text);
-	}
-
-	private static GridData createFieldGridData() {
-		return new GridData(SWT.FILL, SWT.DEFAULT, true, false);
-	}
-
-	/**
-	 * Returns the URI entered in the Wizard page.
-	 * 
-	 * @return the URI entered in the Wizard page.
-	 * @throws URISyntaxException
-	 */
-	public URIish getURI() throws URISyntaxException {
-		return new URIish(uriText.getText());
-	}
-
-	@Override
-	public boolean isPageComplete() {
-		if (uriText.getText().length() == 0) {
-			setErrorMessage(null);
-			return false;
-		}
-
-		try {
-			final URIish finalURI = getURI();
-			String proto = finalURI.getScheme();
-			if (proto == null && scheme.getSelectionIndex() >= 0)
-				proto = scheme.getItem(scheme.getSelectionIndex());
-
-			if (uri.getPath() == null) {
-				uriInvalid();
-				setErrorMessage(NLS.bind(UIText.CloneSourcePage_fieldRequired,
-						UIText.CloneSourcePage_promptPath, proto));
-				return false;
-			}
-
-			if (isFile(finalURI)) {
-				String badField = null;
-				if (uri.getHost() != null)
-					badField = UIText.CloneSourcePage_promptHost;
-				else if (uri.getUser() != null)
-					badField = UIText.CloneSourcePage_promptUser;
-				else if (uri.getPass() != null)
-					badField = UIText.CloneSourcePage_promptPassword;
-				if (badField != null) {
-					uriInvalid();
-					setErrorMessage(NLS.bind(
-							UIText.CloneSourcePage_fieldNotSupported, badField,
-							proto));
-					return false;
-				}
-
-				final File d = FS.resolve(new File("."), uri.getPath());
-				if (!d.exists()) {
-					setErrorMessage(NLS.bind(
-							UIText.CloneSourcePage_fileNotFound, d
-									.getAbsolutePath()));
-					return false;
-				}
-				setErrorMessage(null);
-				return true;
-			}
-
-			if (uri.getHost() == null) {
-				uriInvalid();
-				setErrorMessage(NLS.bind(UIText.CloneSourcePage_fieldRequired,
-						UIText.CloneSourcePage_promptHost, proto));
-				return false;
-			}
-
-			if (isGIT(finalURI)) {
-				String badField = null;
-				if (uri.getUser() != null)
-					badField = UIText.CloneSourcePage_promptUser;
-				else if (uri.getPass() != null)
-					badField = UIText.CloneSourcePage_promptPassword;
-				if (badField != null) {
-					uriInvalid();
-					setErrorMessage(NLS.bind(
-							UIText.CloneSourcePage_fieldNotSupported, badField,
-							proto));
-					return false;
-				}
-			}
-
-			setErrorMessage(null);
-			return true;
-		} catch (URISyntaxException e) {
-			uriInvalid();
-			setErrorMessage(e.getReason());
-			return false;
-		} catch (Exception e) {
-			uriInvalid();
-			Activator.logError("Error validating " + getClass().getName(), e);
-			setErrorMessage(UIText.CloneSourcePage_internalError);
-			return false;
-		}
-	}
-
-	private static boolean isGIT(final URIish uri) {
-		return "git".equals(uri.getScheme());
-	}
-
-	private static boolean isFile(final URIish uri) {
-		if ("file".equals(uri.getScheme()))
-			return true;
-		if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
-				|| uri.getPass() != null || uri.getPath() == null)
-			return false;
-		if (uri.getScheme() == null)
-			return FS.resolve(new File("."), uri.getPath()).isDirectory();
-		return false;
-	}
-
-	private static boolean isSSH(final URIish uri) {
-		if (!uri.isRemote())
-			return false;
-		final String scheme = uri.getScheme();
-		if ("ssh".equals(scheme))
-			return true;
-		if ("ssh+git".equals(scheme))
-			return true;
-		if ("git+ssh".equals(scheme))
-			return true;
-		if (scheme == null && uri.getHost() != null && uri.getPath() != null)
-			return true;
-		return false;
-	}
-
-	private static String nullString(final String value) {
-		if (value == null)
-			return null;
-		final String v = value.trim();
-		return v.length() == 0 ? null : v;
-	}
-
-	private static void safeSet(final Text text, final String value) {
-		text.setText(value != null ? value : "");
-	}
-
-	private void setURI(final URIish u) {
-		try {
-			eventDepth++;
-			if (eventDepth == 1) {
-				for (final URIishChangeListener l : uriishChangeListeners)
-					l.uriishChanged(u);
-				uri = u;
-				uriText.setText(uri.toString());
-				setPageComplete(isPageComplete());
-			}
-		} finally {
-			eventDepth--;
-		}
-	}
-
-	private void updateAuthGroup() {
-		switch (scheme.getSelectionIndex()) {
-		case S_GIT:
-			hostText.setEnabled(true);
-			portText.setEnabled(true);
-			authGroup.setEnabled(false);
-			break;
-		case S_SSH:
-		case S_SFTP:
-		case S_HTTP:
-		case S_HTTPS:
-		case S_FTP:
-			hostText.setEnabled(true);
-			portText.setEnabled(true);
-			authGroup.setEnabled(true);
-			break;
-		case S_FILE:
-			hostText.setEnabled(false);
-			portText.setEnabled(false);
-			authGroup.setEnabled(false);
-			break;
-		}
-	}
-
-	private void uriInvalid() {
-		for (final URIishChangeListener l : uriishChangeListeners)
-			l.uriishChanged(null);
-	}
-}
\ No newline at end of file
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
index 50c2ef9..58c169a 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
@@ -25,7 +25,9 @@ import org.eclipse.ui.IImportWizard;
 import org.eclipse.ui.IWorkbench;
 import org.spearce.egit.core.op.CloneOperation;
 import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIText;
+import org.spearce.egit.ui.internal.components.RepositorySelectionPage;
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.Ref;
 import org.spearce.jgit.lib.Repository;
@@ -42,7 +44,7 @@ public class GitCloneWizard extends Wizard implements IImportWizard {
 	private static final String REMOTES_PREFIX_S = Constants.REMOTES_PREFIX
 			+ "/";
 
-	private CloneSourcePage cloneSource;
+	private RepositorySelectionPage cloneSource;
 
 	private SourceBranchPage validSource;
 
@@ -50,7 +52,8 @@ public class GitCloneWizard extends Wizard implements IImportWizard {
 
 	public void init(IWorkbench arg0, IStructuredSelection arg1) {
 		setWindowTitle(UIText.GitCloneWizard_title);
-		cloneSource = new CloneSourcePage();
+		setDefaultPageImageDescriptor(UIIcons.WIZBAN_IMPORT_REPO);
+		cloneSource = new RepositorySelectionPage(true);
 		validSource = new SourceBranchPage(cloneSource);
 		cloneDestination = new CloneDestinationPage(cloneSource, validSource);
 	}
@@ -68,11 +71,7 @@ public class GitCloneWizard extends Wizard implements IImportWizard {
 		final Repository db;
 		final RemoteConfig origin;
 
-		try {
-			uri = cloneSource.getURI();
-		} catch (URISyntaxException e) {
-			return false;
-		}
+		uri = cloneSource.getURI();
 
 		final File workdir = cloneDestination.getDestinationFile();
 		final String branch = cloneDestination.getInitialBranch();
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java
index b0aba1e..1768dbd 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java
@@ -10,7 +10,6 @@ package org.spearce.egit.ui.internal.clone;
 
 import java.io.File;
 import java.lang.reflect.InvocationTargetException;
-import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -40,19 +39,21 @@ import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableItem;
 import org.spearce.egit.ui.Activator;
-import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIText;
+import org.spearce.egit.ui.internal.components.RepositorySelectionListener;
+import org.spearce.egit.ui.internal.components.RepositorySelectionPage;
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.Ref;
 import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.transport.FetchConnection;
+import org.spearce.jgit.transport.RemoteConfig;
 import org.spearce.jgit.transport.Transport;
 import org.spearce.jgit.transport.URIish;
 
 class SourceBranchPage extends WizardPage {
 	private final List<BranchChangeListener> branchChangeListeners;
 
-	private final CloneSourcePage sourcePage;
+	private final RepositorySelectionPage sourcePage;
 
 	private URIish validated;
 
@@ -68,19 +69,20 @@ class SourceBranchPage extends WizardPage {
 
 	private String transportError;
 
-	SourceBranchPage(final CloneSourcePage sp) {
+	SourceBranchPage(final RepositorySelectionPage sp) {
 		super(SourceBranchPage.class.getName());
 		sourcePage = sp;
-		setTitle(UIText.CloneSourcePage_title);
-		setImageDescriptor(UIIcons.WIZBAN_IMPORT_REPO);
-		sourcePage.addURIishChangeListener(new URIishChangeListener() {
-			public void uriishChanged(final URIish newURI) {
-				if (newURI == null || !newURI.equals(validated)) {
-					validated = null;
-					setPageComplete(false);
-				}
-			}
-		});
+		setTitle(UIText.SourceBranchPage_title);
+		sourcePage
+				.addRepositorySelectionListener(new RepositorySelectionListener() {
+					public void selectionChanged(final URIish newURI,
+							final RemoteConfig newConfig) {
+						if (newURI == null || !newURI.equals(validated)) {
+							validated = null;
+							setPageComplete(false);
+						}
+					}
+				});
 		branchChangeListeners = new ArrayList<BranchChangeListener>(3);
 	}
 
@@ -190,12 +192,7 @@ class SourceBranchPage extends WizardPage {
 
 	private void revalidate() {
 		final URIish newURI;
-		try {
-			newURI = sourcePage.getURI();
-		} catch (URISyntaxException e) {
-			transportError(e.getReason());
-			return;
-		}
+		newURI = sourcePage.getURI();
 
 		label.setText(NLS.bind(UIText.SourceBranchPage_branchList, newURI
 				.toString()));
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/URIishChangeListener.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/URIishChangeListener.java
deleted file mode 100644
index e956e95..0000000
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/URIishChangeListener.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- *
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * See LICENSE for the full license text, also available.
- *******************************************************************************/
-package org.spearce.egit.ui.internal.clone;
-
-import org.spearce.jgit.transport.URIish;
-
-interface URIishChangeListener {
-	/**
-	 * Notify the receiver that the URI has changed.
-	 * 
-	 * @param newURI
-	 *            the new URI. Null if the new URI is invalid.
-	 */
-	void uriishChanged(URIish newURI);
-}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionListener.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionListener.java
new file mode 100644
index 0000000..ef5c33b
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionListener.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import org.spearce.jgit.transport.RemoteConfig;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * Interface for listeners of repository selection events from repository
+ * selection dialogs.
+ */
+public interface RepositorySelectionListener {
+	/**
+	 * Notify the receiver that the repository selection has changed. Each time
+	 * at least one argument of this call is null, which indicates that it has
+	 * illegal value or this form of repository selection is not selected.
+	 * 
+	 * @param newURI
+	 *            the new specified URI. null if the new URI is invalid or user
+	 *            chosen to specify repository as remote config instead of URI.
+	 * @param newConfig
+	 *            the new remote config. null if user chosen to specify
+	 *            repository as URI.
+	 */
+	public void selectionChanged(URIish newURI, RemoteConfig newConfig);
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionPage.java
new file mode 100644
index 0000000..a487cb1
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionPage.java
@@ -0,0 +1,680 @@
+/*******************************************************************************
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.VerifyEvent;
+import org.eclipse.swt.events.VerifyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIText;
+import org.spearce.jgit.transport.RemoteConfig;
+import org.spearce.jgit.transport.URIish;
+import org.spearce.jgit.util.FS;
+
+/**
+ * Wizard page that allows the user entering the location of a remote repository
+ * by specifying URL manually or selecting a preconfigured remote repository.
+ */
+public class RepositorySelectionPage extends WizardPage {
+	private static final int REMOTE_CONFIG_TEXT_MAX_LENGTH = 80;
+
+	private static final String DEFAULT_REMOTE_NAME = "origin";
+
+	private static final int S_GIT = 0;
+
+	private static final int S_SSH = 1;
+
+	private static final int S_SFTP = 2;
+
+	private static final int S_HTTP = 3;
+
+	private static final int S_HTTPS = 4;
+
+	private static final int S_FTP = 5;
+
+	private static final int S_FILE = 6;
+
+	private static final String[] DEFAULT_SCHEMES;
+	static {
+		DEFAULT_SCHEMES = new String[7];
+		DEFAULT_SCHEMES[S_GIT] = "git";
+		DEFAULT_SCHEMES[S_SSH] = "git+ssh";
+		DEFAULT_SCHEMES[S_SFTP] = "sftp";
+		DEFAULT_SCHEMES[S_HTTP] = "http";
+		DEFAULT_SCHEMES[S_HTTPS] = "https";
+		DEFAULT_SCHEMES[S_FTP] = "ftp";
+		DEFAULT_SCHEMES[S_FILE] = "file";
+	}
+	
+	private static void setEnabledRecursively(final Control control,
+			final boolean enable) {
+		control.setEnabled(enable);
+		if (control instanceof Composite)
+			for (final Control child : ((Composite) control).getChildren())
+				setEnabledRecursively(child, enable);
+	}
+
+	private final List<RepositorySelectionListener> selectionListeners;
+
+	private final List<RemoteConfig> configuredRemotes;
+
+	private Group authGroup;
+
+	private Text uriText;
+
+	private Text hostText;
+
+	private Text pathText;
+
+	private Text userText;
+
+	private Text passText;
+
+	private Combo scheme;
+
+	private Text portText;
+
+	private int eventDepth;
+
+	private URIish uri;
+
+	private RemoteConfig remoteConfig;
+
+	private RemoteConfig exposedRemoteConfig;
+
+	private URIish exposedURI;
+
+	private Composite remotePanel;
+
+	private Button remoteButton;
+
+	private Combo remoteCombo;
+
+	private Composite uriPanel;
+
+	private Button uriButton;
+
+	/**
+	 * Create repository selection page, allowing user specifying URI or
+	 * (optionally) choosing from preconfigured remotes list.
+	 * <p>
+	 * Wizard page is created without image, just with text description.
+	 * 
+	 * @param sourceSelection
+	 *            true if dialog is used for source selection; false otherwise
+	 *            (destination selection). This indicates appropriate text
+	 *            messages.
+	 * @param configuredRemotes
+	 *            list of configured remotes that user may select as an
+	 *            alternative to manual URI specification. Remotes appear in
+	 *            given order in GUI, with {@value #DEFAULT_REMOTE_NAME} as the
+	 *            default choice. List may be null or empty - no remotes
+	 *            configurations appear in this case. Note that the provided
+	 *            list may be changed by this constructor.
+	 */
+	public RepositorySelectionPage(final boolean sourceSelection,
+			final List<RemoteConfig> configuredRemotes) {
+		super(RepositorySelectionPage.class.getName());
+		this.uri = new URIish();
+
+		if (configuredRemotes != null)
+			removeUnusableRemoteConfigs(configuredRemotes);
+		if (configuredRemotes == null || configuredRemotes.isEmpty())
+			this.configuredRemotes = null;
+		else {
+			this.configuredRemotes = configuredRemotes;
+			this.remoteConfig = selectDefaultRemoteConfig();
+		}
+
+		if (sourceSelection) {
+			setTitle(UIText.RepositorySelectionPage_sourceSelectionTitle);
+			setDescription(UIText.RepositorySelectionPage_sourceSelectionDescription);
+		} else {
+			setTitle(UIText.RepositorySelectionPage_destinationSelectionTitle);
+			setDescription(UIText.RepositorySelectionPage_destinationSelectionDescription);
+		}
+		selectionListeners = new LinkedList<RepositorySelectionListener>();
+	}
+
+	/**
+	 * Create repository selection page, allowing user specifying URI, with no
+	 * preconfigured remotes selection.
+	 * 
+	 * @param sourceSelection
+	 *            true if dialog is used for source selection; false otherwise
+	 *            (destination selection). This indicates appropriate text
+	 *            messages.
+	 */
+	public RepositorySelectionPage(final boolean sourceSelection) {
+		this(sourceSelection, null);
+	}
+
+	/**
+	 * Add {@link RepositorySelectionListener} to list of listeners notified on
+	 * repository selection change.
+	 * 
+	 * @param l
+	 *            listener that will be notified about changes
+	 */
+	public void addRepositorySelectionListener(
+			final RepositorySelectionListener l) {
+		selectionListeners.add(l);
+	}
+
+	public void createControl(final Composite parent) {
+		final Composite panel = new Composite(parent, SWT.NULL);
+		panel.setLayout(new GridLayout());
+
+		if (configuredRemotes != null)
+			createRemotePanel(panel);
+		createUriPanel(panel);
+
+		updateRemoteAndURIPanels();
+		setControl(panel);
+		checkPage();
+	}
+
+	private void createRemotePanel(final Composite parent) {
+		remoteButton = new Button(parent, SWT.RADIO);
+		remoteButton
+				.setText(UIText.RepositorySelectionPage_configuredRemoteChoice
+						+ ":");
+		remoteButton.setSelection(true);
+
+		remotePanel = new Composite(parent, SWT.NULL);
+		remotePanel.setLayout(new GridLayout());
+		final GridData gd = new GridData();
+		gd.grabExcessHorizontalSpace = true;
+		gd.horizontalAlignment = SWT.FILL;
+		remotePanel.setLayoutData(gd);
+
+		remoteCombo = new Combo(remotePanel, SWT.READ_ONLY | SWT.DROP_DOWN);
+		final String items[] = new String[configuredRemotes.size()];
+		int i = 0;
+		for (final RemoteConfig rc : configuredRemotes)
+			items[i++] = getTextForRemoteConfig(rc);
+		final int defaultIndex = configuredRemotes.indexOf(remoteConfig);
+		remoteCombo.setItems(items);
+		remoteCombo.select(defaultIndex);
+		remoteCombo.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				final int idx = remoteCombo.getSelectionIndex();
+				remoteConfig = configuredRemotes.get(idx);
+				checkPage();
+			}
+		});
+	}
+
+	private void createUriPanel(final Composite parent) {
+		if (configuredRemotes != null) {
+			uriButton = new Button(parent, SWT.RADIO);
+			uriButton.setText(UIText.RepositorySelectionPage_uriChoice + ":");
+			uriButton.addSelectionListener(new SelectionAdapter() {
+				public void widgetSelected(SelectionEvent e) {
+					// occurs either on selection or unselection event
+					updateRemoteAndURIPanels();
+					checkPage();
+				}
+			});
+		}
+
+		uriPanel = new Composite(parent, SWT.NULL);
+		uriPanel.setLayout(new GridLayout());
+		final GridData gd = new GridData();
+		gd.grabExcessHorizontalSpace = true;
+		gd.horizontalAlignment = SWT.FILL;
+		uriPanel.setLayoutData(gd);
+
+		createLocationGroup(uriPanel);
+		createConnectionGroup(uriPanel);
+		authGroup = createAuthenticationGroup(uriPanel);
+	}
+
+	private void createLocationGroup(final Composite parent) {
+		final Group g = createGroup(parent,
+				UIText.RepositorySelectionPage_groupLocation);
+
+		newLabel(g, UIText.RepositorySelectionPage_promptURI + ":");
+		uriText = new Text(g, SWT.BORDER);
+		uriText.setLayoutData(createFieldGridData());
+		uriText.addModifyListener(new ModifyListener() {
+			public void modifyText(final ModifyEvent e) {
+				try {
+					eventDepth++;
+					if (eventDepth != 1)
+						return;
+
+					final URIish u = new URIish(uriText.getText());
+					safeSet(hostText, u.getHost());
+					safeSet(pathText, u.getPath());
+					safeSet(userText, u.getUser());
+					safeSet(passText, u.getPass());
+
+					if (u.getPort() > 0)
+						portText.setText(Integer.toString(u.getPort()));
+					else
+						portText.setText("");
+
+					if (isFile(u))
+						scheme.select(S_FILE);
+					else if (isSSH(u))
+						scheme.select(S_SSH);
+					else {
+						for (int i = 0; i < DEFAULT_SCHEMES.length; i++) {
+							if (DEFAULT_SCHEMES[i].equals(u.getScheme())) {
+								scheme.select(i);
+								break;
+							}
+						}
+					}
+
+					updateAuthGroup();
+					uri = u;
+				} catch (URISyntaxException err) {
+					// leave uriText as it is, but clean up underlying uri and
+					// decomposed fields
+					uri = new URIish();
+					hostText.setText("");
+					pathText.setText("");
+					userText.setText("");
+					passText.setText("");
+					portText.setText("");
+					scheme.select(0);
+				} finally {
+					eventDepth--;
+				}
+				checkPage();
+			}
+		});
+
+		newLabel(g, UIText.RepositorySelectionPage_promptHost + ":");
+		hostText = new Text(g, SWT.BORDER);
+		hostText.setLayoutData(createFieldGridData());
+		hostText.addModifyListener(new ModifyListener() {
+			public void modifyText(final ModifyEvent e) {
+				setURI(uri.setHost(nullString(hostText.getText())));
+			}
+		});
+
+		newLabel(g, UIText.RepositorySelectionPage_promptPath + ":");
+		pathText = new Text(g, SWT.BORDER);
+		pathText.setLayoutData(createFieldGridData());
+		pathText.addModifyListener(new ModifyListener() {
+			public void modifyText(final ModifyEvent e) {
+				setURI(uri.setPath(nullString(pathText.getText())));
+			}
+		});
+	}
+
+	private Group createAuthenticationGroup(final Composite parent) {
+		final Group g = createGroup(parent,
+				UIText.RepositorySelectionPage_groupAuthentication);
+
+		newLabel(g, UIText.RepositorySelectionPage_promptUser + ":");
+		userText = new Text(g, SWT.BORDER);
+		userText.setLayoutData(createFieldGridData());
+		userText.addModifyListener(new ModifyListener() {
+			public void modifyText(final ModifyEvent e) {
+				setURI(uri.setUser(nullString(userText.getText())));
+			}
+		});
+
+		newLabel(g, UIText.RepositorySelectionPage_promptPassword + ":");
+		passText = new Text(g, SWT.BORDER | SWT.PASSWORD);
+		passText.setLayoutData(createFieldGridData());
+		return g;
+	}
+
+	private void createConnectionGroup(final Composite parent) {
+		final Group g = createGroup(parent,
+				UIText.RepositorySelectionPage_groupConnection);
+
+		newLabel(g, UIText.RepositorySelectionPage_promptScheme + ":");
+		scheme = new Combo(g, SWT.DROP_DOWN | SWT.READ_ONLY);
+		scheme.setItems(DEFAULT_SCHEMES);
+		scheme.addSelectionListener(new SelectionAdapter() {
+			public void widgetSelected(final SelectionEvent e) {
+				final int idx = scheme.getSelectionIndex();
+				if (idx < 0)
+					setURI(uri.setScheme(null));
+				else
+					setURI(uri.setScheme(nullString(scheme.getItem(idx))));
+				updateAuthGroup();
+			}
+		});
+
+		newLabel(g, UIText.RepositorySelectionPage_promptPort + ":");
+		portText = new Text(g, SWT.BORDER);
+		portText.addVerifyListener(new VerifyListener() {
+			final Pattern p = Pattern.compile("^(?:[1-9][0-9]*)?$");
+
+			public void verifyText(final VerifyEvent e) {
+				final String v = portText.getText();
+				e.doit = p.matcher(
+						v.substring(0, e.start) + e.text + v.substring(e.end))
+						.matches();
+			}
+		});
+		portText.addModifyListener(new ModifyListener() {
+			public void modifyText(final ModifyEvent e) {
+				final String val = nullString(portText.getText());
+				if (val == null)
+					setURI(uri.setPort(-1));
+				else {
+					try {
+						setURI(uri.setPort(Integer.parseInt(val)));
+					} catch (NumberFormatException err) {
+						// Ignore it for now.
+					}
+				}
+			}
+		});
+	}
+
+	private static Group createGroup(final Composite parent, final String text) {
+		final Group g = new Group(parent, SWT.NONE);
+		final GridLayout layout = new GridLayout();
+		layout.numColumns = 2;
+		g.setLayout(layout);
+		g.setText(text);
+		final GridData gd = new GridData();
+		gd.grabExcessHorizontalSpace = true;
+		gd.horizontalAlignment = SWT.FILL;
+		g.setLayoutData(gd);
+		return g;
+	}
+
+	private static void newLabel(final Group g, final String text) {
+		new Label(g, SWT.NULL).setText(text);
+	}
+
+	private static GridData createFieldGridData() {
+		return new GridData(SWT.FILL, SWT.DEFAULT, true, false);
+	}
+
+	/**
+	 * @return the URI entered in the Wizard page. null if URI is invalid or
+	 *         user chosen to select remote config instead of providing direct
+	 *         URI.
+	 */
+	public URIish getURI() {
+		return exposedURI;
+	}
+
+	/**
+	 * @return the selected remote configuration in the Wizard page. null if
+	 *         user chosen to select repository as URI.
+	 */
+	public RemoteConfig getRemoteConfig() {
+		return exposedRemoteConfig;
+	}
+
+	private static boolean isGIT(final URIish uri) {
+		return "git".equals(uri.getScheme());
+	}
+
+	private static boolean isFile(final URIish uri) {
+		if ("file".equals(uri.getScheme()))
+			return true;
+		if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
+				|| uri.getPass() != null || uri.getPath() == null)
+			return false;
+		if (uri.getScheme() == null)
+			return FS.resolve(new File("."), uri.getPath()).isDirectory();
+		return false;
+	}
+
+	private static boolean isSSH(final URIish uri) {
+		if (!uri.isRemote())
+			return false;
+		final String scheme = uri.getScheme();
+		if ("ssh".equals(scheme))
+			return true;
+		if ("ssh+git".equals(scheme))
+			return true;
+		if ("git+ssh".equals(scheme))
+			return true;
+		if (scheme == null && uri.getHost() != null && uri.getPath() != null)
+			return true;
+		return false;
+	}
+
+	private static String nullString(final String value) {
+		if (value == null)
+			return null;
+		final String v = value.trim();
+		return v.length() == 0 ? null : v;
+	}
+
+	private static void safeSet(final Text text, final String value) {
+		text.setText(value != null ? value : "");
+	}
+
+	private boolean isURISelected() {
+		return configuredRemotes == null || uriButton.getSelection();
+	}
+
+	private void setURI(final URIish u) {
+		try {
+			eventDepth++;
+			if (eventDepth == 1) {
+				uri = u;
+				uriText.setText(uri.toString());
+				checkPage();
+			}
+		} finally {
+			eventDepth--;
+		}
+	}
+
+	private static void removeUnusableRemoteConfigs(
+			final List<RemoteConfig> remotes) {
+		final Iterator<RemoteConfig> iter = remotes.iterator();
+		while (iter.hasNext()) {
+			final RemoteConfig rc = iter.next();
+			if (rc.getURIs().isEmpty())
+				iter.remove();
+		}
+	}
+
+	private RemoteConfig selectDefaultRemoteConfig() {
+		for (final RemoteConfig rc : configuredRemotes)
+			if (getTextForRemoteConfig(rc) == DEFAULT_REMOTE_NAME)
+				return rc;
+		return configuredRemotes.get(0);
+	}
+
+	private static String getTextForRemoteConfig(final RemoteConfig rc) {
+		final StringBuilder sb = new StringBuilder(rc.getName());
+		sb.append(": ");
+		boolean first = true;
+		for (final URIish u : rc.getURIs()) {
+			final String uString = u.toString();
+			if (first)
+				first = false;
+			else {
+				sb.append(", ");
+				if (sb.length() + uString.length() > REMOTE_CONFIG_TEXT_MAX_LENGTH) {
+					sb.append("...");
+					break;
+				}
+			}
+			sb.append(uString);
+		}
+		return sb.toString();
+	}
+
+	private void checkPage() {
+		if (isURISelected()) {
+			assert uri != null;
+			if (uriText.getText().length() == 0) {
+				selectionIncomplete(null);
+				return;
+			}
+
+			try {
+				final URIish finalURI = new URIish(uriText.getText());
+				String proto = finalURI.getScheme();
+				if (proto == null && scheme.getSelectionIndex() >= 0)
+					proto = scheme.getItem(scheme.getSelectionIndex());
+
+				if (uri.getPath() == null) {
+					selectionIncomplete(NLS.bind(
+							UIText.RepositorySelectionPage_fieldRequired,
+							UIText.RepositorySelectionPage_promptPath, proto));
+					return;
+				}
+
+				if (isFile(finalURI)) {
+					String badField = null;
+					if (uri.getHost() != null)
+						badField = UIText.RepositorySelectionPage_promptHost;
+					else if (uri.getUser() != null)
+						badField = UIText.RepositorySelectionPage_promptUser;
+					else if (uri.getPass() != null)
+						badField = UIText.RepositorySelectionPage_promptPassword;
+					if (badField != null) {
+						selectionIncomplete(NLS
+								.bind(
+										UIText.RepositorySelectionPage_fieldNotSupported,
+										badField, proto));
+						return;
+					}
+
+					final File d = FS.resolve(new File("."), uri.getPath());
+					if (!d.exists()) {
+						selectionIncomplete(NLS.bind(
+								UIText.RepositorySelectionPage_fileNotFound, d
+										.getAbsolutePath()));
+						return;
+					}
+
+					selectionComplete(finalURI, null);
+					return;
+				}
+
+				if (uri.getHost() == null) {
+					selectionIncomplete(NLS.bind(
+							UIText.RepositorySelectionPage_fieldRequired,
+							UIText.RepositorySelectionPage_promptHost, proto));
+					return;
+				}
+
+				if (isGIT(finalURI)) {
+					String badField = null;
+					if (uri.getUser() != null)
+						badField = UIText.RepositorySelectionPage_promptUser;
+					else if (uri.getPass() != null)
+						badField = UIText.RepositorySelectionPage_promptPassword;
+					if (badField != null) {
+						selectionIncomplete(NLS
+								.bind(
+										UIText.RepositorySelectionPage_fieldNotSupported,
+										badField, proto));
+						return;
+					}
+				}
+
+				selectionComplete(finalURI, null);
+				return;
+			} catch (URISyntaxException e) {
+				selectionIncomplete(e.getReason());
+				return;
+			} catch (Exception e) {
+				Activator.logError("Error validating " + getClass().getName(),
+						e);
+				selectionIncomplete(UIText.RepositorySelectionPage_internalError);
+				return;
+			}
+		} else {
+			assert remoteButton.getSelection();
+			selectionComplete(null, remoteConfig);
+			return;
+		}
+	}
+
+	private void selectionIncomplete(final String errorMessage) {
+		setExposedSelection(null, null);
+		setErrorMessage(errorMessage);
+		setPageComplete(false);
+	}
+
+	private void selectionComplete(final URIish u, final RemoteConfig rc) {
+		setExposedSelection(u, rc);
+		setErrorMessage(null);
+		setPageComplete(true);
+	}
+
+	private void setExposedSelection(final URIish u, final RemoteConfig rc) {
+		if (u == exposedURI && rc == exposedRemoteConfig) // nothing changed
+			return;
+		this.exposedURI = u;
+		this.exposedRemoteConfig = rc;
+
+		for (final RepositorySelectionListener l : selectionListeners)
+			l.selectionChanged(u, rc);
+	}
+
+	private void updateRemoteAndURIPanels() {
+		setEnabledRecursively(uriPanel, isURISelected());
+		if (uriPanel.getEnabled())
+			updateAuthGroup();
+		if (configuredRemotes != null)
+			setEnabledRecursively(remotePanel, !isURISelected());
+	}
+
+	private void updateAuthGroup() {
+		switch (scheme.getSelectionIndex()) {
+		case S_GIT:
+			hostText.setEnabled(true);
+			portText.setEnabled(true);
+			setEnabledRecursively(authGroup, false);
+			break;
+		case S_SSH:
+		case S_SFTP:
+		case S_HTTP:
+		case S_HTTPS:
+		case S_FTP:
+			hostText.setEnabled(true);
+			portText.setEnabled(true);
+			setEnabledRecursively(authGroup, true);
+			break;
+		case S_FILE:
+			hostText.setEnabled(false);
+			portText.setEnabled(false);
+			setEnabledRecursively(authGroup, false);
+			break;
+		}
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index 55b1348..857568a 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -31,23 +31,28 @@ GitCloneWizard_title=Import Git Repository
 GitCloneWizard_jobName=Cloning from {0}
 GitCloneWizard_failed=Git repository clone failed.
 
-CloneSourcePage_title=Source Git Repository
-CloneSourcePage_description=Enter the location of the repository to be cloned.
-CloneSourcePage_groupLocation=Location
-CloneSourcePage_groupAuthentication=Authentication
-CloneSourcePage_groupConnection=Connection
-CloneSourcePage_promptURI=URI
-CloneSourcePage_promptHost=Host
-CloneSourcePage_promptPath=Repository path
-CloneSourcePage_promptUser=User
-CloneSourcePage_promptPassword=Password
-CloneSourcePage_promptScheme=Protocol
-CloneSourcePage_promptPort=Port
-CloneSourcePage_fieldRequired={0} required for {1} protocol.
-CloneSourcePage_fieldNotSupported={0} not supported on {1} protocol.
-CloneSourcePage_fileNotFound={0} does not exist.
-CloneSourcePage_internalError=Internal error; consult Eclipse error log.
+RepositorySelectionPage_sourceSelectionTitle=Source Git Repository
+RepositorySelectionPage_sourceSelectionDescription=Enter the location of the source repository.
+RepositorySelectionPage_destinationSelectionTitle=Destination Git Repository
+RepositorySelectionPage_destinationSelectionDescription=Enter the location of the destination repository.
+RepositorySelectionPage_configuredRemoteChoice=Configured remote repository
+RepositorySelectionPage_uriChoice=Custom URI
+RepositorySelectionPage_groupLocation=Location
+RepositorySelectionPage_groupAuthentication=Authentication
+RepositorySelectionPage_groupConnection=Connection
+RepositorySelectionPage_promptURI=URI
+RepositorySelectionPage_promptHost=Host
+RepositorySelectionPage_promptPath=Repository path
+RepositorySelectionPage_promptUser=User
+RepositorySelectionPage_promptPassword=Password
+RepositorySelectionPage_promptScheme=Protocol
+RepositorySelectionPage_promptPort=Port
+RepositorySelectionPage_fieldRequired={0} required for {1} protocol.
+RepositorySelectionPage_fieldNotSupported={0} not supported on {1} protocol.
+RepositorySelectionPage_fileNotFound={0} does not exist.
+RepositorySelectionPage_internalError=Internal error; consult Eclipse error log.
 
+SourceBranchPage_title=Source Git Repository
 SourceBranchPage_branchList=Branches of {0}:
 SourceBranchPage_selectAll=Select All
 SourceBranchPage_selectNone=Deselect All
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 19/31] Clone wizard and related: refactor, clean-up, fixes or improvements
  2008-08-17 20:43                                   ` [EGIT PATCH 18/31] Refactor/rewrite CloneSourcePage to universal RepositorySelectionPage Marek Zawirski
@ 2008-08-17 20:44                                     ` Marek Zawirski
  2008-08-17 20:44                                       ` [EGIT PATCH 20/31] Move clone logic away from GitCloneWizard to CloneOperation Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

At some stage, it was hard to change or improve anything in Clone wizard
or reuse some of its components without some global refactor or clean-up.

This changeset introduces some common patterns of handling internal pages
validation and inter-pages dependencies, it tries to address existing
issues (complicated internal dependencies, redundant asymmetrical code)
with clone code that IMHO were making it hard to maintain or modify.

Some of these changes introduce new behavior (fixes or improvements),
as it was hard for me to separate this in my iterative process...

- Remove mess of using both setPageComplete() and custom isPageComplete().
  Now, checkPage() is used (calling setPageComplete()) instead - for
  internal validation, while direct setPageComplete() calls are used in
  special situations like handling inter-pages dependencies.
- RepositorySelection class introduces encapsulated result of
  RepositorySelectionPage with helper methods working on that result.
- SelectionChangeListener became new common interface for notifying other
  (dependent) pages about selection changes in current page.
  BaseWizardPage is helper abstract class handling these listeners.
- User can move backward in wizard, change something, revert this change,
  then subsequent pages doesn't set page complete to false. It's now
  supported in common way in whole wizard by *selectionEquals() methods.
- Real-time checking for some fields of destination page is introduced.
- Minor changes like renames or some simplifications, forgotten changes.

Some of this commit resulting components, may be reused in other places,
that's why they are actually in .components package.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../ui/internal/clone/CloneDestinationPage.java    |  115 ++++++-----
 .../egit/ui/internal/clone/GitCloneWizard.java     |    3 +-
 .../egit/ui/internal/clone/SourceBranchPage.java   |  212 ++++++++++----------
 .../ui/internal/components/BaseWizardPage.java     |   58 ++++++
 .../internal/components/RepositorySelection.java   |  133 ++++++++++++
 .../components/RepositorySelectionListener.java    |   32 ---
 .../components/RepositorySelectionPage.java        |   61 ++----
 .../SelectionChangeListener.java}                  |   16 +-
 8 files changed, 397 insertions(+), 233 deletions(-)
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/BaseWizardPage.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelection.java
 delete mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionListener.java
 rename org.spearce.egit.ui/src/org/spearce/egit/ui/internal/{clone/BranchChangeListener.java => components/SelectionChangeListener.java} (50%)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
index 249bfd3..01bf54a 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
@@ -1,6 +1,7 @@
 /*******************************************************************************
  * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -9,6 +10,7 @@
 package org.spearce.egit.ui.internal.clone;
 
 import java.io.File;
+import java.util.List;
 
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.jface.wizard.WizardPage;
@@ -16,8 +18,8 @@ import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
 import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
@@ -28,12 +30,11 @@ import org.eclipse.swt.widgets.Group;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Text;
 import org.spearce.egit.ui.UIText;
-import org.spearce.egit.ui.internal.components.RepositorySelectionListener;
+import org.spearce.egit.ui.internal.components.RepositorySelection;
 import org.spearce.egit.ui.internal.components.RepositorySelectionPage;
+import org.spearce.egit.ui.internal.components.SelectionChangeListener;
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.Ref;
-import org.spearce.jgit.transport.RemoteConfig;
-import org.spearce.jgit.transport.URIish;
 
 /**
  * Wizard page that allows the user entering the location of a repository to be
@@ -44,7 +45,11 @@ class CloneDestinationPage extends WizardPage {
 
 	private final SourceBranchPage branchPage;
 
-	private URIish validated;
+	private RepositorySelection validatedRepoSelection;
+
+	private List<Ref> validatedSelectedBranches;
+
+	private Ref validatedHEAD;
 
 	private Combo initialBranch;
 
@@ -60,18 +65,13 @@ class CloneDestinationPage extends WizardPage {
 
 		setTitle(UIText.CloneDestinationPage_title);
 
-		sourcePage.addRepositorySelectionListener(new RepositorySelectionListener() {
-			public void selectionChanged(final URIish newURI,
-					final RemoteConfig newConfig) {
-				if (newURI == null || !newURI.equals(validated))
-					setPageComplete(false);
+		final SelectionChangeListener listener = new SelectionChangeListener() {
+			public void selectionChanged() {
+				checkPreviousPagesSelections();
 			}
-		});
-		branchPage.addBranchChangeListener(new BranchChangeListener() {
-			public void branchesChanged() {
-				setPageComplete(false);
-			}
-		});
+		};
+		sourcePage.addSelectionListener(listener);
+		branchPage.addSelectionListener(listener);
 	}
 
 	public void createControl(final Composite parent) {
@@ -84,7 +84,7 @@ class CloneDestinationPage extends WizardPage {
 		createConfigGroup(panel);
 
 		setControl(panel);
-		setPageComplete(isPageComplete());
+		checkPage();
 	}
 
 	@Override
@@ -94,6 +94,15 @@ class CloneDestinationPage extends WizardPage {
 		super.setVisible(visible);
 	}
 
+	private void checkPreviousPagesSelections() {
+		if (!sourcePage.selectionEquals(validatedRepoSelection)
+				|| !branchPage.selectionEquals(validatedSelectedBranches,
+						validatedHEAD))
+			setPageComplete(false);
+		else
+			checkPage();
+	}
+
 	private void createDestinationGroup(final Composite parent) {
 		final Group g = createGroup(parent,
 				UIText.CloneDestinationPage_groupDestination);
@@ -108,16 +117,12 @@ class CloneDestinationPage extends WizardPage {
 		directoryText.setLayoutData(createFieldGridData());
 		directoryText.addModifyListener(new ModifyListener() {
 			public void modifyText(final ModifyEvent e) {
-				setPageComplete(isPageComplete());
+				checkPage();
 			}
 		});
 		final Button b = new Button(p, SWT.PUSH);
 		b.setText(UIText.CloneDestinationPage_browseButton);
-		b.addSelectionListener(new SelectionListener() {
-			public void widgetDefaultSelected(SelectionEvent e) {
-				// Do nothing.
-			}
-
+		b.addSelectionListener(new SelectionAdapter() {
 			public void widgetSelected(final SelectionEvent e) {
 				final FileDialog d;
 
@@ -136,6 +141,12 @@ class CloneDestinationPage extends WizardPage {
 		newLabel(g, UIText.CloneDestinationPage_promptInitialBranch + ":");
 		initialBranch = new Combo(g, SWT.DROP_DOWN | SWT.READ_ONLY);
 		initialBranch.setLayoutData(createFieldGridData());
+		initialBranch.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(final SelectionEvent e) {
+				checkPage();
+			}
+		});
 	}
 
 	private void createConfigGroup(final Composite parent) {
@@ -146,6 +157,11 @@ class CloneDestinationPage extends WizardPage {
 		remoteText = new Text(g, SWT.BORDER);
 		remoteText.setText("origin");
 		remoteText.setLayoutData(createFieldGridData());
+		remoteText.addModifyListener(new ModifyListener() {
+			public void modifyText(ModifyEvent e) {
+				checkPage();
+			}
+		});
 	}
 
 	private static Group createGroup(final Composite parent, final String text) {
@@ -193,50 +209,55 @@ class CloneDestinationPage extends WizardPage {
 		return remoteText.getText();
 	}
 
-	@Override
-	public boolean isPageComplete() {
+	/**
+	 * Check internal state for page completion status.
+	 */
+	private void checkPage() {
 		final String dstpath = directoryText.getText();
 		if (dstpath.length() == 0) {
 			setErrorMessage(NLS.bind(UIText.CloneDestinationPage_fieldRequired,
 					UIText.CloneDestinationPage_promptDirectory));
-			return false;
+			setPageComplete(false);
+			return;
 		}
 		if (new File(dstpath).exists()) {
 			setErrorMessage(NLS.bind(UIText.CloneDestinationPage_errorExists,
 					new File(dstpath).getName()));
-			return false;
+			setPageComplete(false);
+			return;
 		}
 		if (initialBranch.getSelectionIndex() < 0) {
 			setErrorMessage(NLS.bind(UIText.CloneDestinationPage_fieldRequired,
 					UIText.CloneDestinationPage_promptInitialBranch));
-			return false;
+			setPageComplete(false);
+			return;
 		}
 		if (remoteText.getText().length() == 0) {
 			setErrorMessage(NLS.bind(UIText.CloneDestinationPage_fieldRequired,
 					UIText.CloneDestinationPage_promptRemoteName));
-			return false;
+			setPageComplete(false);
+			return;
 		}
 
 		setErrorMessage(null);
-		return true;
+		setPageComplete(true);
 	}
 
 	private void revalidate() {
-		URIish newURI = null;
-		newURI = sourcePage.getURI();
-		validated = newURI;
-
-		if (newURI == null || !newURI.equals(validated)) {
-			final String n = getSuggestedName();
-			setDescription(NLS.bind(UIText.CloneDestinationPage_description,
-					n != null ? n : "<unknown>"));
-
-			if (n != null) {
-				directoryText.setText(new File(ResourcesPlugin.getWorkspace()
-						.getRoot().getRawLocation().toFile(), n)
-						.getAbsolutePath());
-			}
+		if (sourcePage.selectionEquals(validatedRepoSelection)
+				&& branchPage.selectionEquals(validatedSelectedBranches,
+						validatedHEAD)) {
+			checkPage();
+			return;
 		}
+		validatedRepoSelection = sourcePage.getSelection();
+		validatedSelectedBranches = branchPage.getSelectedBranches();
+		validatedHEAD = branchPage.getHEAD();
+
+		final String n = getSuggestedName();
+		setDescription(NLS.bind(UIText.CloneDestinationPage_description, n));
+		directoryText.setText(new File(ResourcesPlugin.getWorkspace().getRoot()
+				.getRawLocation().toFile(), n).getAbsolutePath());
 
 		initialBranch.removeAll();
 		final Ref head = branchPage.getHEAD();
@@ -250,13 +271,11 @@ class CloneDestinationPage extends WizardPage {
 			initialBranch.add(name);
 		}
 		initialBranch.select(newix);
+		checkPage();
 	}
 
 	private String getSuggestedName() {
-		if (validated == null)
-			return null;
-
-		String path = validated.getPath();
+		String path = validatedRepoSelection.getURI().getPath();
 		int s = path.lastIndexOf('/');
 		if (s != -1)
 			path = path.substring(s + 1);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
index 58c169a..9c1d691 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
@@ -1,6 +1,7 @@
 /*******************************************************************************
  * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -71,7 +72,7 @@ public class GitCloneWizard extends Wizard implements IImportWizard {
 		final Repository db;
 		final RemoteConfig origin;
 
-		uri = cloneSource.getURI();
+		uri = cloneSource.getSelection().getURI();
 
 		final File workdir = cloneDestination.getDestinationFile();
 		final String branch = cloneDestination.getInitialBranch();
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java
index 1768dbd..7710612 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java
@@ -1,6 +1,7 @@
 /*******************************************************************************
  * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -23,49 +24,44 @@ import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.operation.IRunnableWithProgress;
-import org.eclipse.jface.wizard.WizardPage;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.layout.RowLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableItem;
 import org.spearce.egit.ui.Activator;
 import org.spearce.egit.ui.UIText;
-import org.spearce.egit.ui.internal.components.RepositorySelectionListener;
+import org.spearce.egit.ui.internal.components.BaseWizardPage;
+import org.spearce.egit.ui.internal.components.RepositorySelection;
 import org.spearce.egit.ui.internal.components.RepositorySelectionPage;
+import org.spearce.egit.ui.internal.components.SelectionChangeListener;
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.Ref;
 import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.transport.FetchConnection;
-import org.spearce.jgit.transport.RemoteConfig;
 import org.spearce.jgit.transport.Transport;
-import org.spearce.jgit.transport.URIish;
-
-class SourceBranchPage extends WizardPage {
-	private final List<BranchChangeListener> branchChangeListeners;
 
+class SourceBranchPage extends BaseWizardPage {
 	private final RepositorySelectionPage sourcePage;
 
-	private URIish validated;
+	private RepositorySelection validatedRepoSelection;
 
 	private Ref head;
 
-	private List<Ref> available = Collections.<Ref> emptyList();
+	private List<Ref> availableRefs = new ArrayList<Ref>();
 
-	private Label label;
+	private List<Ref> selectedRefs = new ArrayList<Ref>();
 
-	private Table availTable;
+	private Label label;
 
-	private boolean allSelected;
+	private Table refsTable;
 
 	private String transportError;
 
@@ -73,33 +69,19 @@ class SourceBranchPage extends WizardPage {
 		super(SourceBranchPage.class.getName());
 		sourcePage = sp;
 		setTitle(UIText.SourceBranchPage_title);
-		sourcePage
-				.addRepositorySelectionListener(new RepositorySelectionListener() {
-					public void selectionChanged(final URIish newURI,
-							final RemoteConfig newConfig) {
-						if (newURI == null || !newURI.equals(validated)) {
-							validated = null;
-							setPageComplete(false);
-						}
-					}
-				});
-		branchChangeListeners = new ArrayList<BranchChangeListener>(3);
-	}
 
-	void addBranchChangeListener(final BranchChangeListener l) {
-		branchChangeListeners.add(l);
+		sourcePage.addSelectionListener(new SelectionChangeListener() {
+			public void selectionChanged() {
+				if (!sourcePage.selectionEquals(validatedRepoSelection))
+					setPageComplete(false);
+				else
+					checkPage();
+			}
+		});
 	}
 
-	Collection<Ref> getSelectedBranches() {
-		allSelected = true;
-		final ArrayList<Ref> r = new ArrayList<Ref>(available.size());
-		for (int i = 0; i < available.size(); i++) {
-			if (availTable.getItem(i).getChecked())
-				r.add(available.get(i));
-			else
-				allSelected = false;
-		}
-		return r;
+	List<Ref> getSelectedBranches() {
+		return new ArrayList<Ref>(selectedRefs);
 	}
 
 	Ref getHEAD() {
@@ -107,7 +89,11 @@ class SourceBranchPage extends WizardPage {
 	}
 
 	boolean isAllSelected() {
-		return allSelected;
+		return availableRefs.size() == selectedRefs.size();
+	}
+
+	boolean selectionEquals(final List<Ref> selectedRefs, final Ref head) {
+		return this.selectedRefs.equals(selectedRefs) && this.head == head;
 	}
 
 	public void createControl(final Composite parent) {
@@ -119,14 +105,30 @@ class SourceBranchPage extends WizardPage {
 		label = new Label(panel, SWT.NONE);
 		label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
 
-		availTable = new Table(panel, SWT.CHECK | SWT.V_SCROLL | SWT.BORDER);
-		availTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-		availTable.addListener(SWT.Selection, new Listener() {
-			public void handleEvent(final Event event) {
-				if (event.detail == SWT.CHECK) {
-					notifyChanged();
-					setPageComplete(isPageComplete());
-				}
+		refsTable = new Table(panel, SWT.CHECK | SWT.V_SCROLL | SWT.BORDER);
+		refsTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+		refsTable.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(final SelectionEvent e) {
+				if (e.detail != SWT.CHECK)
+					return;
+
+				final TableItem tableItem = (TableItem) e.item;
+				final int i = refsTable.indexOf(tableItem);
+				final Ref ref = availableRefs.get(i);
+
+				if (tableItem.getChecked()) {
+					int insertionPos = 0;
+					for (int j = 0; j < i; j++) {
+						if (selectedRefs.contains(availableRefs.get(j)))
+							insertionPos++;
+					}
+					selectedRefs.add(insertionPos, ref);
+				} else
+					selectedRefs.remove(ref);
+
+				notifySelectionChanged();
+				checkPage();
 			}
 		});
 
@@ -135,36 +137,31 @@ class SourceBranchPage extends WizardPage {
 		Button b;
 		b = new Button(bPanel, SWT.PUSH);
 		b.setText(UIText.SourceBranchPage_selectAll);
-		b.addSelectionListener(new SelectionListener() {
-			public void widgetDefaultSelected(SelectionEvent e) {
-				// Do nothing.
-			}
-
-			public void widgetSelected(SelectionEvent e) {
-				for (int i = 0; i < availTable.getItemCount(); i++)
-					availTable.getItem(i).setChecked(true);
-				notifyChanged();
-				setPageComplete(isPageComplete());
+		b.addSelectionListener(new SelectionAdapter() {
+			public void widgetSelected(final SelectionEvent e) {
+				for (int i = 0; i < refsTable.getItemCount(); i++)
+					refsTable.getItem(i).setChecked(true);
+				selectedRefs.clear();
+				selectedRefs.addAll(availableRefs);
+				notifySelectionChanged();
+				checkPage();
 			}
 		});
 		b = new Button(bPanel, SWT.PUSH);
 		b.setText(UIText.SourceBranchPage_selectNone);
-		b.addSelectionListener(new SelectionListener() {
-			public void widgetDefaultSelected(SelectionEvent e) {
-				// Do nothing.
-			}
-
-			public void widgetSelected(SelectionEvent e) {
-				for (int i = 0; i < availTable.getItemCount(); i++)
-					availTable.getItem(i).setChecked(false);
-				notifyChanged();
-				setPageComplete(isPageComplete());
+		b.addSelectionListener(new SelectionAdapter() {
+			public void widgetSelected(final SelectionEvent e) {
+				for (int i = 0; i < refsTable.getItemCount(); i++)
+					refsTable.getItem(i).setChecked(false);
+				selectedRefs.clear();
+				notifySelectionChanged();
+				checkPage();
 			}
 		});
 		bPanel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
 
 		setControl(panel);
-		setPageComplete(false);
+		checkPage();
 	}
 
 	@Override
@@ -174,52 +171,57 @@ class SourceBranchPage extends WizardPage {
 		super.setVisible(visible);
 	}
 
-	@Override
-	public boolean isPageComplete() {
+	/**
+	 * Check internal state for page completion status. This method should be
+	 * called only when all necessary data from previous form is available.
+	 */
+	private void checkPage() {
 		if (transportError != null) {
 			setErrorMessage(transportError);
-			return false;
+			setPageComplete(false);
+			return;
 		}
 
 		if (getSelectedBranches().isEmpty()) {
 			setErrorMessage(UIText.SourceBranchPage_errorBranchRequired);
-			return false;
+			setPageComplete(false);
+			return;
 		}
 
 		setErrorMessage(null);
-		return true;
+		setPageComplete(true);
 	}
 
 	private void revalidate() {
-		final URIish newURI;
-		newURI = sourcePage.getURI();
-
-		label.setText(NLS.bind(UIText.SourceBranchPage_branchList, newURI
-				.toString()));
-		label.getParent().layout();
-
-		if (newURI.equals(validated)) {
-			setPageComplete(isPageComplete());
+		if (sourcePage.selectionEquals(validatedRepoSelection)) {
+			// URI hasn't changed, no need to refill the page with new data
+			checkPage();
 			return;
 		}
 
-		setErrorMessage(null);
-		setPageComplete(false);
+		final RepositorySelection newRepoSelection = sourcePage.getSelection();
+		label.setText(NLS.bind(UIText.SourceBranchPage_branchList,
+				newRepoSelection.getURI().toString()));
+		label.getParent().layout();
+
+		validatedRepoSelection = null;
 		transportError = null;
 		head = null;
-		available = new ArrayList<Ref>();
-		availTable.removeAll();
-		allSelected = false;
+		availableRefs.clear();
+		selectedRefs.clear();
+		refsTable.removeAll();
+		checkPage();
 		label.getDisplay().asyncExec(new Runnable() {
 			public void run() {
-				revalidateImpl(newURI);
+				revalidateImpl(newRepoSelection);
 			}
 		});
 	}
 
-	private void revalidateImpl(final URIish newURI) {
+	private void revalidateImpl(final RepositorySelection newRepoSelection) {
 		if (label.isDisposed() || !isCurrentPage())
 			return;
+
 		try {
 			getContainer().run(true, true, new IRunnableWithProgress() {
 				public void run(final IProgressMonitor pm)
@@ -231,7 +233,8 @@ class SourceBranchPage extends WizardPage {
 						monitor = pm;
 					try {
 						final Repository db = new Repository(new File("/tmp"));
-						final Transport tn = Transport.open(db, newURI);
+						final Transport tn = Transport.open(db,
+								newRepoSelection.getURI());
 						final Collection<Ref> adv;
 						final FetchConnection fn = tn.openFetch();
 						try {
@@ -247,20 +250,20 @@ class SourceBranchPage extends WizardPage {
 							final String n = r.getName();
 							if (!n.startsWith(Constants.HEADS_PREFIX + "/"))
 								continue;
-							available.add(r);
+							availableRefs.add(r);
 							if (idHEAD == null || head != null)
 								continue;
 							if (r.getObjectId().equals(idHEAD.getObjectId()))
 								head = r;
 						}
-						Collections.sort(available, new Comparator<Ref>() {
+						Collections.sort(availableRefs, new Comparator<Ref>() {
 							public int compare(final Ref o1, final Ref o2) {
 								return o1.getName().compareTo(o2.getName());
 							}
 						});
 						if (idHEAD != null && head == null) {
 							head = idHEAD;
-							available.add(0, idHEAD);
+							availableRefs.add(0, idHEAD);
 						}
 					} catch (Exception err) {
 						throw new InvocationTargetException(err);
@@ -287,29 +290,22 @@ class SourceBranchPage extends WizardPage {
 			return;
 		}
 
-		validated = newURI;
-		allSelected = true;
-		for (final Ref r : available) {
+		validatedRepoSelection = newRepoSelection;
+		for (final Ref r : availableRefs) {
 			String n = r.getName();
 			if (n.startsWith(Constants.HEADS_PREFIX + "/"))
 				n = n.substring((Constants.HEADS_PREFIX + "/").length());
-			final TableItem ti = new TableItem(availTable, SWT.NONE);
+			final TableItem ti = new TableItem(refsTable, SWT.NONE);
 			ti.setText(n);
 			ti.setChecked(true);
+			selectedRefs.add(r);
 		}
-		notifyChanged();
-		setErrorMessage(null);
-		setPageComplete(isPageComplete());
+		notifySelectionChanged();
+		checkPage();
 	}
 
 	private void transportError(final String msg) {
 		transportError = msg;
-		setErrorMessage(msg);
-		setPageComplete(false);
-	}
-
-	private void notifyChanged() {
-		for (final BranchChangeListener l : branchChangeListeners)
-			l.branchesChanged();
+		checkPage();
 	}
 }
\ No newline at end of file
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/BaseWizardPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/BaseWizardPage.java
new file mode 100644
index 0000000..bb5d1d4
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/BaseWizardPage.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.jface.wizard.WizardPage;
+
+/**
+ * Base wizard page class for pages that need support for inter-pages
+ * dependencies.
+ * <p>
+ * This abstract class maintains list of selection change listeners and provides
+ * method to notify them about selection change.
+ * 
+ * @see SelectionChangeListener
+ */
+public abstract class BaseWizardPage extends WizardPage {
+	private final List<SelectionChangeListener> selectionListeners;
+
+	/**
+	 * Create base wizard with specified name. Listeners list is empty.
+	 * 
+	 * @see WizardPage#WizardPage(String)
+	 * @param pageName
+	 *            page name.
+	 */
+	public BaseWizardPage(final String pageName) {
+		super(pageName);
+		selectionListeners = new LinkedList<SelectionChangeListener>();
+	}
+
+	/**
+	 * Add {@link SelectionChangeListener} to list of listeners notified on
+	 * selection change on this page.
+	 * 
+	 * @param l
+	 *            listener that will be notified about changes.
+	 */
+	public void addSelectionListener(final SelectionChangeListener l) {
+		selectionListeners.add(l);
+	}
+
+	/**
+	 * Notifies registered listeners about selection change.
+	 */
+	protected void notifySelectionChanged() {
+		for (final SelectionChangeListener l : selectionListeners)
+			l.selectionChanged();
+	}
+
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelection.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelection.java
new file mode 100644
index 0000000..d9aa6d5
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelection.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+
+package org.spearce.egit.ui.internal.components;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.spearce.jgit.transport.RemoteConfig;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * Data class representing selection of remote repository made by user.
+ * Selection is an URI or remote repository configuration.
+ * <p>
+ * Each immutable instance has at least one of two class fields (URI, remote
+ * config) set to null. null value indicates that it has illegal value or this
+ * form of repository selection is not selected.
+ * <p>
+ * If remote configuration is selected, it always has non-empty URIs list.
+ */
+public class RepositorySelection {
+	private URIish uri;
+
+	private RemoteConfig config;
+
+	static final RepositorySelection INVALID_SELECTION = new RepositorySelection(
+			null, null);
+
+	/**
+	 * @param uri
+	 *            the new specified URI. null if the new URI is invalid or user
+	 *            chosen to specify repository as remote config instead of URI.
+	 * @param config
+	 *            the new remote config. null if user chosen to specify
+	 *            repository as URI.
+	 */
+	RepositorySelection(final URIish uri, final RemoteConfig config) {
+		if (config != null && uri != null)
+			throw new IllegalArgumentException(
+					"URI and config cannot be set at the same time.");
+		this.config = config;
+		this.uri = uri;
+	}
+
+	/**
+	 * @return the selected URI (if specified by user as valid custom URI) or
+	 *         first URI from selected configuration (if specified by user as
+	 *         May be null if there is no valid selection.
+	 */
+	public URIish getURI() {
+		if (isConfigSelected())
+			return config.getURIs().get(0);
+		return uri;
+	}
+
+	/**
+	 * @return list of all selected URIs - either the one specified as custom
+	 *         URI or all URIs from selected configuration. May be null in case
+	 *         of no valid selection.
+	 */
+	public List<URIish> getAllURIs() {
+		if (isURISelected())
+			return Collections.singletonList(uri);
+		if (isConfigSelected())
+			return config.getURIs();
+		return null;
+	}
+
+	/**
+	 * @return the selected remote configuration. null if user chosen to select
+	 *         repository as URI.
+	 */
+	public RemoteConfig getConfig() {
+		return config;
+	}
+
+	/**
+	 * @return selected remote configuration name or null if selection is not a
+	 *         remote configuration.
+	 */
+	public String getConfigName() {
+		if (isConfigSelected())
+			return config.getName();
+		return null;
+	}
+
+	/**
+	 * @return true if selection contains valid URI or remote config, false if
+	 *         there is no valid selection.
+	 */
+	public boolean isValidSelection() {
+		return uri != null || config != null;
+	}
+
+	/**
+	 * @return true if user selected valid URI, false if user selected invalid
+	 *         URI or remote config.
+	 */
+	public boolean isURISelected() {
+		return uri != null;
+	}
+
+	/**
+	 * @return true if user selected remote configuration, false if user
+	 *         selected (invalid or valid) URI.
+	 */
+	public boolean isConfigSelected() {
+		return config != null;
+	}
+
+	@Override
+	public boolean equals(final Object obj) {
+		if (obj instanceof RepositorySelection) {
+			final RepositorySelection other = (RepositorySelection) obj;
+			if (uri == null ^ other.uri == null)
+				return false;
+			if (uri != null && !uri.equals(other.uri))
+				return false;
+
+			if (config != other.config)
+				return false;
+
+			return true;
+		} else
+			return false;
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionListener.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionListener.java
deleted file mode 100644
index ef5c33b..0000000
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionListener.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
- *
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * See LICENSE for the full license text, also available.
- *******************************************************************************/
-package org.spearce.egit.ui.internal.components;
-
-import org.spearce.jgit.transport.RemoteConfig;
-import org.spearce.jgit.transport.URIish;
-
-/**
- * Interface for listeners of repository selection events from repository
- * selection dialogs.
- */
-public interface RepositorySelectionListener {
-	/**
-	 * Notify the receiver that the repository selection has changed. Each time
-	 * at least one argument of this call is null, which indicates that it has
-	 * illegal value or this form of repository selection is not selected.
-	 * 
-	 * @param newURI
-	 *            the new specified URI. null if the new URI is invalid or user
-	 *            chosen to specify repository as remote config instead of URI.
-	 * @param newConfig
-	 *            the new remote config. null if user chosen to specify
-	 *            repository as URI.
-	 */
-	public void selectionChanged(URIish newURI, RemoteConfig newConfig);
-}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionPage.java
index a487cb1..abb7ddb 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RepositorySelectionPage.java
@@ -13,11 +13,9 @@ package org.spearce.egit.ui.internal.components;
 import java.io.File;
 import java.net.URISyntaxException;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.regex.Pattern;
 
-import org.eclipse.jface.wizard.WizardPage;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
@@ -45,7 +43,7 @@ import org.spearce.jgit.util.FS;
  * Wizard page that allows the user entering the location of a remote repository
  * by specifying URL manually or selecting a preconfigured remote repository.
  */
-public class RepositorySelectionPage extends WizardPage {
+public class RepositorySelectionPage extends BaseWizardPage {
 	private static final int REMOTE_CONFIG_TEXT_MAX_LENGTH = 80;
 
 	private static final String DEFAULT_REMOTE_NAME = "origin";
@@ -75,7 +73,7 @@ public class RepositorySelectionPage extends WizardPage {
 		DEFAULT_SCHEMES[S_FTP] = "ftp";
 		DEFAULT_SCHEMES[S_FILE] = "file";
 	}
-	
+
 	private static void setEnabledRecursively(final Control control,
 			final boolean enable) {
 		control.setEnabled(enable);
@@ -84,8 +82,6 @@ public class RepositorySelectionPage extends WizardPage {
 				setEnabledRecursively(child, enable);
 	}
 
-	private final List<RepositorySelectionListener> selectionListeners;
-
 	private final List<RemoteConfig> configuredRemotes;
 
 	private Group authGroup;
@@ -110,9 +106,7 @@ public class RepositorySelectionPage extends WizardPage {
 
 	private RemoteConfig remoteConfig;
 
-	private RemoteConfig exposedRemoteConfig;
-
-	private URIish exposedURI;
+	private RepositorySelection selection;
 
 	private Composite remotePanel;
 
@@ -155,6 +149,7 @@ public class RepositorySelectionPage extends WizardPage {
 			this.configuredRemotes = configuredRemotes;
 			this.remoteConfig = selectDefaultRemoteConfig();
 		}
+		selection = RepositorySelection.INVALID_SELECTION;
 
 		if (sourceSelection) {
 			setTitle(UIText.RepositorySelectionPage_sourceSelectionTitle);
@@ -163,7 +158,6 @@ public class RepositorySelectionPage extends WizardPage {
 			setTitle(UIText.RepositorySelectionPage_destinationSelectionTitle);
 			setDescription(UIText.RepositorySelectionPage_destinationSelectionDescription);
 		}
-		selectionListeners = new LinkedList<RepositorySelectionListener>();
 	}
 
 	/**
@@ -180,15 +174,22 @@ public class RepositorySelectionPage extends WizardPage {
 	}
 
 	/**
-	 * Add {@link RepositorySelectionListener} to list of listeners notified on
-	 * repository selection change.
+	 * @return repository selection representing current page state.
+	 */
+	public RepositorySelection getSelection() {
+		return selection;
+	}
+
+	/**
+	 * Compare current repository selection set by user to provided one.
 	 * 
-	 * @param l
-	 *            listener that will be notified about changes
+	 * @param s
+	 *            repository selection to compare.
+	 * @return true if provided selection is equal to current page selection,
+	 *         false otherwise.
 	 */
-	public void addRepositorySelectionListener(
-			final RepositorySelectionListener l) {
-		selectionListeners.add(l);
+	public boolean selectionEquals(final RepositorySelection s) {
+		return selection.equals(s);
 	}
 
 	public void createControl(final Composite parent) {
@@ -423,23 +424,6 @@ public class RepositorySelectionPage extends WizardPage {
 		return new GridData(SWT.FILL, SWT.DEFAULT, true, false);
 	}
 
-	/**
-	 * @return the URI entered in the Wizard page. null if URI is invalid or
-	 *         user chosen to select remote config instead of providing direct
-	 *         URI.
-	 */
-	public URIish getURI() {
-		return exposedURI;
-	}
-
-	/**
-	 * @return the selected remote configuration in the Wizard page. null if
-	 *         user chosen to select repository as URI.
-	 */
-	public RemoteConfig getRemoteConfig() {
-		return exposedRemoteConfig;
-	}
-
 	private static boolean isGIT(final URIish uri) {
 		return "git".equals(uri.getScheme());
 	}
@@ -637,13 +621,12 @@ public class RepositorySelectionPage extends WizardPage {
 	}
 
 	private void setExposedSelection(final URIish u, final RemoteConfig rc) {
-		if (u == exposedURI && rc == exposedRemoteConfig) // nothing changed
+		final RepositorySelection newSelection = new RepositorySelection(u, rc);
+		if (newSelection.equals(selection))
 			return;
-		this.exposedURI = u;
-		this.exposedRemoteConfig = rc;
 
-		for (final RepositorySelectionListener l : selectionListeners)
-			l.selectionChanged(u, rc);
+		selection = newSelection;
+		notifySelectionChanged();
 	}
 
 	private void updateRemoteAndURIPanels() {
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/BranchChangeListener.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/SelectionChangeListener.java
similarity index 50%
rename from org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/BranchChangeListener.java
rename to org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/SelectionChangeListener.java
index 3ef8e16..bde1d21 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/BranchChangeListener.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/SelectionChangeListener.java
@@ -1,13 +1,19 @@
 /*******************************************************************************
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * See LICENSE for the full license text, also available.
  *******************************************************************************/
-package org.spearce.egit.ui.internal.clone;
+package org.spearce.egit.ui.internal.components;
 
-interface BranchChangeListener {
-	/** Notify the receiver that the branches have changed. */
-	void branchesChanged();
+/**
+ * General interface for receivers of selection-changed notifications from
+ * various components.
+ */
+public interface SelectionChangeListener {
+	/**
+	 * Called when selection in calling component has changed.
+	 */
+	public void selectionChanged();
 }
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 20/31] Move clone logic away from GitCloneWizard to CloneOperation
  2008-08-17 20:44                                     ` [EGIT PATCH 19/31] Clone wizard and related: refactor, clean-up, fixes or improvements Marek Zawirski
@ 2008-08-17 20:44                                       ` Marek Zawirski
  2008-08-17 20:44                                         ` [EGIT PATCH 21/31] Add canCreateSubdir() heuristic in CloneDestinationPage Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Clone logic was unnaturally splitted between GitCloneWizard and
CloneOperation classes. Now, repository initialization part is moved
to CloneOperation, so there is cleaner separation of responsibilties.

Failure handling is also improved, incompletely cloned repository
directory is removed before reporting problem to user.

Directory creation is problematic issue because of potential errors,
so it's keeped in GitCloneWizard.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../org/spearce/egit/core/op/CloneOperation.java   |  129 +++++++++++++++++---
 .../src/org/spearce/egit/ui/UIText.java            |    4 +
 .../egit/ui/internal/clone/GitCloneWizard.java     |   78 +++----------
 .../src/org/spearce/egit/ui/uitext.properties      |    1 +
 4 files changed, 130 insertions(+), 82 deletions(-)

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/op/CloneOperation.java b/org.spearce.egit.core/src/org/spearce/egit/core/op/CloneOperation.java
index 656f3cb..531045b 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/op/CloneOperation.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/op/CloneOperation.java
@@ -3,6 +3,7 @@
  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -10,8 +11,11 @@
  *******************************************************************************/
 package org.spearce.egit.core.op;
 
+import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
+import java.net.URISyntaxException;
+import java.util.Collection;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.NullProgressMonitor;
@@ -31,39 +35,71 @@ import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.lib.Tree;
 import org.spearce.jgit.lib.WorkDirCheckout;
 import org.spearce.jgit.transport.FetchResult;
+import org.spearce.jgit.transport.RefSpec;
 import org.spearce.jgit.transport.RemoteConfig;
 import org.spearce.jgit.transport.Transport;
+import org.spearce.jgit.transport.URIish;
 
 /**
  * Clones a repository from a remote location to a local location.
  */
 public class CloneOperation implements IRunnableWithProgress {
-	private final Repository local;
+	private static final String HEADS_PREFIX = Constants.HEADS_PREFIX;
 
-	private final RemoteConfig remote;
+	private static final String REMOTES_PREFIX_S = Constants.REMOTES_PREFIX
+			+ "/";
+
+	private final URIish uri;
+
+	private final boolean allSelected;
+
+	private final Collection<Ref> selectedBranches;
+
+	private final File workdir;
 
 	private final String branch;
 
+	private final String remoteName;
+
+	private Repository local;
+
+	private RemoteConfig remoteConfig;
+
 	private FetchResult fetchResult;
 
 	/**
 	 * Create a new clone operation.
 	 * 
-	 * @param r
-	 *            repository the checkout will happen within.
-	 * @param t
+	 * @param uri
 	 *            remote we should fetch from.
-	 * @param b
+	 * @param allSelected
+	 *            true when all branches have to be fetched (indicates wildcard
+	 *            in created fetch refspec), false otherwise.
+	 * @param selectedBranches
+	 *            collection of branches to fetch. Ignored when allSelected is
+	 *            true.
+	 * @param workdir
+	 *            working directory to clone to. The directory may or may not
+	 *            already exist.
+	 * @param branch
 	 *            branch to initially clone from.
+	 * @param remoteName
+	 *            name of created remote config as source remote (typically
+	 *            named "origin").
 	 */
-	public CloneOperation(final Repository r, final RemoteConfig t,
-			final String b) {
-		local = r;
-		remote = t;
-		branch = b;
+	public CloneOperation(final URIish uri, final boolean allSelected,
+			final Collection<Ref> selectedBranches, final File workdir,
+			final String branch, final String remoteName) {
+		this.uri = uri;
+		this.allSelected = allSelected;
+		this.selectedBranches = selectedBranches;
+		this.workdir = workdir;
+		this.branch = branch;
+		this.remoteName = remoteName;
 	}
 
-	public void run(final IProgressMonitor pm) throws InvocationTargetException {
+	public void run(final IProgressMonitor pm)
+			throws InvocationTargetException, InterruptedException {
 		final IProgressMonitor monitor;
 		if (pm == null)
 			monitor = new NullProgressMonitor();
@@ -71,21 +107,65 @@ public class CloneOperation implements IRunnableWithProgress {
 			monitor = pm;
 
 		try {
-			monitor.beginTask(NLS.bind(CoreText.CloneOperation_title, remote
-					.getURIs().get(0).toString()), 5000);
-			doFetch(new SubProgressMonitor(monitor, 4000));
-			doCheckout(new SubProgressMonitor(monitor, 1000));
-		} catch (IOException e) {
-			if (!monitor.isCanceled())
+			monitor.beginTask(NLS.bind(CoreText.CloneOperation_title, uri),
+					5000);
+			try {
+				doInit(new SubProgressMonitor(monitor, 100));
+				doFetch(new SubProgressMonitor(monitor, 4000));
+				doCheckout(new SubProgressMonitor(monitor, 900));
+			} finally {
+				closeLocal();
+			}
+		} catch (final Exception e) {
+			delete(workdir);
+			if (monitor.isCanceled())
+				throw new InterruptedException();
+			else
 				throw new InvocationTargetException(e);
 		} finally {
 			monitor.done();
 		}
 	}
 
+	private void closeLocal() {
+		if (local != null) {
+			local.close();
+			local = null;
+		}
+	}
+
+	private void doInit(final IProgressMonitor monitor)
+			throws URISyntaxException, IOException {
+		monitor.setTaskName("Initializing local repository");
+
+		final File gitdir = new File(workdir, ".git");
+		local = new Repository(gitdir);
+		local.create();
+		local.writeSymref(Constants.HEAD, branch);
+
+		remoteConfig = new RemoteConfig(local.getConfig(), remoteName);
+		remoteConfig.addURI(uri);
+
+		final String dst = REMOTES_PREFIX_S + remoteConfig.getName();
+		RefSpec wcrs = new RefSpec();
+		wcrs = wcrs.setForceUpdate(true);
+		wcrs = wcrs.setSourceDestination(HEADS_PREFIX + "/*", dst + "/*");
+
+		if (allSelected) {
+			remoteConfig.addFetchRefSpec(wcrs);
+		} else {
+			for (final Ref ref : selectedBranches)
+				if (wcrs.matchSource(ref))
+					remoteConfig.addFetchRefSpec(wcrs.expandFromSource(ref));
+		}
+
+		remoteConfig.update(local.getConfig());
+		local.getConfig().save();
+	}
+
 	private void doFetch(final IProgressMonitor monitor)
 			throws NotSupportedException, TransportException {
-		final Transport tn = Transport.open(local, remote);
+		final Transport tn = Transport.open(local, remoteConfig);
 		try {
 			final EclipseGitProgressTransformer pm;
 			pm = new EclipseGitProgressTransformer(monitor);
@@ -116,4 +196,15 @@ public class CloneOperation implements IRunnableWithProgress {
 		monitor.setTaskName("Writing index");
 		index.write();
 	}
+
+	private static void delete(final File d) {
+		if (d.isDirectory()) {
+			final File[] items = d.listFiles();
+			if (items != null) {
+				for (final File c : items)
+					delete(c);
+			}
+		}
+		d.delete();
+	}
 }
\ No newline at end of file
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 0d39440..9150832 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -53,6 +53,9 @@ public class UIText extends NLS {
 	public static String GitCloneWizard_failed;
 
 	/** */
+	public static String GitCloneWizard_errorCannotCreate;
+
+	/** */
 	public static String RepositorySelectionPage_sourceSelectionTitle;
 
 	/** */
@@ -180,6 +183,7 @@ public class UIText extends NLS {
 
 	/** */
 	public static String ResourceHistory_toggleCommentFill;
+
 	/** */
 	public static String ResourceHistory_toggleRevDetail;
 
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
index 9c1d691..efcf57f 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
@@ -10,9 +10,8 @@
 package org.spearce.egit.ui.internal.clone;
 
 import java.io.File;
-import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
-import java.net.URISyntaxException;
+import java.util.Collection;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
@@ -29,22 +28,13 @@ import org.spearce.egit.ui.Activator;
 import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIText;
 import org.spearce.egit.ui.internal.components.RepositorySelectionPage;
-import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.Ref;
-import org.spearce.jgit.lib.Repository;
-import org.spearce.jgit.transport.RefSpec;
-import org.spearce.jgit.transport.RemoteConfig;
 import org.spearce.jgit.transport.URIish;
 
 /**
  * Import Git Repository Wizard. A front end to a git clone operation.
  */
 public class GitCloneWizard extends Wizard implements IImportWizard {
-	private static final String HEADS_PREFIX = Constants.HEADS_PREFIX;
-
-	private static final String REMOTES_PREFIX_S = Constants.REMOTES_PREFIX
-			+ "/";
-
 	private RepositorySelectionPage cloneSource;
 
 	private SourceBranchPage validSource;
@@ -68,62 +58,35 @@ public class GitCloneWizard extends Wizard implements IImportWizard {
 
 	@Override
 	public boolean performFinish() {
-		final URIish uri;
-		final Repository db;
-		final RemoteConfig origin;
-
-		uri = cloneSource.getSelection().getURI();
-
+		final URIish uri = cloneSource.getSelection().getURI();
+		final boolean allSelected = validSource.isAllSelected();
+		final Collection<Ref> selectedBranches = validSource
+				.getSelectedBranches();
 		final File workdir = cloneDestination.getDestinationFile();
 		final String branch = cloneDestination.getInitialBranch();
-		final File gitdir = new File(workdir, ".git");
-		try {
-			db = new Repository(gitdir);
-			db.create();
-			db.writeSymref(Constants.HEAD, branch);
+		final String remoteName = cloneDestination.getRemote();
 
-			final String rn = cloneDestination.getRemote();
-			origin = new RemoteConfig(db.getConfig(), rn);
-			origin.addURI(uri);
-
-			final String dst = REMOTES_PREFIX_S + origin.getName();
-			RefSpec wcrs = new RefSpec();
-			wcrs = wcrs.setForceUpdate(true);
-			wcrs = wcrs.setSourceDestination(HEADS_PREFIX + "/*", dst + "/*");
-
-			if (validSource.isAllSelected()) {
-				origin.addFetchRefSpec(wcrs);
-			} else {
-				for (final Ref ref : validSource.getSelectedBranches())
-					if (wcrs.matchSource(ref))
-						origin.addFetchRefSpec(wcrs.expandFromSource(ref));
-			}
-
-			origin.update(db.getConfig());
-			db.getConfig().save();
-		} catch (IOException err) {
-			Activator.logError(UIText.GitCloneWizard_failed, err);
+		if (!workdir.mkdirs()) {
+			final String errorMessage = NLS.bind(
+					UIText.GitCloneWizard_errorCannotCreate, workdir.getPath());
 			ErrorDialog.openError(getShell(), getWindowTitle(),
 					UIText.GitCloneWizard_failed, new Status(IStatus.ERROR,
-							Activator.getPluginId(), 0, err.getMessage(), err));
-			return false;
-		} catch (URISyntaxException e) {
+							Activator.getPluginId(), 0, errorMessage, null));
+			// let's give user a chance to fix this minor problem
 			return false;
 		}
 
-		final CloneOperation op = new CloneOperation(db, origin, branch);
+		final CloneOperation op = new CloneOperation(uri, allSelected,
+				selectedBranches, workdir, branch, remoteName);
 		final Job job = new Job(NLS.bind(UIText.GitCloneWizard_jobName, uri
 				.toString())) {
 			@Override
 			protected IStatus run(final IProgressMonitor monitor) {
 				try {
 					op.run(monitor);
-					if (monitor.isCanceled()) {
-						db.close();
-						delete(workdir);
-						return Status.CANCEL_STATUS;
-					}
 					return Status.OK_STATUS;
+				} catch (InterruptedException e) {
+					return Status.CANCEL_STATUS;
 				} catch (InvocationTargetException e) {
 					Throwable thr = e.getCause();
 					return new Status(IStatus.ERROR, Activator.getPluginId(),
@@ -135,15 +98,4 @@ public class GitCloneWizard extends Wizard implements IImportWizard {
 		job.schedule();
 		return true;
 	}
-
-	private static void delete(final File d) {
-		if (d.isDirectory()) {
-			final File[] items = d.listFiles();
-			if (items != null) {
-				for (final File c : items)
-					delete(c);
-			}
-		}
-		d.delete();
-	}
 }
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index 857568a..420b610 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -30,6 +30,7 @@ ExistingOrNewPage_createInParent=Create repository in project's parent directory
 GitCloneWizard_title=Import Git Repository
 GitCloneWizard_jobName=Cloning from {0}
 GitCloneWizard_failed=Git repository clone failed.
+GitCloneWizard_errorCannotCreate=Cannot create directory {0}.
 
 RepositorySelectionPage_sourceSelectionTitle=Source Git Repository
 RepositorySelectionPage_sourceSelectionDescription=Enter the location of the source repository.
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 21/31] Add canCreateSubdir() heuristic in CloneDestinationPage
  2008-08-17 20:44                                       ` [EGIT PATCH 20/31] Move clone logic away from GitCloneWizard to CloneOperation Marek Zawirski
@ 2008-08-17 20:44                                         ` Marek Zawirski
  2008-08-17 20:44                                           ` [EGIT PATCH 22/31] Set FileDialog selection appropriately in clone wizard Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

This method provide user with live-feedback when chosen subdirectory
can't be created for sure.

This is only a heuristic (may produce false-positive errors), as it is
problematic to handle File#canWrite() noisy return values for Windows'
Java.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../ui/internal/clone/CloneDestinationPage.java    |   23 ++++++++++++++++++-
 1 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
index 01bf54a..e1e9858 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
@@ -220,9 +220,17 @@ class CloneDestinationPage extends WizardPage {
 			setPageComplete(false);
 			return;
 		}
-		if (new File(dstpath).exists()) {
+		final File dstFile = new File(dstpath);
+		if (dstFile.exists()) {
 			setErrorMessage(NLS.bind(UIText.CloneDestinationPage_errorExists,
-					new File(dstpath).getName()));
+					dstFile.getName()));
+			setPageComplete(false);
+			return;
+		}
+		final File absoluteFile = dstFile.getAbsoluteFile();
+		if (!canCreateSubdir(absoluteFile.getParentFile())) {
+			setErrorMessage(NLS.bind(UIText.GitCloneWizard_errorCannotCreate,
+					dstFile.getPath()));
 			setPageComplete(false);
 			return;
 		}
@@ -243,6 +251,17 @@ class CloneDestinationPage extends WizardPage {
 		setPageComplete(true);
 	}
 
+	// this is actually just an optimistic heuristic - should be named
+	// isThereHopeThatCanCreateSubdir() as probably there is no 100% reliable
+	// way to check that in Java for Windows
+	private static boolean canCreateSubdir(final File parent) {
+		if (parent == null)
+			return true;
+		if (parent.exists())
+			return parent.isDirectory() && parent.canWrite();
+		return canCreateSubdir(parent.getParentFile());
+	}
+
 	private void revalidate() {
 		if (sourcePage.selectionEquals(validatedRepoSelection)
 				&& branchPage.selectionEquals(validatedSelectedBranches,
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 22/31] Set FileDialog selection appropriately in clone wizard
  2008-08-17 20:44                                         ` [EGIT PATCH 21/31] Add canCreateSubdir() heuristic in CloneDestinationPage Marek Zawirski
@ 2008-08-17 20:44                                           ` Marek Zawirski
  2008-08-17 20:44                                             ` [EGIT PATCH 23/31] Allow selecting empty dir " Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Previous selection scheme was probably not correct, or at least not
portable. It hasn't worked on GTK+ SWT implementation.

New code still doesn't work on GTK+ SWT in Eclipse 3.3, but it works in
Eclipse 3.4. FileDialog for GTK+ in 3.3 was probably buggy. I've created
a bug at eclipse.org bugzilla for that (241824), but it won't be fixed
for 3.3.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../ui/internal/clone/CloneDestinationPage.java    |    7 ++++---
 1 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
index e1e9858..a445cf5 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
@@ -128,9 +128,10 @@ class CloneDestinationPage extends WizardPage {
 
 				d = new FileDialog(getShell(), SWT.APPLICATION_MODAL | SWT.SAVE);
 				if (directoryText.getText().length() > 0) {
-					final File f = new File(directoryText.getText());
-					d.setFilterPath(f.getAbsoluteFile().getAbsolutePath());
-					d.setFileName(f.getName());
+					final File file = new File(directoryText.getText())
+							.getAbsoluteFile();
+					d.setFilterPath(file.getParent());
+					d.setFileName(file.getName());
 				}
 				final String r = d.open();
 				if (r != null)
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 23/31] Allow selecting empty dir in clone wizard
  2008-08-17 20:44                                           ` [EGIT PATCH 22/31] Set FileDialog selection appropriately in clone wizard Marek Zawirski
@ 2008-08-17 20:44                                             ` Marek Zawirski
  2008-08-17 20:44                                               ` [EGIT PATCH 24/31] Clone wizard: force dir to suggested path only if repo selection change Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Previous behavior was to not allow selecting empty directory. While
it seems to be sensible to use empty dir as destination and it's just
a little bit more complex, this feature is added.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/egit/ui/UIText.java            |    2 +-
 .../ui/internal/clone/CloneDestinationPage.java    |   20 ++++++++++++++------
 .../egit/ui/internal/clone/GitCloneWizard.java     |    3 ++-
 .../src/org/spearce/egit/ui/uitext.properties      |    2 +-
 4 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 9150832..189b769 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -170,7 +170,7 @@ public class UIText extends NLS {
 	public static String CloneDestinationPage_browseButton;
 
 	/** */
-	public static String CloneDestinationPage_errorExists;
+	public static String CloneDestinationPage_errorNotEmptyDir;
 
 	/** */
 	public static String Decorator_failedLazyLoading;
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
index a445cf5..97f166a 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
@@ -221,17 +221,17 @@ class CloneDestinationPage extends WizardPage {
 			setPageComplete(false);
 			return;
 		}
-		final File dstFile = new File(dstpath);
-		if (dstFile.exists()) {
-			setErrorMessage(NLS.bind(UIText.CloneDestinationPage_errorExists,
-					dstFile.getName()));
+		final File absoluteFile = new File(dstpath).getAbsoluteFile();
+		if (!isEmptyDir(absoluteFile)) {
+			setErrorMessage(NLS.bind(UIText.CloneDestinationPage_errorNotEmptyDir,
+					absoluteFile.getPath()));
 			setPageComplete(false);
 			return;
 		}
-		final File absoluteFile = dstFile.getAbsoluteFile();
+
 		if (!canCreateSubdir(absoluteFile.getParentFile())) {
 			setErrorMessage(NLS.bind(UIText.GitCloneWizard_errorCannotCreate,
-					dstFile.getPath()));
+					absoluteFile.getPath()));
 			setPageComplete(false);
 			return;
 		}
@@ -252,6 +252,14 @@ class CloneDestinationPage extends WizardPage {
 		setPageComplete(true);
 	}
 
+	private static boolean isEmptyDir(final File dir) {
+		if (!dir.exists())
+			return true;
+		if (!dir.isDirectory())
+			return false;
+		return dir.listFiles().length == 0;
+	}
+
 	// this is actually just an optimistic heuristic - should be named
 	// isThereHopeThatCanCreateSubdir() as probably there is no 100% reliable
 	// way to check that in Java for Windows
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
index efcf57f..10b2cd4 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
@@ -66,7 +66,8 @@ public class GitCloneWizard extends Wizard implements IImportWizard {
 		final String branch = cloneDestination.getInitialBranch();
 		final String remoteName = cloneDestination.getRemote();
 
-		if (!workdir.mkdirs()) {
+		workdir.mkdirs();
+		if (!workdir.isDirectory()) {
 			final String errorMessage = NLS.bind(
 					UIText.GitCloneWizard_errorCannotCreate, workdir.getPath());
 			ErrorDialog.openError(getShell(), getWindowTitle(),
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index 420b610..f46e183 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -73,7 +73,7 @@ CloneDestinationPage_promptInitialBranch=Initial branch
 CloneDestinationPage_promptRemoteName=Remote name
 CloneDestinationPage_fieldRequired={0} is required.
 CloneDestinationPage_browseButton=Browse
-CloneDestinationPage_errorExists={0} already exists.
+CloneDestinationPage_errorNotEmptyDir={0} is not an empty directory.
 
 Decorator_failedLazyLoading=Resource decorator failed to load tree contents on demand.
 QuickDiff_failedLoading=Quick diff failed to obtain file data.
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 24/31] Clone wizard: force dir to suggested path only if repo selection change
  2008-08-17 20:44                                             ` [EGIT PATCH 23/31] Allow selecting empty dir " Marek Zawirski
@ 2008-08-17 20:44                                               ` Marek Zawirski
  2008-08-17 20:44                                                 ` [EGIT PATCH 25/31] Create ListRemoteOperation for listing remote repo branches Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

When user moves backward in wizard from destination page to source
branch selection, then moves forward, we shouldn't overwrite his/her
directory selection in destination page.
Let's do it only when repository selection changes.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../ui/internal/clone/CloneDestinationPage.java    |   16 ++++++++++------
 1 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
index 97f166a..508781d 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/CloneDestinationPage.java
@@ -278,15 +278,19 @@ class CloneDestinationPage extends WizardPage {
 			checkPage();
 			return;
 		}
-		validatedRepoSelection = sourcePage.getSelection();
+		
+		if (!sourcePage.selectionEquals(validatedRepoSelection)) {
+			validatedRepoSelection = sourcePage.getSelection();
+			// update repo-related selection only if it changed
+			final String n = getSuggestedName();
+			setDescription(NLS.bind(UIText.CloneDestinationPage_description, n));
+			directoryText.setText(new File(ResourcesPlugin.getWorkspace()
+					.getRoot().getRawLocation().toFile(), n).getAbsolutePath());
+		}
+		
 		validatedSelectedBranches = branchPage.getSelectedBranches();
 		validatedHEAD = branchPage.getHEAD();
 
-		final String n = getSuggestedName();
-		setDescription(NLS.bind(UIText.CloneDestinationPage_description, n));
-		directoryText.setText(new File(ResourcesPlugin.getWorkspace().getRoot()
-				.getRawLocation().toFile(), n).getAbsolutePath());
-
 		initialBranch.removeAll();
 		final Ref head = branchPage.getHEAD();
 		int newix = 0;
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 25/31] Create ListRemoteOperation for listing remote repo branches
  2008-08-17 20:44                                               ` [EGIT PATCH 24/31] Clone wizard: force dir to suggested path only if repo selection change Marek Zawirski
@ 2008-08-17 20:44                                                 ` Marek Zawirski
  2008-08-17 20:44                                                   ` [EGIT PATCH 26/31] Make Clone's SourceBranchPage more user-friendly Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

This code is a common task, so it's now bundled in this operation
with simple progress monitor information.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/egit/core/CoreText.java        |    3 +
 .../src/org/spearce/egit/core/coretext.properties  |    2 +
 .../spearce/egit/core/op/ListRemoteOperation.java  |  104 ++++++++++++++++++++
 3 files changed, 109 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/op/ListRemoteOperation.java

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/CoreText.java b/org.spearce.egit.core/src/org/spearce/egit/core/CoreText.java
index 8fbda4a..5974a5f 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/CoreText.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/CoreText.java
@@ -95,6 +95,9 @@ public class CoreText extends NLS {
 	/** */
 	public static String CloneOperation_title;
 
+	/** */
+	public static String ListRemoteOperation_title;
+
 	static {
 		final Class c = CoreText.class;
 		initializeMessages(c.getPackage().getName() + ".coretext", c);
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/coretext.properties b/org.spearce.egit.core/src/org/spearce/egit/core/coretext.properties
index 3b3c229..c412161 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/coretext.properties
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/coretext.properties
@@ -55,3 +55,5 @@ CheckpointJob_writingTrees=modified trees
 CheckpointJob_failed=Failed to write modified objects.
 
 CloneOperation_title=Cloning from {0}
+
+ListRemoteOperation_title=Getting remote branches information
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/op/ListRemoteOperation.java b/org.spearce.egit.core/src/org/spearce/egit/core/op/ListRemoteOperation.java
new file mode 100644
index 0000000..d3f777a
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/op/ListRemoteOperation.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.op;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.spearce.egit.core.CoreText;
+import org.spearce.jgit.errors.NotSupportedException;
+import org.spearce.jgit.errors.TransportException;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.transport.Connection;
+import org.spearce.jgit.transport.Transport;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * Operation of listing remote repository advertised refs.
+ */
+public class ListRemoteOperation implements IRunnableWithProgress {
+	private final Repository localDb;
+
+	private final URIish uri;
+
+	private Map<String, Ref> remoteRefsMap;
+
+	/**
+	 * Create listing operation for specified local repository (needed by
+	 * transport) and remote repository URI.
+	 * 
+	 * @param localDb
+	 *            local repository (needed for transport) where fetch would
+	 *            occur.
+	 * @param uri
+	 *            URI of remote repository to list.
+	 */
+	public ListRemoteOperation(final Repository localDb, final URIish uri) {
+		this.localDb = localDb;
+		this.uri = uri;
+	}
+
+	/**
+	 * @return collection of refs advertised by remote side.
+	 * @throws IllegalStateException
+	 *             if error occurred during earlier remote refs listing.
+	 */
+	public Collection<Ref> getRemoteRefs() {
+		checkState();
+		return remoteRefsMap.values();
+	}
+
+	/**
+	 * @param refName
+	 *            remote ref name to search for.
+	 * @return ref with specified refName or null if not found.
+	 * @throws IllegalStateException
+	 *             if error occurred during earlier remote refs listing.
+	 */
+	public Ref getRemoteRef(final String refName) {
+		checkState();
+		return remoteRefsMap.get(refName);
+	}
+
+	public void run(IProgressMonitor pm) throws InvocationTargetException,
+			InterruptedException {
+		Transport transport = null;
+		Connection connection = null;
+		try {
+			transport = Transport.open(localDb, uri);
+
+			if (pm != null)
+				pm.beginTask(CoreText.ListRemoteOperation_title,
+						IProgressMonitor.UNKNOWN);
+			connection = transport.openFetch();
+			remoteRefsMap = connection.getRefsMap();
+		} catch (NotSupportedException e) {
+			throw new InvocationTargetException(e);
+		} catch (TransportException e) {
+			throw new InvocationTargetException(e);
+		} finally {
+			if (connection != null)
+				connection.close();
+			if (transport != null)
+				transport.close();
+			if (pm != null)
+				pm.done();
+		}
+	}
+
+	private void checkState() {
+		if (remoteRefsMap == null)
+			throw new IllegalStateException(
+					"Error occurred during remote repo listing, no refs available");
+	}
+}
\ No newline at end of file
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 26/31] Make Clone's SourceBranchPage more user-friendly
  2008-08-17 20:44                                                 ` [EGIT PATCH 25/31] Create ListRemoteOperation for listing remote repo branches Marek Zawirski
@ 2008-08-17 20:44                                                   ` Marek Zawirski
  2008-08-17 20:44                                                     ` [EGIT PATCH 27/31] Add few EPL Eclipse icons Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

-Show page description and progress monitor so user get some feedback
what's happening when we're listing remote refs (which may take some
time... sometimes).
-Don't display error during connection to remote repo.
-Set page error before showing error dialog (avoid flashing over Our
Dear User eyers).
-Enable/disable select all and unselect all buttons in response to
current selections.
-Use ListRemoteOperation instead of our own anonymous class. This
introduces some progress information. It also remove unnecessary
(wrong?) code for cancellation handling and always close Transport (even
if fetch is not supported by Transport).

Perhaps-making-too-big-commit...

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/egit/ui/UIText.java            |    9 +-
 .../egit/ui/internal/clone/GitCloneWizard.java     |    1 +
 .../egit/ui/internal/clone/SourceBranchPage.java   |  134 +++++++++-----------
 .../src/org/spearce/egit/ui/uitext.properties      |    3 +-
 4 files changed, 67 insertions(+), 80 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 189b769..9b290f3 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -119,6 +119,9 @@ public class UIText extends NLS {
 	public static String SourceBranchPage_title;
 
 	/** */
+	public static String SourceBranchPage_description;
+
+	/** */
 	public static String SourceBranchPage_branchList;
 
 	/** */
@@ -134,15 +137,15 @@ public class UIText extends NLS {
 	public static String SourceBranchPage_transportError;
 
 	/** */
-	public static String SourceBranchPage_interrupted;
-
-	/** */
 	public static String SourceBranchPage_cannotListBranches;
 
 	/** */
 	public static String SourceBranchPage_remoteListingCancelled;
 
 	/** */
+	public static String SourceBranchPage_cannotCreateTemp;
+
+	/** */
 	public static String CloneDestinationPage_title;
 
 	/** */
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
index 10b2cd4..a69dc52 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/GitCloneWizard.java
@@ -44,6 +44,7 @@ public class GitCloneWizard extends Wizard implements IImportWizard {
 	public void init(IWorkbench arg0, IStructuredSelection arg1) {
 		setWindowTitle(UIText.GitCloneWizard_title);
 		setDefaultPageImageDescriptor(UIIcons.WIZBAN_IMPORT_REPO);
+		setNeedsProgressMonitor(true);
 		cloneSource = new RepositorySelectionPage(true);
 		validSource = new SourceBranchPage(cloneSource);
 		cloneDestination = new CloneDestinationPage(cloneSource, validSource);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java
index 7710612..030419d 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/clone/SourceBranchPage.java
@@ -10,20 +10,16 @@
 package org.spearce.egit.ui.internal.clone;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
-import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.dialogs.ErrorDialog;
-import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionAdapter;
@@ -36,6 +32,7 @@ import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableItem;
+import org.spearce.egit.core.op.ListRemoteOperation;
 import org.spearce.egit.ui.Activator;
 import org.spearce.egit.ui.UIText;
 import org.spearce.egit.ui.internal.components.BaseWizardPage;
@@ -45,8 +42,7 @@ import org.spearce.egit.ui.internal.components.SelectionChangeListener;
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.Ref;
 import org.spearce.jgit.lib.Repository;
-import org.spearce.jgit.transport.FetchConnection;
-import org.spearce.jgit.transport.Transport;
+import org.spearce.jgit.transport.URIish;
 
 class SourceBranchPage extends BaseWizardPage {
 	private final RepositorySelectionPage sourcePage;
@@ -69,7 +65,8 @@ class SourceBranchPage extends BaseWizardPage {
 		super(SourceBranchPage.class.getName());
 		sourcePage = sp;
 		setTitle(UIText.SourceBranchPage_title);
-
+		setDescription(UIText.SourceBranchPage_description);
+		
 		sourcePage.addSelectionListener(new SelectionChangeListener() {
 			public void selectionChanged() {
 				if (!sourcePage.selectionEquals(validatedRepoSelection))
@@ -134,10 +131,10 @@ class SourceBranchPage extends BaseWizardPage {
 
 		final Composite bPanel = new Composite(panel, SWT.NONE);
 		bPanel.setLayout(new RowLayout());
-		Button b;
-		b = new Button(bPanel, SWT.PUSH);
-		b.setText(UIText.SourceBranchPage_selectAll);
-		b.addSelectionListener(new SelectionAdapter() {
+		final Button selectB;
+		selectB = new Button(bPanel, SWT.PUSH);
+		selectB.setText(UIText.SourceBranchPage_selectAll);
+		selectB.addSelectionListener(new SelectionAdapter() {
 			public void widgetSelected(final SelectionEvent e) {
 				for (int i = 0; i < refsTable.getItemCount(); i++)
 					refsTable.getItem(i).setChecked(true);
@@ -147,9 +144,9 @@ class SourceBranchPage extends BaseWizardPage {
 				checkPage();
 			}
 		});
-		b = new Button(bPanel, SWT.PUSH);
-		b.setText(UIText.SourceBranchPage_selectNone);
-		b.addSelectionListener(new SelectionAdapter() {
+		final Button unselectB = new Button(bPanel, SWT.PUSH);
+		unselectB.setText(UIText.SourceBranchPage_selectNone);
+		unselectB.addSelectionListener(new SelectionAdapter() {
 			public void widgetSelected(final SelectionEvent e) {
 				for (int i = 0; i < refsTable.getItemCount(); i++)
 					refsTable.getItem(i).setChecked(false);
@@ -160,6 +157,13 @@ class SourceBranchPage extends BaseWizardPage {
 		});
 		bPanel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
 
+		addSelectionListener(new SelectionChangeListener() {
+			public void selectionChanged() {
+				selectB.setEnabled(selectedRefs.size() != availableRefs.size());
+				unselectB.setEnabled(selectedRefs.size() != 0);
+			}
+		});
+
 		setControl(panel);
 		checkPage();
 	}
@@ -210,7 +214,8 @@ class SourceBranchPage extends BaseWizardPage {
 		availableRefs.clear();
 		selectedRefs.clear();
 		refsTable.removeAll();
-		checkPage();
+		setPageComplete(false);
+		setErrorMessage(null);
 		label.getDisplay().asyncExec(new Runnable() {
 			public void run() {
 				revalidateImpl(newRepoSelection);
@@ -222,73 +227,50 @@ class SourceBranchPage extends BaseWizardPage {
 		if (label.isDisposed() || !isCurrentPage())
 			return;
 
+		final ListRemoteOperation listRemoteOp; 
 		try {
-			getContainer().run(true, true, new IRunnableWithProgress() {
-				public void run(final IProgressMonitor pm)
-						throws InvocationTargetException, InterruptedException {
-					final IProgressMonitor monitor;
-					if (pm == null)
-						monitor = new NullProgressMonitor();
-					else
-						monitor = pm;
-					try {
-						final Repository db = new Repository(new File("/tmp"));
-						final Transport tn = Transport.open(db,
-								newRepoSelection.getURI());
-						final Collection<Ref> adv;
-						final FetchConnection fn = tn.openFetch();
-						try {
-							adv = fn.getRefs();
-						} finally {
-							fn.close();
-							tn.close();
-						}
-
-						final Ref idHEAD = fn.getRef(Constants.HEAD);
-						head = null;
-						for (final Ref r : adv) {
-							final String n = r.getName();
-							if (!n.startsWith(Constants.HEADS_PREFIX + "/"))
-								continue;
-							availableRefs.add(r);
-							if (idHEAD == null || head != null)
-								continue;
-							if (r.getObjectId().equals(idHEAD.getObjectId()))
-								head = r;
-						}
-						Collections.sort(availableRefs, new Comparator<Ref>() {
-							public int compare(final Ref o1, final Ref o2) {
-								return o1.getName().compareTo(o2.getName());
-							}
-						});
-						if (idHEAD != null && head == null) {
-							head = idHEAD;
-							availableRefs.add(0, idHEAD);
-						}
-					} catch (Exception err) {
-						throw new InvocationTargetException(err);
-					}
-					monitor.done();
-				}
-			});
+			final URIish uri = newRepoSelection.getURI();
+			final Repository db = new Repository(new File("/tmp"));
+			listRemoteOp = new ListRemoteOperation(db, uri);
+			getContainer().run(true, true, listRemoteOp);
 		} catch (InvocationTargetException e) {
 			Throwable why = e.getCause();
-			if ((why instanceof OperationCanceledException)) {
-				transportError(UIText.SourceBranchPage_remoteListingCancelled);
-				return;
-			} else {
-				ErrorDialog.openError(getShell(),
-						UIText.SourceBranchPage_transportError,
-						UIText.SourceBranchPage_cannotListBranches, new Status(
-								IStatus.ERROR, Activator.getPluginId(), 0, why
-										.getMessage(), why.getCause()));
-				transportError(why.getMessage());
-			}
+			transportError(why.getMessage());
+			ErrorDialog.openError(getShell(),
+					UIText.SourceBranchPage_transportError,
+					UIText.SourceBranchPage_cannotListBranches, new Status(
+							IStatus.ERROR, Activator.getPluginId(), 0, why
+									.getMessage(), why.getCause()));
+			return;
+		} catch (IOException e) {
+			transportError(UIText.SourceBranchPage_cannotCreateTemp);
 			return;
 		} catch (InterruptedException e) {
-			transportError(UIText.SourceBranchPage_interrupted);
+			transportError(UIText.SourceBranchPage_remoteListingCancelled);
 			return;
 		}
+		
+		final Ref idHEAD = listRemoteOp.getRemoteRef(Constants.HEAD);
+		head = null;
+		for (final Ref r : listRemoteOp.getRemoteRefs()) {
+			final String n = r.getName();
+			if (!n.startsWith(Constants.HEADS_PREFIX + "/"))
+				continue;
+			availableRefs.add(r);
+			if (idHEAD == null || head != null)
+				continue;
+			if (r.getObjectId().equals(idHEAD.getObjectId()))
+				head = r;
+		}
+		Collections.sort(availableRefs, new Comparator<Ref>() {
+			public int compare(final Ref o1, final Ref o2) {
+				return o1.getName().compareTo(o2.getName());
+			}
+		});
+		if (idHEAD != null && head == null) {
+			head = idHEAD;
+			availableRefs.add(0, idHEAD);
+		}
 
 		validatedRepoSelection = newRepoSelection;
 		for (final Ref r : availableRefs) {
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index f46e183..55f6c87 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -54,14 +54,15 @@ RepositorySelectionPage_fileNotFound={0} does not exist.
 RepositorySelectionPage_internalError=Internal error; consult Eclipse error log.
 
 SourceBranchPage_title=Source Git Repository
+SourceBranchPage_description=Select branches to clone from remote repository.
 SourceBranchPage_branchList=Branches of {0}:
 SourceBranchPage_selectAll=Select All
 SourceBranchPage_selectNone=Deselect All
 SourceBranchPage_errorBranchRequired=At least one branch must be selected.
 SourceBranchPage_transportError=Transport Error
 SourceBranchPage_cannotListBranches=Cannot list the available branches.
-SourceBranchPage_interrupted=Connection attempt interrupted.
 SourceBranchPage_remoteListingCancelled=Operation cancelled
+SourceBranchPage_cannotCreateTemp=Couldn't create temporary repository.
 
 
 CloneDestinationPage_title=Local Destination
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 27/31] Add few EPL Eclipse icons
  2008-08-17 20:44                                                   ` [EGIT PATCH 26/31] Make Clone's SourceBranchPage more user-friendly Marek Zawirski
@ 2008-08-17 20:44                                                     ` Marek Zawirski
  2008-08-17 20:44                                                       ` [EGIT PATCH 28/31] Checkbox images/screenshots Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

These are icons describing common taks, borrowed from official Eclipse
icons set. Some of them can be accessed through Eclipse 3.4 API, but we
still should try to be compatible with 3.3 version.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 org.spearce.egit.ui/icons/elcl16/add.gif           |  Bin 0 -> 318 bytes
 org.spearce.egit.ui/icons/elcl16/clear.gif         |  Bin 0 -> 595 bytes
 org.spearce.egit.ui/icons/elcl16/delete.gif        |  Bin 0 -> 351 bytes
 org.spearce.egit.ui/icons/elcl16/trash.gif         |  Bin 0 -> 590 bytes
 .../src/org/spearce/egit/ui/UIIcons.java           |   12 ++++++++++++
 5 files changed, 12 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit.ui/icons/elcl16/add.gif
 create mode 100644 org.spearce.egit.ui/icons/elcl16/clear.gif
 create mode 100644 org.spearce.egit.ui/icons/elcl16/delete.gif
 create mode 100644 org.spearce.egit.ui/icons/elcl16/trash.gif

diff --git a/org.spearce.egit.ui/icons/elcl16/add.gif b/org.spearce.egit.ui/icons/elcl16/add.gif
new file mode 100644
index 0000000000000000000000000000000000000000..252d7ebcb8c74d6e5de66ef0eb8856622a0e9d89
GIT binary patch
literal 318
zcmZ?wbhEHb6krfwxXQp_S{7na6>d=(Ze15;Qy1mX8t2lT<l2+$3M9LeU3-$eCZ~H%
z&hVd=6EG_$WMN_G!orY+1rf_jLl+gstf`FOP#wFbvUf{x@0O5hJ42`K3|nv@V&Q>^
zHOHgY9FJamJZkN+sI|wVk6ueSb}ixP)r4bLfn?%^+sU^crQUj&`rv8W|Ns9PC;*B-
zSr{1@v>9|jW`O*}z!rUAYJrE2RKG`JYNRKhwpQrnor{(Ph`sVEnyR2Kc;c~(o$WdP
zMb0Wl6K^dQ-0hgs;<SKI<H^q-;x25QTwKyJ($ZWU?Cc(_l2WWuEE3|(QH)cWq8R2f
LL@ik0$Y2cszSC!^

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/icons/elcl16/clear.gif b/org.spearce.egit.ui/icons/elcl16/clear.gif
new file mode 100644
index 0000000000000000000000000000000000000000..af30a42f83d2f91801206463e1f4601a747dd38e
GIT binary patch
literal 595
zcmZ?wbhEHb6krfwc*el+;Qr&!Uw{Aj`RCh@zn{MT*?;oxyHCG9ef|CO*PkChf49xr
z*E9dn%Iz0#-hcJ>)9(*oeotO@Y}T67i#DHMvE%ZZJy-8O|Ni97&*$%czWwy;^S9r3
zo`1jp^2fthKi+)${p!QdZ$ExNd-wC(&%YnO{{Hms_tzi4zyJI_W5&cyo7UdHe|Of*
z{<*XJmMv^wG_UL6rjotu%lB?5-?_GG%gWmI%NmdGpZDg~qvKl(k8UnLx~2Hg#?qZ@
zs#Y&<d2pit%BGAfn=)_ht+~1>v%S5&t*x!DuCAn{q`0`asHmv0urNPAKQAvYB_$;}
zIXNyaE;cqcGBPqWG&DFkI4CG6FfcG6Ai&?>-_OtQ|Ns9C0}2#>vM@3*gfi%WTmgy`
z2KEgNp{C6(&7tib?WP<(y|$*E9cHRboKu+W?9C^J>Z%9|aC6C9+u2x5)MuAemSmLN
zAgMReQb|NuXu%?WgNcR`5^M(!OBhcylH0yhfQMJnWTKUV_?0Uxu3jz-u0F1z+G3{`
zuzEW<Gq^Z<hw8|@d(Z6Z>)`F=drwRH^dde6KTp?5HjDwPt!%ua0$DEtSh$2VJr-3M
iJ8pc$DH7Bta<eUkOWnnFMTW7`(G$lfhcYoTSOWlzmhQX&

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/icons/elcl16/delete.gif b/org.spearce.egit.ui/icons/elcl16/delete.gif
new file mode 100644
index 0000000000000000000000000000000000000000..b6922ac11cf64e16a15cf2976cdaa1e40118abed
GIT binary patch
literal 351
zcmZ?wbhEHb6krfwxXQqA+}QL>Vab=`lFvmYpNmUxXz4%DF}bf}d|cDunVH>FGrOY-
z+D{EEKZPcINzVS9ob?$<req(LP(LQAaa2<Mn1arykc6YcsvqrxKG+9-@`(885pgB8
z<o=u$r(!+N<;9%}cRLl~b~3{4YDdk<0Ovc)7MygqJLztJ!rlJ3t>uHn=&y~n$8D`1
z2l;<(sQc1T_xQ+>|Ns9p5DgT6vM@3*7&7R9bb$QCz*ci$MuCTpRKNR-^bncK1eb^v
zqDfrti^MpzbSB1VUt0b}aql73M;xvKD|}WtK1fk5iV!?;_@hVR1O_f%UNKGv4?_+f
zX(=fl4i6boi78XK>s@7pr?3jKN=S;#aFP{YATA>-vrJN4*4aRuZH4n{cJUaejf<UD
N@9a2m(9n^=8UUYcd3yi=

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/icons/elcl16/trash.gif b/org.spearce.egit.ui/icons/elcl16/trash.gif
new file mode 100644
index 0000000000000000000000000000000000000000..bf961b3e00675b71052fc1798198d0e0f31d24cc
GIT binary patch
literal 590
zcmd6kJ8u&~6h<eGiK8SY27+V>1W9<<!2x!INyc#!46&U}c-f>$X*!oHsx4P*fkrfp
zkdUUJ*m5MATV9D~=q}xqQ*MR)7pRQ=FZjx%Qyl3#IqmYs`^&7v{xD|Uc&KP7F;pgq
zp(k)4ai~m~(&B}xhMGsZ6+<9Uq60fF4U`vYJ~QEBW@4miai9VL<)PxS9w-z*60I^W
z(k2E{NYzlThf;t^$(2LtMKaP@jif@uW2p?KWWi-Tid#Z*Ap<#uQ<*doI%k{<q=<Ez
z8U!-NSplg+tqH*zv{OP7lqLX+=!`a+G6K?Q!8oMMP=>~W&5X^^X5hv!nM~M!u#La@
zImXV}_N-<02`l`F&yytoF+Uoy`Grd>SC*ER^9w8~tUlTc-u8ln-PJ<Eip90u^&4yD
ztHp1L-#GM-j{L)hFZk&j-hanWjvxPQpYdLwpS5>{_pIZ6_d0D))b^XTSFdZ${hFve
zcv^Y!vQmFi5u0}(ZXI+tH@3IL{oD8M>^|>?V=veLG=FQyv9G_9(Tb>-T5K-=dHGsy
T*s6DqlP~LgA8!8kORV`1ElJDZ

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
index edeffaa..ba14df3 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
@@ -47,6 +47,14 @@ public class UIIcons {
 	public static final ImageDescriptor ELCL16_AUTHOR;
 	/** Committer icon */
 	public static final ImageDescriptor ELCL16_COMMITTER;
+	/** Delete icon */
+	public static final ImageDescriptor ELCL16_DELETE;
+	/** Add icon */
+	public static final ImageDescriptor ELCL16_ADD;
+	/** Trash icon */
+	public static final ImageDescriptor ELCL16_TRASH;
+	/** Clear icon */
+	public static final ImageDescriptor ELCL16_CLEAR;
 
 	/** Import Wizard banner */
 	public static final ImageDescriptor WIZBAN_IMPORT_REPO;
@@ -68,6 +76,10 @@ public class UIIcons {
 		ELCL16_COMMENTS = map("elcl16/comment.gif");
 		ELCL16_AUTHOR = map("elcl16/author.gif");
 		ELCL16_COMMITTER = map("elcl16/committer.gif");
+		ELCL16_DELETE = map("elcl16/delete.gif");
+		ELCL16_ADD = map("elcl16/add.gif");
+		ELCL16_TRASH = map("elcl16/trash.gif");
+		ELCL16_CLEAR = map("elcl16/clear.gif");
 	}
 
 	private static ImageDescriptor map(final String icon) {
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 28/31] Checkbox images/screenshots
  2008-08-17 20:44                                                     ` [EGIT PATCH 27/31] Add few EPL Eclipse icons Marek Zawirski
@ 2008-08-17 20:44                                                       ` Marek Zawirski
  2008-08-17 20:44                                                         ` [EGIT PATCH 29/31] Universal GUI for specifications edition: RefSpecPanel and related Marek Zawirski
  2008-08-19 18:24                                                         ` [EGIT PATCH 28/31] Checkbox images/screenshots Robin Rosenberg
  0 siblings, 2 replies; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Screenshots of checkboxes in various states, made at QT with Plastik
style. These images may be used as workaround in cases when we can
only display images and handle buttons pressed, but can't display Button
directly.

I'm somewhat uncertain about license issues regarding these images.
Recently, I realized that they are LGPL licensed perhaps. Is it possible
to license that small stuff? If so, we may have to replace them by some
other images or create ours.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../icons/checkboxes/disabled_checked.gif          |  Bin 0 -> 166 bytes
 .../icons/checkboxes/disabled_unchecked.gif        |  Bin 0 -> 125 bytes
 .../icons/checkboxes/enabled_checked.gif           |  Bin 0 -> 166 bytes
 .../icons/checkboxes/enabled_unchecked.gif         |  Bin 0 -> 157 bytes
 .../src/org/spearce/egit/ui/UIIcons.java           |   13 +++++++++++++
 5 files changed, 13 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit.ui/icons/checkboxes/disabled_checked.gif
 create mode 100644 org.spearce.egit.ui/icons/checkboxes/disabled_unchecked.gif
 create mode 100644 org.spearce.egit.ui/icons/checkboxes/enabled_checked.gif
 create mode 100644 org.spearce.egit.ui/icons/checkboxes/enabled_unchecked.gif

diff --git a/org.spearce.egit.ui/icons/checkboxes/disabled_checked.gif b/org.spearce.egit.ui/icons/checkboxes/disabled_checked.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6b86dc94de2bb83490c2f506fa76855e11b7b838
GIT binary patch
literal 166
zcmZ?wbhEHb<YnMxc+A1DcHP=z$B&&meeT@(bI+eYfB*VD5Pbjq{XY(%_)pNeC^fMp
zHASI3vm`^o-P1RKLGdRGBNqb)gAM}_fDC3}u?uMM)SjUJy7rmmfrOp~FBly&8ZsYl
wRFv9vVB@Kk9=FTwF10b`7-=vhDkuen`7m)zj=mVtWXE}9&9|hNTuKbq0JEx6@Bjb+

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/icons/checkboxes/disabled_unchecked.gif b/org.spearce.egit.ui/icons/checkboxes/disabled_unchecked.gif
new file mode 100644
index 0000000000000000000000000000000000000000..4b20c7eddc78f300565f520e2661abcc4c081122
GIT binary patch
literal 125
zcmZ?wbhEHb<YnMxIK;-VcHP=@r_Viq{`~#x_dxLd^Y{N?p!iSFxhOTUBsE2$JhLQ2
z!QIn0fI;ym3nLc;JA)1b5P%F|VAhq`aA$$)smL%zi|p0EAH;rR4DaY&YANLWdLQ@i
Zb07b3ZN2-b;lY20Ifou{s4y^C0|0zaIU@i7

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/icons/checkboxes/enabled_checked.gif b/org.spearce.egit.ui/icons/checkboxes/enabled_checked.gif
new file mode 100644
index 0000000000000000000000000000000000000000..f25b4ae6b6f55585e2ecfeb3e4ab3ca430ec2c7a
GIT binary patch
literal 166
zcmZ?wbhEHb<YnMxc+A6KXkggWH)YDSDSLPAy?6iK^T*GD;Qj0OK=A$ZcOdxx`#&;J
z{3qyKl$uzQnxasiS(2gP?&%xAp!k!8k&A(!L5BedKn63g*cG&RW>3(0eO67lVL{`e
zM~oSqi@LVAWz48JHCu}#_IOiMHRtR%4<3EwSuXOZm8Zt3Lt~|ZV#NB0Lq`wEx-c+U
F0|3l^QBME>

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/icons/checkboxes/enabled_unchecked.gif b/org.spearce.egit.ui/icons/checkboxes/enabled_unchecked.gif
new file mode 100644
index 0000000000000000000000000000000000000000..82138fec0ce82ffa1c946740956c62d919356c63
GIT binary patch
literal 157
zcmZ?wbhEHb<YnMxc+ADn(>G=BuD$o}-+TV}IS{;m{T>LufBp^x|9}6-1d9Izor_Wv
zOHxx5$}>wc6x=<10~i#4vM_Qn@H6Nz00GEg1{PC;22bq?TCdltJ@1^}qte-tT3O-o
odKcU8eTEBUD&~pLdnYW{TwuX+tK-B$mjgLgtm54!0*nmS0LAf6#sB~S

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
index ba14df3..fcc5707 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
@@ -55,6 +55,15 @@ public class UIIcons {
 	public static final ImageDescriptor ELCL16_TRASH;
 	/** Clear icon */
 	public static final ImageDescriptor ELCL16_CLEAR;
+	
+	/** Enabled, checked, checkbox image */
+	public static final ImageDescriptor CHECKBOX_ENABLED_CHECKED;
+	/** Enabled, unchecked, checkbox image */
+	public static final ImageDescriptor CHECKBOX_ENABLED_UNCHECKED;
+	/** Disabled, checked, checkbox image */
+	public static final ImageDescriptor CHECKBOX_DISABLED_CHECKED;
+	/** Disabled, unchecked, checkbox image */
+	public static final ImageDescriptor CHECKBOX_DISABLED_UNCHECKED;
 
 	/** Import Wizard banner */
 	public static final ImageDescriptor WIZBAN_IMPORT_REPO;
@@ -80,6 +89,10 @@ public class UIIcons {
 		ELCL16_ADD = map("elcl16/add.gif");
 		ELCL16_TRASH = map("elcl16/trash.gif");
 		ELCL16_CLEAR = map("elcl16/clear.gif");
+		CHECKBOX_ENABLED_CHECKED = map("checkboxes/enabled_checked.gif");
+		CHECKBOX_ENABLED_UNCHECKED = map("checkboxes/enabled_unchecked.gif");
+		CHECKBOX_DISABLED_CHECKED = map("checkboxes/disabled_checked.gif");
+		CHECKBOX_DISABLED_UNCHECKED = map("checkboxes/disabled_unchecked.gif");
 	}
 
 	private static ImageDescriptor map(final String icon) {
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 29/31] Universal GUI for specifications edition: RefSpecPanel and related
  2008-08-17 20:44                                                       ` [EGIT PATCH 28/31] Checkbox images/screenshots Marek Zawirski
@ 2008-08-17 20:44                                                         ` Marek Zawirski
  2008-08-17 20:44                                                           ` [EGIT PATCH 30/31] Add PushOperation to plugin Marek Zawirski
  2008-08-19 18:24                                                         ` [EGIT PATCH 28/31] Checkbox images/screenshots Robin Rosenberg
  1 sibling, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

RefSpecPanel provides universal GUI (Control) for editing list of RefSpec
specifications for both push or fetch, depending on panel configuration.

It is intended to allow user easily edit specifications, supporting user
with rich content assistant and giving feedback with error information as
soon as possible. Component uses editable specifications table and panels
for easy creation of new specifications basing on typical push/fetch
schemes (like branch update, deletion, all branches update, saved
configuration etc.).

Possibly there are still some places when this panel usability could be
improved by some UI-engineer. I hope it shouldn't be very hard and panel
core and API should resist such changes. Keyboard handling could be
improved perhaps, some bugs are still open for MacOS, with one critical
reported to bugzilla.

RefSpecPage class is fetch/push WizardPage using RefSpecPanel extensively.

Beside of RefSpecPanel class this commit introduces some components needed
to build this one. As these components as potentially reusable, they are
put in components package and declared as public.

Influenced-by-smart-comments-of: Shawn O. Pearce <spearce@spearce.org>
Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/egit/ui/UIText.java            |  207 +++
 .../components/CenteredImageLabelProvider.java     |   52 +
 .../internal/components/CheckboxLabelProvider.java |  138 ++
 .../internal/components/ClickableCellEditor.java   |   68 +
 .../internal/components/ComboLabelingSupport.java  |   77 +
 .../ui/internal/components/RefContentProposal.java |  140 ++
 .../egit/ui/internal/components/RefSpecPage.java   |  237 +++
 .../egit/ui/internal/components/RefSpecPanel.java  | 1823 ++++++++++++++++++++
 .../src/org/spearce/egit/ui/uitext.properties      |   71 +
 9 files changed, 2813 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CenteredImageLabelProvider.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CheckboxLabelProvider.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ClickableCellEditor.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ComboLabelingSupport.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefContentProposal.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPanel.java

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 9b290f3..ff2b541 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -176,6 +176,213 @@ public class UIText extends NLS {
 	public static String CloneDestinationPage_errorNotEmptyDir;
 
 	/** */
+	public static String RefSpecPanel_refChooseSome;
+
+	/** */
+	public static String RefSpecPanel_refChooseSomeWildcard;
+
+	/** */
+	public static String RefSpecPanel_clickToChange;
+
+	/** */
+	public static String RefSpecPanel_columnDst;
+
+	/** */
+	public static String RefSpecPanel_columnForce;
+
+	/** */
+	public static String RefSpecPanel_columnMode;
+
+	/** */
+	public static String RefSpecPanel_columnRemove;
+
+	/** */
+	public static String RefSpecPanel_columnSrc;
+
+	/** */
+	public static String RefSpecPanel_creationButton;
+
+	/** */
+	public static String RefSpecPanel_creationButtonDescription;
+
+	/** */
+	public static String RefSpecPanel_creationDst;
+
+	/** */
+	public static String RefSpecPanel_creationGroup;
+
+	/** */
+	public static String RefSpecPanel_creationSrc;
+
+	/** */
+	public static String RefSpecPanel_deletionButton;
+
+	/** */
+	public static String RefSpecPanel_deletionButtonDescription;
+
+	/** */
+	public static String RefSpecPanel_deletionGroup;
+
+	/** */
+	public static String RefSpecPanel_deletionRef;
+
+	/** */
+	public static String RefSpecPanel_dstDeletionDescription;
+
+	/** */
+	public static String RefSpecPanel_dstFetchDescription;
+
+	/** */
+	public static String RefSpecPanel_dstPushDescription;
+
+	/** */
+	public static String RefSpecPanel_errorRemoteConfigDescription;
+
+	/** */
+	public static String RefSpecPanel_errorRemoteConfigTitle;
+
+	/** */
+	public static String RefSpecPanel_fetch;
+
+	/** */
+	public static String RefSpecPanel_srcFetchDescription;
+
+	/** */
+	public static String RefSpecPanel_forceAll;
+
+	/** */
+	public static String RefSpecPanel_forceAllDescription;
+
+	/** */
+	public static String RefSpecPanel_forceDeleteDescription;
+
+	/** */
+	public static String RefSpecPanel_forceFalseDescription;
+
+	/** */
+	public static String RefSpecPanel_forceTrueDescription;
+
+	/** */
+	public static String RefSpecPanel_modeDelete;
+
+	/** */
+	public static String RefSpecPanel_modeDeleteDescription;
+
+	/** */
+	public static String RefSpecPanel_modeUpdate;
+
+	/** */
+	public static String RefSpecPanel_modeUpdateDescription;
+
+	/** */
+	public static String RefSpecPanel_predefinedAll;
+
+	/** */
+	public static String RefSpecPanel_predefinedAllDescription;
+
+	/** */
+	public static String RefSpecPanel_predefinedConfigured;
+
+	/** */
+	public static String RefSpecPanel_predefinedConfiguredDescription;
+
+	/** */
+	public static String RefSpecPanel_predefinedGroup;
+
+	/** */
+	public static String RefSpecPanel_predefinedTags;
+
+	/** */
+	public static String RefSpecPanel_predefinedTagsDescription;
+
+	/** */
+	public static String RefSpecPanel_push;
+
+	/** */
+	public static String RefSpecPanel_srcPushDescription;
+
+	/** */
+	public static String RefSpecPanel_removeAll;
+
+	/** */
+	public static String RefSpecPanel_removeAllDescription;
+
+	/** */
+	public static String RefSpecPanel_removeDescription;
+
+	/** */
+	public static String RefSpecPanel_specifications;
+
+	/** */
+	public static String RefSpecPanel_srcDeleteDescription;
+
+	/** */
+	public static String RefSpecPanel_validationDstInvalidExpression;
+
+	/** */
+	public static String RefSpecPanel_validationDstRequired;
+
+	/** */
+	public static String RefSpecPanel_validationRefDeleteRequired;
+
+	/** */
+	public static String RefSpecPanel_validationRefDeleteWildcard;
+
+	/** */
+	public static String RefSpecPanel_validationRefInvalidExpression;
+
+	/** */
+	public static String RefSpecPanel_validationRefInvalidLocal;
+
+	/** */
+	public static String RefSpecPanel_validationRefNonExistingRemote;
+
+	/** */
+	public static String RefSpecPanel_validationRefNonExistingRemoteDelete;
+
+	/** */
+	public static String RefSpecPanel_validationRefNonMatchingLocal;
+
+	/** */
+	public static String RefSpecPanel_validationRefNonMatchingRemote;
+
+	/** */
+	public static String RefSpecPanel_validationSpecificationsOverlappingDestination;
+
+	/** */
+	public static String RefSpecPanel_validationSrcUpdateRequired;
+
+	/** */
+	public static String RefSpecPanel_validationWildcardInconsistent;
+
+	/** */
+	public static String RefSpecPage_descriptionFetch;
+
+	/** */
+	public static String RefSpecPage_descriptionPush;
+
+	/** */
+	public static String RefSpecPage_errorDontMatchSrc;
+
+	/** */
+	public static String RefSpecPage_errorTransportDialogMessage;
+
+	/** */
+	public static String RefSpecPage_errorTransportDialogTitle;
+
+	/** */
+	public static String RefSpecPage_operationCancelled;
+
+	/** */
+	public static String RefSpecPage_saveSpecifications;
+
+	/** */
+	public static String RefSpecPage_titleFetch;
+
+	/** */
+	public static String RefSpecPage_titlePush;
+
+	/** */
 	public static String Decorator_failedLazyLoading;
 
 	/** */
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CenteredImageLabelProvider.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CenteredImageLabelProvider.java
new file mode 100644
index 0000000..8752a60
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CenteredImageLabelProvider.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import org.eclipse.jface.viewers.OwnerDrawLabelProvider;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.TableItem;
+
+/**
+ * Label provider displaying image centered.
+ * <p>
+ * This implementation is actually workaround for lacking SWT/JFace features.
+ * Code is based on official snippet found on Internet.
+ */
+// FIXME: doesn't work on Mac OS X 10.5 / Eclipse 3.3
+public abstract class CenteredImageLabelProvider extends OwnerDrawLabelProvider {
+	/**
+	 * @param element
+	 *            element to provide label for.
+	 * @return image for provided element.
+	 */
+	protected abstract Image getImage(final Object element);
+
+	@Override
+	protected void measure(Event event, Object element) {
+		// empty
+	}
+
+	@Override
+	protected void paint(final Event event, final Object element) {
+		final Image image = getImage(element);
+		final Rectangle bounds = ((TableItem) event.item)
+				.getBounds(event.index);
+		final Rectangle imgBounds = image.getBounds();
+		bounds.width /= 2;
+		bounds.width -= imgBounds.width / 2;
+		bounds.height /= 2;
+		bounds.height -= imgBounds.height / 2;
+
+		final int x = bounds.width > 0 ? bounds.x + bounds.width : bounds.x;
+		final int y = bounds.height > 0 ? bounds.y + bounds.height : bounds.y;
+
+		event.gc.drawImage(image, x, y);
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CheckboxLabelProvider.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CheckboxLabelProvider.java
new file mode 100644
index 0000000..2222afc
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/CheckboxLabelProvider.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Control;
+import org.spearce.egit.ui.UIIcons;
+
+/**
+ * Label provider displaying native check boxes images for boolean values.
+ * Label-image is centralized.
+ * <p>
+ * Concrete implementations must provide object to boolean mapping.
+ * <p>
+ * This implementation is actually workaround for lacking features in
+ * TableViewer. It is based on (workaround) snippets&tricks found on Internet.
+ */
+public abstract class CheckboxLabelProvider extends CenteredImageLabelProvider {
+	private static Image createCheckboxImage(final Control control,
+			boolean checked, boolean enabled) {
+		// Temporary workaround?
+		if (enabled) {
+			if (checked)
+				return UIIcons.CHECKBOX_ENABLED_CHECKED.createImage();
+			return UIIcons.CHECKBOX_ENABLED_UNCHECKED.createImage();
+		}
+		if (checked)
+			return UIIcons.CHECKBOX_DISABLED_CHECKED.createImage();
+		return UIIcons.CHECKBOX_DISABLED_UNCHECKED.createImage();
+		
+		// FIXME: Shawn says that blinking shell caused by below code is very
+		// annoying...(at least on Mac) - anyone knows better workaround?
+		// final Shell s = new Shell(control.getShell(), SWT.NO_TRIM);
+		// // Hopefully no platform uses exactly this color because we'll make
+		// // it transparent in the image.
+		// final Color greenScreen = new Color(control.getDisplay(), 222, 223,
+		// 224);
+		//
+		// // otherwise we have a default gray color
+		// s.setBackground(greenScreen);
+		//
+		// final Button b = new Button(s, SWT.CHECK);
+		// b.setSelection(checked);
+		// b.setEnabled(enabled);
+		// b.setBackground(greenScreen);
+		//
+		// // otherwise an image is located in a corner
+		// b.setLocation(0, 0);
+		// final Point bSize = b.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+		// // otherwise an image is stretched by width
+		// bSize.x = Math.max(bSize.x, bSize.y);
+		// bSize.y = Math.max(bSize.x, bSize.y);
+		// b.setSize(bSize);
+		// s.setSize(bSize);
+		// s.open();
+		//
+		// final GC gc = new GC(b);
+		// final Image image = new Image(control.getShell().getDisplay(),
+		// bSize.x,
+		// bSize.y);
+		// gc.copyArea(image, 0, 0);
+		// gc.dispose();
+		// s.close();
+		//
+		// final ImageData imageData = image.getImageData();
+		// imageData.transparentPixel = imageData.palette.getPixel(greenScreen
+		// .getRGB());
+		// return new Image(control.getDisplay(), imageData);
+	}
+
+	private final Image imageCheckedEnabled;
+
+	private final Image imageUncheckedEnabled;
+
+	private final Image imageCheckedDisabled;
+
+	private final Image imageUncheckedDisabled;
+
+	/**
+	 * Create label provider for provided viewer.
+	 * 
+	 * @param control
+	 *            viewer where label provided is used.
+	 */
+	public CheckboxLabelProvider(final Control control) {
+		imageCheckedEnabled = createCheckboxImage(control, true, true);
+		imageUncheckedEnabled = createCheckboxImage(control, false, true);
+		imageCheckedDisabled = createCheckboxImage(control, true, false);
+		imageUncheckedDisabled = createCheckboxImage(control, false, false);
+	}
+
+	@Override
+	public void dispose() {
+		super.dispose();
+		imageCheckedEnabled.dispose();
+		imageUncheckedEnabled.dispose();
+		imageCheckedDisabled.dispose();
+		imageUncheckedDisabled.dispose();
+	}
+
+	@Override
+	protected Image getImage(final Object element) {
+		if (isEnabled(element)) {
+			if (isChecked(element))
+				return imageCheckedEnabled;
+			return imageUncheckedEnabled;
+		} else {
+			if (isChecked(element))
+				return imageCheckedDisabled;
+			return imageUncheckedDisabled;
+		}
+	}
+
+	/**
+	 * @param element
+	 *            element to provide label for.
+	 * @return true if checkbox label should be checked for this element, false
+	 *         otherwise.
+	 */
+	protected abstract boolean isChecked(Object element);
+
+	/**
+	 * Default implementation always return true.
+	 * 
+	 * @param element
+	 *            element to provide label for.
+	 * @return true if checkbox label should be enabled for this element, false
+	 *         otherwise.
+	 */
+	protected boolean isEnabled(final Object element) {
+		return true;
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ClickableCellEditor.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ClickableCellEditor.java
new file mode 100644
index 0000000..af4cb86
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ClickableCellEditor.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Table;
+
+/**
+ * Workaround class allowing usage of clickable element as a TableViewer cell,
+ * acting as button.
+ * <p>
+ * setValue method of EditingSupport is called on cell click, with this cell
+ * editor configured.
+ * 
+ */
+public class ClickableCellEditor extends CellEditor {
+
+	/**
+	 * Create cell editor for provided table.
+	 * 
+	 * @param table
+	 *            the parent table.
+	 */
+	public ClickableCellEditor(final Table table) {
+		super(table, SWT.NONE);
+	}
+
+	@Override
+	protected Control createControl(Composite parent) {
+		return null;
+	}
+
+	@Override
+	protected Object doGetValue() {
+		return null;
+	}
+
+	@Override
+	protected void doSetFocus() {
+		// nothing to do
+	}
+
+	@Override
+	protected void doSetValue(Object value) {
+		// nothing to do
+	}
+
+	@Override
+	public void activate() {
+		// just force setValue on editing support
+		fireApplyEditorValue();
+	}
+
+	public void activate(ColumnViewerEditorActivationEvent activationEvent) {
+		if (activationEvent.eventType != ColumnViewerEditorActivationEvent.TRAVERSAL) {
+			super.activate(activationEvent);
+		}
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ComboLabelingSupport.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ComboLabelingSupport.java
new file mode 100644
index 0000000..724c570
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/ComboLabelingSupport.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jface.fieldassist.IContentProposal;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Combo;
+
+/**
+ * Support class for Combo, extending its functionality to differentiate between
+ * item label and item content.
+ * <p>
+ * This implementation takes {@link IContentProposal} instances as data source.
+ */
+public class ComboLabelingSupport {
+	private final Combo combo;
+
+	private List<? extends IContentProposal> proposals;
+
+	/**
+	 * Installs labeling support on provided combo. setItems method of combo
+	 * shouldn't be called manually after that installation.
+	 * <p>
+	 * Support class is initialized with empty proposals list.
+	 * 
+	 * @param combo
+	 *            target combo to install on.
+	 * @param selectionListener
+	 *            listener that is notified when content is filled after label
+	 *            being clicked. May be null.
+	 */
+	public ComboLabelingSupport(final Combo combo,
+			final SelectionListener selectionListener) {
+		this.combo = combo;
+
+		combo.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				final int i = combo.getSelectionIndex();
+				if (i != -1 && i < proposals.size()) {
+					combo.setText(proposals.get(i).getContent());
+					if (selectionListener != null)
+						selectionListener.widgetSelected(e);
+				}
+			}
+		});
+		setProposals(Collections.<IContentProposal> emptyList());
+	}
+
+	/**
+	 * Sets input data for combo.
+	 * <p>
+	 * Proposals are set in provided order.
+	 * 
+	 * @param proposals
+	 *            model of input data.
+	 */
+	public void setProposals(final List<? extends IContentProposal> proposals) {
+		this.proposals = proposals;
+
+		final String[] itemsLabels = new String[proposals.size()];
+		int i = 0;
+		for (final IContentProposal p : proposals)
+			itemsLabels[i++] = p.getLabel();
+		combo.setItems(itemsLabels);
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefContentProposal.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefContentProposal.java
new file mode 100644
index 0000000..feeac26
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefContentProposal.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import java.io.IOException;
+import java.sql.Blob;
+
+import org.eclipse.jface.fieldassist.IContentProposal;
+import org.spearce.egit.ui.Activator;
+import org.spearce.jgit.lib.Commit;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.PersonIdent;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.Tag;
+import org.spearce.jgit.lib.Tree;
+
+/**
+ * Content proposal class for refs names, specifically Ref objects - name with
+ * optionally associated object id. This class can be used for Eclipse field
+ * assist as content proposal.
+ * <p>
+ * Content of this proposal is simply a ref name, but description and labels
+ * tries to be smarter - showing easier to read label for user (stripping
+ * prefixes) and information about pointed object if it exists locally.
+ */
+public class RefContentProposal implements IContentProposal {
+	private static final String PREFIXES[] = new String[] {
+			Constants.HEADS_PREFIX + "/", Constants.REMOTES_PREFIX + "/",
+			Constants.TAGS_PREFIX + "/" };
+
+	private static final String PREFIXES_DESCRIPTIONS[] = new String[] {
+			" [branch]", " [tracking branch]", " [tag]" };
+
+	private static void appendObjectSummary(final StringBuilder sb,
+			final String type, final PersonIdent author, final String message) {
+		sb.append(type + " by ");
+		sb.append(author.getName());
+		sb.append("\n");
+		sb.append(author.getWhen());
+		sb.append("\n\n");
+		final int newLine = message.indexOf('\n');
+		final int last = (newLine != -1 ? newLine : message.length());
+		sb.append(message.substring(0, last));
+	}
+
+	private final Repository db;
+
+	private final String refName;
+
+	private final ObjectId objectId;
+
+	/**
+	 * Create content proposal for specified ref.
+	 * 
+	 * @param repo
+	 *            repository for accessing information about objects. Could be a
+	 *            local repository even for remote objects.
+	 * @param ref
+	 *            ref being a content proposal. May have null or locally
+	 *            non-existent object id.
+	 */
+	public RefContentProposal(final Repository repo, final Ref ref) {
+		this(repo, ref.getName(), ref.getObjectId());
+	}
+
+	/**
+	 * Create content proposal for specified ref name and object id.
+	 * 
+	 * @param repo
+	 *            repository for accessing information about objects. Could be a
+	 *            local repository even for remote objects.
+	 * @param refName
+	 *            ref name being a content proposal.
+	 * @param objectId
+	 *            object being pointed by this ref name. May be null or locally
+	 *            non-existent object.
+	 */
+	public RefContentProposal(final Repository repo, final String refName,
+			final ObjectId objectId) {
+		this.db = repo;
+		this.refName = refName;
+		this.objectId = objectId;
+	}
+
+	public String getContent() {
+		return refName;
+	}
+
+	public int getCursorPosition() {
+		return refName.length();
+	}
+
+	public String getDescription() {
+		if (objectId == null)
+			return null;
+		final Object obj;
+		try {
+			obj = db.mapObject(objectId, refName);
+		} catch (IOException e) {
+			Activator.logError("Unable to read object " + objectId
+					+ " for content proposal assistance", e);
+			return null;
+		}
+
+		final StringBuilder sb = new StringBuilder();
+		sb.append(refName);
+		sb.append('\n');
+		sb.append(objectId.abbreviate(db));
+		sb.append(" - ");
+		if (obj instanceof Commit) {
+			final Commit c = ((Commit) obj);
+			appendObjectSummary(sb, "commit", c.getAuthor(), c.getMessage());
+		} else if (obj instanceof Tag) {
+			final Tag t = ((Tag) obj);
+			appendObjectSummary(sb, "tag", t.getAuthor(), t.getMessage());
+		} else if (obj instanceof Tree) {
+			sb.append("tree");
+		} else if (obj instanceof Blob) {
+			sb.append("blob");
+		} else
+			sb.append("locally unknown object");
+		return sb.toString();
+	}
+
+	public String getLabel() {
+		for (int i = 0; i < PREFIXES.length; i++)
+			if (refName.startsWith(PREFIXES[i]))
+				return refName.substring(PREFIXES[i].length())
+						+ PREFIXES_DESCRIPTIONS[i];
+		return refName;
+
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
new file mode 100644
index 0000000..4471e24
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.spearce.egit.core.op.ListRemoteOperation;
+import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIText;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.transport.RefSpec;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * This wizard page allows user easy selection of specifications for push or
+ * fetch (configurable).
+ * <p>
+ * Page is relying highly on {@link RefSpecPanel} component, see its description
+ * for details.
+ * <p>
+ * Page is designed to be successor of {@link RepositorySelectionPage} in
+ * wizard.
+ */
+public class RefSpecPage extends BaseWizardPage {
+
+	private final Repository local;
+
+	private final RepositorySelectionPage repoPage;
+
+	private final boolean pushPage;
+
+	private RepositorySelection validatedRepoSelection;
+
+	private RefSpecPanel specsPanel;
+
+	private Button saveButton;
+
+	private String transportError;
+
+	/**
+	 * Create specifications selection page for provided context.
+	 * 
+	 * @param local
+	 *            local repository.
+	 * @param pushPage
+	 *            true if this page is used for push specifications selection,
+	 *            false if it used for fetch specifications selection.
+	 * @param repoPage
+	 *            repository selection page - must be predecessor of this page
+	 *            in wizard.
+	 */
+	public RefSpecPage(final Repository local, final boolean pushPage,
+			final RepositorySelectionPage repoPage) {
+		super(RefSpecPage.class.getName());
+		this.local = local;
+		this.repoPage = repoPage;
+		this.pushPage = pushPage;
+		if (pushPage) {
+			setTitle(UIText.RefSpecPage_titlePush);
+			setDescription(UIText.RefSpecPage_descriptionPush);
+		} else {
+			setTitle(UIText.RefSpecPage_titleFetch);
+			setDescription(UIText.RefSpecPage_descriptionFetch);
+		}
+
+		repoPage.addSelectionListener(new SelectionChangeListener() {
+			public void selectionChanged() {
+				if (!repoPage.selectionEquals(validatedRepoSelection))
+					setPageComplete(false);
+				else
+					checkPage();
+			}
+		});
+	}
+
+	public void createControl(Composite parent) {
+		final Composite panel = new Composite(parent, SWT.NULL);
+		panel.setLayout(new GridLayout());
+
+		specsPanel = new RefSpecPanel(panel, pushPage);
+		specsPanel.getControl().setLayoutData(
+				new GridData(SWT.FILL, SWT.FILL, true, true));
+		specsPanel.addRefSpecTableListener(new SelectionChangeListener() {
+			public void selectionChanged() {
+				notifySelectionChanged();
+				checkPage();
+			}
+		});
+
+		saveButton = new Button(panel, SWT.CHECK);
+		saveButton.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, false));
+		saveButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				notifySelectionChanged();
+			}
+		});
+
+		setControl(panel);
+		notifySelectionChanged();
+		checkPage();
+	}
+
+	@Override
+	public void setVisible(final boolean visible) {
+		if (visible)
+			revalidate();
+		super.setVisible(visible);
+	}
+
+	/**
+	 * @return ref specifications as selected by user. Returned collection is a
+	 *         copy, so it may be modified by caller.
+	 */
+	public List<RefSpec> getRefSpecs() {
+		if (specsPanel == null)
+			return Collections.emptyList();
+		else
+			return new ArrayList<RefSpec>(specsPanel.getRefSpecs());
+	}
+
+	/**
+	 * @return true if user chosen to save selected specification in remote
+	 *         configuration, false otherwise.
+	 */
+	public boolean isSaveRequested() {
+		return saveButton.getSelection();
+	}
+
+	/**
+	 * Compare provided specifications to currently selected ones.
+	 * 
+	 * @param specs
+	 *            specifications to compare to. May be null.
+	 * @return true if provided specifications are equal to currently selected
+	 *         ones, false otherwise.
+	 */
+	public boolean specsSelectionEquals(final List<RefSpec> specs) {
+		return getRefSpecs().equals(specs);
+	}
+
+	private void revalidate() {
+		final RepositorySelection newRepoSelection = repoPage.getSelection();
+
+		if (repoPage.selectionEquals(validatedRepoSelection)) {
+			// nothing changed on previous page
+			checkPage();
+			return;
+		}
+
+		specsPanel.clearRefSpecs();
+		specsPanel.setEnable(false);
+		saveButton.setVisible(false);
+		saveButton.setSelection(false);
+		notifySelectionChanged();
+		validatedRepoSelection = null;
+		transportError = null;
+		getControl().getDisplay().asyncExec(new Runnable() {
+			public void run() {
+				revalidateImpl(newRepoSelection);
+			}
+		});
+	}
+
+	private void revalidateImpl(final RepositorySelection newRepoSelection) {
+		final ListRemoteOperation listRemotesOp;
+		try {
+			final URIish uri;
+			uri = newRepoSelection.getURI();
+			listRemotesOp = new ListRemoteOperation(local, uri);
+			getContainer().run(true, true, listRemotesOp);
+		} catch (InvocationTargetException e) {
+			final Throwable cause = e.getCause();
+			transportError(cause.getMessage());
+			ErrorDialog.openError(getShell(), UIText.RefSpecPage_errorTransportDialogTitle,
+					UIText.RefSpecPage_errorTransportDialogMessage, new Status(
+							IStatus.ERROR, Activator.getPluginId(), 0, cause
+									.getMessage(), cause));
+			return;
+		} catch (InterruptedException e) {
+			transportError(UIText.RefSpecPage_operationCancelled);
+			return;
+		}
+
+		this.validatedRepoSelection = newRepoSelection;
+		final String remoteName = validatedRepoSelection.getConfigName();
+		specsPanel.setAssistanceData(local, listRemotesOp.getRemoteRefs(),
+				remoteName);
+		if (newRepoSelection.isConfigSelected()) {
+			saveButton.setVisible(true);
+			saveButton.setText(NLS.bind(UIText.RefSpecPage_saveSpecifications,
+					remoteName));
+			saveButton.pack();
+		}
+		checkPage();
+	}
+
+	private void transportError(final String message) {
+		transportError = message;
+		checkPage();
+	}
+
+	private void checkPage() {
+		if (transportError != null) {
+			setErrorMessage(transportError);
+			setPageComplete(false);
+			return;
+		}
+		if (!specsPanel.isEmpty() && specsPanel.isValid()
+				&& !specsPanel.isMatchingAnyRefs()) {
+			setErrorMessage(UIText.RefSpecPage_errorDontMatchSrc);
+			setPageComplete(false);
+			return;
+		}
+		setErrorMessage(specsPanel.getErrorMessage());
+		setPageComplete(!specsPanel.isEmpty() && specsPanel.isValid());
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPanel.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPanel.java
new file mode 100644
index 0000000..caef4d2
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPanel.java
@@ -0,0 +1,1823 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.components;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.fieldassist.ComboContentAdapter;
+import org.eclipse.jface.fieldassist.ContentProposalAdapter;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
+import org.eclipse.jface.fieldassist.IContentProposal;
+import org.eclipse.jface.fieldassist.IContentProposalListener;
+import org.eclipse.jface.fieldassist.IContentProposalProvider;
+import org.eclipse.jface.fieldassist.TextContentAdapter;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.resource.ImageRegistry;
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.CheckboxCellEditor;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.EditingSupport;
+import org.eclipse.jface.viewers.IElementComparer;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.TextCellEditor;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter;
+import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIIcons;
+import org.spearce.egit.ui.UIText;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.Ref.Storage;
+import org.spearce.jgit.transport.FetchConnection;
+import org.spearce.jgit.transport.RefSpec;
+import org.spearce.jgit.transport.RemoteConfig;
+import org.spearce.jgit.transport.Transport;
+
+/**
+ * This class provides universal panel for editing list of {@link RefSpec} -
+ * specifications for both push or fetch, depending on panel configuration.
+ * <p>
+ * It is intended to allow user easily edit specifications, supporting user with
+ * content assistant and giving feedback with error information as soon as
+ * possible. Component uses editable specifications table and panels for easy
+ * creation of new specifications basing on typical push/fetch schemes (like
+ * branch update, deletion, all branches update, saved configuration etc.).
+ * <p>
+ * The model of specifications list behind panel is accessible by public methods
+ * - giving both read and write access. Listener interface for handling changes
+ * in model is provided by {@link SelectionChangeListener}.
+ * <p>
+ * Typical class usage:
+ * 
+ * <pre>
+ * // create panel for editing push-specifications
+ * RefSpecPanel panel = new RefSpecPanel(parent, true);
+ * // register model listener
+ * panel.addRefSpecPanelListener(listener);
+ * 
+ * // provide information about local and remote refs
+ * panel.setRefsData(localRepo, remoteRefs, remoteName);
+ * 
+ * // get result data 
+ * List&lt;RefSpec&gt; result = panel.getRefSpecs();
+ * // further processing: push or save configuration...
+ * </pre>
+ * 
+ * @see SelectionChangeListener
+ */
+public class RefSpecPanel {
+	private static final String REMOTES_PREFIX_S = Constants.REMOTES_PREFIX + '/';
+
+	private static final String HEADS_PREFIX_S = Constants.HEADS_PREFIX + '/';
+
+	private static final String IMAGE_ADD = "ADD"; //$NON-NLS-1$
+
+	private static final String IMAGE_DELETE = "DELETE"; //$NON-NLS-1$
+
+	private static final String IMAGE_TRASH = "TRASH"; //$NON-NLS-1$
+
+	private static final String IMAGE_CLEAR = "CLEAR"; //$NON-NLS-1$
+
+	private static final int TABLE_PREFERRED_HEIGHT = 165;
+
+	private static final int TABLE_PREFERRED_WIDTH = 560;
+
+	private static final int COLUMN_MODE_WEIGHT = 23;
+
+	private static final int COLUMN_SRC_WEIGHT = 40;
+
+	private static final int COLUMN_DST_WEIGHT = 40;
+
+	private static final int COLUMN_FORCE_WEIGHT = 30;
+
+	private static final int COLUMN_REMOVE_WEIGHT = 20;
+
+	private static boolean isDeleteRefSpec(final Object element) {
+		return ((RefSpec) element).getSource() == null;
+	}
+
+	private static boolean isValidRefExpression(final String s) {
+		if (RefSpec.isWildcard(s)) {
+			// replace wildcard with some legal name just for checking
+			return Repository
+					.isValidRefName(s.substring(0, s.length() - 1) + 'X');
+		} else
+			return Repository.isValidRefName(s);
+	}
+
+	private static RefSpec setRefSpecSource(final RefSpec spec, final String src) {
+		final String dst;
+		if (RefSpec.isWildcard(src))
+			dst = wildcardSpecComponent(spec.getDestination());
+		else
+			dst = unwildcardSpecComponent(spec.getDestination(), src);
+		return spec.setSourceDestination(src, dst);
+	}
+
+	private static RefSpec setRefSpecDestination(final RefSpec spec,
+			final String dst) {
+		final String src;
+		if (RefSpec.isWildcard(dst))
+			src = wildcardSpecComponent(spec.getSource());
+		else
+			src = unwildcardSpecComponent(spec.getSource(), dst);
+		return spec.setSourceDestination(src, dst);
+	}
+
+	private static String wildcardSpecComponent(final String comp) {
+		final int i;
+		if (RefSpec.isWildcard(comp))
+			return comp;
+		if (comp == null || (i = comp.lastIndexOf('/')) == -1) {
+			// That's somewhat ugly. What better can we do here?
+			return UIText.RefSpecPanel_refChooseSomeWildcard;
+		}
+		return comp.substring(0, i + 1) + '*';
+	}
+
+	private static String unwildcardSpecComponent(final String comp,
+			final String other) {
+		if (!RefSpec.isWildcard(comp))
+			return comp;
+		if (other == null || other.length() == 0)
+			return ""; //$NON-NLS-1$
+		final int i = other.lastIndexOf('/');
+		return comp.substring(0, comp.length() - 1) + other.substring(i + 1);
+	}
+
+	private static List<RefContentProposal> createProposalsFilteredRemote(
+			final List<RefContentProposal> proposals) {
+		final List<RefContentProposal> result = new ArrayList<RefContentProposal>();
+		for (final RefContentProposal p : proposals) {
+			final String content = p.getContent();
+			if (content.equals(Constants.HEAD)
+					|| content.startsWith(HEADS_PREFIX_S))
+				result.add(p);
+		}
+		return result;
+	}
+
+	private static Image getDecorationImage(final String key) {
+		return FieldDecorationRegistry.getDefault().getFieldDecoration(key)
+				.getImage();
+	}
+
+	private static void setControlDecoration(final ControlDecoration control,
+			final String imageKey, final String description) {
+		control.setImage(getDecorationImage(imageKey));
+		control.setDescriptionText(description);
+		control.show();
+	}
+
+	private final List<RefSpec> specs = new ArrayList<RefSpec>();
+
+	private final Composite panel;
+
+	private TableViewer tableViewer;
+
+	private CellEditor modeCellEditor;
+
+	private CellEditor localRefCellEditor;
+
+	private CellEditor remoteRefCellEditor;
+
+	private CellEditor forceUpdateCellEditor;
+
+	private CellEditor removeSpecCellEditor;
+
+	private int srcColumnIndex;
+
+	private Button removeAllSpecButton;
+
+	private Button forceUpdateAllButton;
+
+	private Button creationButton;
+
+	private Button addConfiguredButton;
+
+	private Button addTagsButton;
+
+	private Button addBranchesButton;
+
+	private ControlDecoration creationSrcDecoration;
+
+	private ControlDecoration creationDstDecoration;
+
+	private ControlDecoration deleteRefDecoration;
+
+	private Combo creationSrcCombo;
+
+	private Combo creationDstCombo;
+
+	private Combo deleteRefCombo;
+
+	private Button deleteButton;
+
+	private Repository localDb;
+
+	private String remoteName;
+
+	private Set<String> localRefNames = Collections.emptySet();
+
+	private Set<String> remoteRefNames = Collections.emptySet();
+
+	private List<RefSpec> predefinedConfigured = Collections.emptyList();
+
+	private RefSpec predefinedBranches = null;
+
+	private final RefContentProposalProvider remoteProposalProvider;
+
+	private final RefContentProposalProvider localProposalProvider;
+
+	private ComboLabelingSupport creationSrcComboSupport;
+
+	private ComboLabelingSupport creationDstComboSupport;
+
+	private ComboLabelingSupport deleteRefComboSupport;
+
+	private final boolean pushSpecs;
+
+	private final List<SelectionChangeListener> listeners = new LinkedList<SelectionChangeListener>();
+
+	private final ImageRegistry imageRegistry;
+
+	private boolean matchingAnyRefs;
+
+	private RefSpec invalidSpec;
+
+	private RefSpec invalidSpecSameDst;
+
+	private String errorMessage;
+
+	private Color errorBackgroundColor;
+
+	private Color errorTextColor;
+
+	/**
+	 * Create a new panel and install it on a provided composite. Panel is
+	 * created either for editing push or fetch specifications - this setting
+	 * can't be changed later, after constructing object.
+	 * <p>
+	 * Panel is created with an empty model, with no provided assistant. It
+	 * can't be used by user until
+	 * {@link #setAssistanceData(Repository, Collection, String)} method is
+	 * called, and to this time is disabled.
+	 * 
+	 * @param parent
+	 *            parent control for panel.
+	 * @param pushSpecs
+	 *            true if panel is used for editing push specifications, false
+	 *            if panel is used for editing fetch specifications.
+	 */
+	public RefSpecPanel(final Composite parent, final boolean pushSpecs) {
+		this.pushSpecs = pushSpecs;
+		this.localProposalProvider = new RefContentProposalProvider(true);
+		this.remoteProposalProvider = new RefContentProposalProvider(false);
+		this.imageRegistry = new ImageRegistry(parent.getDisplay());
+
+		panel = new Composite(parent, SWT.NONE);
+		panel.setLayout(new GridLayout());
+
+		safeCreateResources();
+
+		createCreationPanel();
+		if (pushSpecs)
+			createDeleteCreationPanel();
+		createPredefinedCreationPanel();
+		createTableGroup();
+
+		addRefSpecTableListener(new SelectionChangeListener() {
+			public void selectionChanged() {
+				validateSpecs();
+			}
+		});
+		setEnable(false);
+	}
+
+	/**
+	 * Enable or disable panel controls.
+	 * 
+	 * @param enable
+	 *            true to enable panel, false to disable.
+	 */
+	public void setEnable(final boolean enable) {
+		getControl().setEnabled(enable);
+	}
+
+	/**
+	 * Set information needed for assisting user with entering data and
+	 * validating user input. This method automatically enables the panel.
+	 * 
+	 * @param localRepo
+	 *            local repository where specifications will be applied.
+	 * @param remoteRefs
+	 *            collection of remote refs as advertised by remote repository.
+	 *            Typically they are collected by {@link FetchConnection}
+	 *            implementation.
+	 * @param remoteName
+	 *            optional name for remote configuration, if edited
+	 *            specification list is related to this remote configuration.
+	 *            Can be null. When not null, panel is filled with default
+	 *            fetch/push specifications for this remote configuration.
+	 */
+	public void setAssistanceData(final Repository localRepo,
+			final Collection<Ref> remoteRefs, final String remoteName) {
+		this.localDb = localRepo;
+		this.remoteName = remoteName;
+
+		final List<RefContentProposal> remoteProposals = createContentProposals(
+				remoteRefs, null);
+		remoteProposalProvider.setProposals(remoteProposals);
+		remoteRefNames = new HashSet<String>();
+		for (final RefContentProposal p : remoteProposals)
+			remoteRefNames.add(p.getContent());
+
+		Ref HEAD = null;
+		try {
+			final ObjectId id = localDb.resolve(Constants.HEAD);
+			if (id != null)
+				HEAD = new Ref(Storage.LOOSE, Constants.HEAD, id);
+		} catch (IOException e) {
+			Activator.logError("Couldn't read HEAD from local repository", e); //$NON-NLS-1$
+		}
+		final List<RefContentProposal> localProposals = createContentProposals(
+				localDb.getAllRefs().values(), HEAD);
+		localProposalProvider.setProposals(localProposals);
+		localRefNames = new HashSet<String>();
+		for (final RefContentProposal ref : localProposals)
+			localRefNames.add(ref.getContent());
+
+		final List<RefContentProposal> localFilteredProposals = createProposalsFilteredLocal(localProposals);
+		final List<RefContentProposal> remoteFilteredProposals = createProposalsFilteredRemote(remoteProposals);
+
+		if (pushSpecs) {
+			creationSrcComboSupport.setProposals(localFilteredProposals);
+			creationDstComboSupport.setProposals(remoteFilteredProposals);
+		} else {
+			creationSrcComboSupport.setProposals(remoteFilteredProposals);
+			creationDstComboSupport.setProposals(localFilteredProposals);
+		}
+		validateCreationPanel();
+
+		if (pushSpecs) {
+			deleteRefComboSupport.setProposals(remoteFilteredProposals);
+			validateDeleteCreationPanel();
+		}
+
+		try {
+			final RemoteConfig rc = new RemoteConfig(localDb.getConfig(),
+					remoteName);
+			if (pushSpecs)
+				predefinedConfigured = rc.getPushRefSpecs();
+			else
+				predefinedConfigured = rc.getFetchRefSpecs();
+			for (final RefSpec spec : predefinedConfigured)
+				addRefSpec(spec);
+		} catch (URISyntaxException e) {
+			predefinedConfigured = null;
+			ErrorDialog.openError(panel.getShell(),
+					UIText.RefSpecPanel_errorRemoteConfigTitle,
+					UIText.RefSpecPanel_errorRemoteConfigDescription,
+					new Status(IStatus.ERROR, Activator.getPluginId(), 0, e
+							.getMessage(), e));
+		}
+		updateAddPredefinedButton(addConfiguredButton, predefinedConfigured);
+		if (pushSpecs)
+			predefinedBranches = Transport.REFSPEC_PUSH_ALL;
+		else
+			predefinedBranches = new RefSpec("refs/heads/*:refs/remotes/" //$NON-NLS-1$
+					+ remoteName + "/*"); //$NON-NLS-1$
+		updateAddPredefinedButton(addBranchesButton, predefinedBranches);
+		setEnable(true);
+	}
+
+	/**
+	 * @return underlying control for this panel.
+	 */
+	public Control getControl() {
+		return panel;
+	}
+
+	/**
+	 * Return current list of specifications of this panel.
+	 * <p>
+	 * This method should be called only from the UI thread.
+	 * 
+	 * @return unmodifiable view of specifications list as edited by user in
+	 *         this panel. Note that this view underlying model may change -
+	 *         create a copy if needed.
+	 */
+	public List<RefSpec> getRefSpecs() {
+		return Collections.unmodifiableList(specs);
+	}
+
+	/**
+	 * @return true if specifications list is empty, false otherwise.
+	 */
+	public boolean isEmpty() {
+		return getRefSpecs().isEmpty();
+	}
+
+	/**
+	 * @return true if specifications match any ref(s) in source repository -
+	 *         resolve to concrete ref updates, false otherwise. For non empty
+	 *         specifications list, false value is possible only in case of
+	 *         specifications with wildcards.
+	 */
+	public boolean isMatchingAnyRefs() {
+		return matchingAnyRefs;
+	}
+
+	/**
+	 * Add provided specification to this panel. Panel view is automatically
+	 * refreshed, model is revalidated.
+	 * <p>
+	 * Note that the same reference can't be added twice to the panel, while two
+	 * or more equals RefSpec (in terms of equals method) can be - likely
+	 * causing validation error.
+	 * <p>
+	 * This method should be called only from the UI thread.
+	 * 
+	 * @param spec
+	 *            specification to add.
+	 * @throws IllegalArgumentException
+	 *             if specification with same reference already exists in panel.
+	 */
+	public void addRefSpec(final RefSpec spec) {
+		final int i = indexOfSpec(spec);
+		if (i != -1)
+			throw new IllegalArgumentException("RefSpec " + spec //$NON-NLS-1$
+					+ " already exists."); //$NON-NLS-1$
+		specs.add(spec);
+		tableViewer.add(spec);
+		notifySpecsChanged();
+	}
+
+	/**
+	 * Remove provided specification from this panel. Panel view is
+	 * automatically refreshed, model is revalidated.
+	 * <p>
+	 * Provided specification must be equals with existing one in terms of
+	 * reference equality, not an equals method.
+	 * <p>
+	 * This method should be called only from the UI thread.
+	 * 
+	 * @param spec
+	 *            specification to remove.
+	 * @throws IllegalArgumentException
+	 *             if specification with this reference doesn't exist in this
+	 *             panel.
+	 */
+	public void removeRefSpec(final RefSpec spec) {
+		final int i = indexOfSpec(spec);
+		if (i == -1)
+			throw new IllegalArgumentException("RefSpec " + spec //$NON-NLS-1$
+					+ " not found."); //$NON-NLS-1$
+		specs.remove(i);
+		tableViewer.remove(spec);
+		notifySpecsChanged();
+	}
+
+	/**
+	 * Change some specification to the new one.
+	 * <p>
+	 * Old specification must exist in the panel, while new specification can't
+	 * exist before (both in terms of reference equality).
+	 * <p>
+	 * This method should be called only from the UI thread.
+	 * 
+	 * @param oldSpec
+	 *            specification to change. Can't be null.
+	 * @param newSpec
+	 *            new specification to override existing one. Can't be null.
+	 */
+	public void setRefSpec(final RefSpec oldSpec, final RefSpec newSpec) {
+		final int oldI = indexOfSpec(oldSpec);
+		if (oldI == -1)
+			throw new IllegalArgumentException("RefSpec " + oldSpec //$NON-NLS-1$
+					+ " not found."); //$NON-NLS-1$
+		final int newI = indexOfSpec(newSpec);
+		if (newI != -1)
+			throw new IllegalArgumentException("RefSpec " + newSpec //$NON-NLS-1$
+					+ " already exists."); //$NON-NLS-1$
+		specs.set(oldI, newSpec);
+
+		// have to refresh whole table as we are operating on immutable objects
+		// (this shouldn't be an issue)
+		tableViewer.refresh();
+		notifySpecsChanged();
+	}
+
+	/**
+	 * Clear all specifications from this panel.
+	 * <p>
+	 * This method should be called only from the UI thread.
+	 */
+	public void clearRefSpecs() {
+		final RefSpec toRemove[] = specs.toArray(new RefSpec[0]);
+		specs.clear();
+		tableViewer.remove(toRemove);
+		notifySpecsChanged();
+	}
+
+	/**
+	 * Add listener of changes in panel model.
+	 * <p>
+	 * Listeners are notified on events caused by both operations invoked by
+	 * external calls and user interaction. Listener method(s) is always called
+	 * from UI thread and shouln't perform long computations.
+	 * <p>
+	 * Order of adding listeners is significant. This method is not thread-safe.
+	 * Listeners should be set up before panel usage.
+	 * 
+	 * @param listener
+	 *            listener to add.
+	 */
+	public void addRefSpecTableListener(final SelectionChangeListener listener) {
+		listeners.add(listener);
+	}
+
+	/**
+	 * Get user-friendly error message regarding invalid specification.
+	 * 
+	 * @return user-readable information about invalid specification.
+	 */
+	public String getErrorMessage() {
+		return errorMessage;
+	}
+
+	/**
+	 * Return information about validity of specifications.
+	 * <p>
+	 * Specifications are considered valid if pushing/fetching (depending on
+	 * panel configuration) shouldn't cause any error except for
+	 * non-fast-forward or server-related errors complaint. I.e. specifications
+	 * destinations don't overlap and every specification is correctly
+	 * formulated, preferably none is referring to non-existing ref etc.
+	 * 
+	 * @return true if all specifications in panel are valid, false if at least
+	 *         one specification is invalid (in this case
+	 *         {@link #getErrorMessage()} gives detailed information for user).
+	 */
+	public boolean isValid() {
+		return errorMessage == null;
+	}
+
+	private int indexOfSpec(final RefSpec spec) {
+		int i;
+		for (i = 0; i < specs.size(); i++) {
+			// we have to compare references, not use List#indexOf,
+			// as equals is implemented in RefSpec
+			if (specs.get(i) == spec)
+				break;
+		}
+		if (i == specs.size())
+			return -1;
+		return i;
+	}
+
+	private void notifySpecsChanged() {
+		for (final SelectionChangeListener listener : listeners)
+			listener.selectionChanged();
+	}
+
+	private void safeCreateResources() {
+		imageRegistry.put(IMAGE_ADD, UIIcons.ELCL16_ADD);
+		imageRegistry.put(IMAGE_DELETE, UIIcons.ELCL16_DELETE);
+		imageRegistry.put(IMAGE_TRASH, UIIcons.ELCL16_TRASH);
+		imageRegistry.put(IMAGE_CLEAR, UIIcons.ELCL16_CLEAR);
+		errorBackgroundColor = new Color(panel.getDisplay(), 255, 150, 150);
+		errorTextColor = new Color(panel.getDisplay(), 255, 0, 0);
+
+		panel.addDisposeListener(new DisposeListener() {
+			public void widgetDisposed(DisposeEvent e) {
+				imageRegistry.dispose();
+				errorBackgroundColor.dispose();
+				errorTextColor.dispose();
+			}
+		});
+	}
+
+	private RefContentProposalProvider getRefsProposalProvider(
+			final boolean local) {
+		return (local ? localProposalProvider : remoteProposalProvider);
+	}
+
+	private void createCreationPanel() {
+		final Group creationPanel = new Group(panel, SWT.NONE);
+		creationPanel.setText(UIText.RefSpecPanel_creationGroup);
+		creationPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
+				false));
+		final GridLayout layout = new GridLayout();
+		layout.numColumns = 3;
+		layout.horizontalSpacing = 10;
+		layout.verticalSpacing = 2;
+		creationPanel.setLayout(layout);
+
+		new Label(creationPanel, SWT.NONE)
+				.setText(UIText.RefSpecPanel_creationSrc);
+		new Label(creationPanel, SWT.NONE)
+				.setText(UIText.RefSpecPanel_creationDst);
+		creationButton = new Button(creationPanel, SWT.PUSH);
+		creationButton.setLayoutData(new GridData(SWT.RIGHT, SWT.BOTTOM, false,
+				false, 1, 2));
+		creationButton.setImage(imageRegistry.get(IMAGE_ADD));
+		creationButton.setText(UIText.RefSpecPanel_creationButton);
+		creationButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				final String src = creationSrcCombo.getText();
+				final String dst = creationDstCombo.getText();
+				RefSpec spec = new RefSpec(src + ':' + dst);
+				addRefSpec(spec);
+				creationSrcCombo.setText(""); //$NON-NLS-1$
+				creationDstCombo.setText(""); //$NON-NLS-1$
+			}
+		});
+		creationButton.setToolTipText(NLS.bind(
+				UIText.RefSpecPanel_creationButtonDescription, typeString()));
+
+		creationSrcDecoration = createAssistedDecoratedCombo(creationPanel,
+				getRefsProposalProvider(pushSpecs),
+				new IContentProposalListener() {
+					public void proposalAccepted(IContentProposal proposal) {
+						tryAutoCompleteSrcToDst();
+					}
+				});
+		creationSrcCombo = (Combo) creationSrcDecoration.getControl();
+		creationSrcCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
+				false));
+		creationSrcCombo.addTraverseListener(new TraverseListener() {
+			public void keyTraversed(TraverseEvent e) {
+				// SWT.TRAVERSE_RETURN may be also reasonable here, but
+				// it can be confused with RETURN for content proposal
+				if (e.detail == SWT.TRAVERSE_TAB_NEXT)
+					tryAutoCompleteSrcToDst();
+			}
+		});
+		if (pushSpecs)
+			creationSrcCombo
+					.setToolTipText(UIText.RefSpecPanel_srcPushDescription);
+		else
+			creationSrcCombo
+					.setToolTipText(UIText.RefSpecPanel_srcFetchDescription);
+		creationSrcComboSupport = new ComboLabelingSupport(creationSrcCombo,
+				new SelectionAdapter() {
+					@Override
+					public void widgetSelected(SelectionEvent e) {
+						tryAutoCompleteSrcToDst();
+					}
+				});
+
+		creationDstDecoration = createAssistedDecoratedCombo(creationPanel,
+				getRefsProposalProvider(!pushSpecs),
+				new IContentProposalListener() {
+					public void proposalAccepted(IContentProposal proposal) {
+						tryAutoCompleteDstToSrc();
+					}
+				});
+		creationDstCombo = (Combo) creationDstDecoration.getControl();
+		creationDstCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
+				false));
+		creationDstCombo.addTraverseListener(new TraverseListener() {
+			public void keyTraversed(TraverseEvent e) {
+				// SWT.TRAVERSE_RETURN may be also reasonable here, but
+				// it can be confused with RETURN for content proposal
+				if (e.detail == SWT.TRAVERSE_TAB_NEXT)
+					tryAutoCompleteDstToSrc();
+			}
+		});
+		if (pushSpecs)
+			creationDstCombo
+					.setToolTipText(UIText.RefSpecPanel_dstPushDescription);
+		else
+			creationDstCombo
+					.setToolTipText(UIText.RefSpecPanel_dstFetchDescription);
+		creationDstComboSupport = new ComboLabelingSupport(creationDstCombo,
+				new SelectionAdapter() {
+					@Override
+					public void widgetSelected(SelectionEvent e) {
+						tryAutoCompleteDstToSrc();
+					}
+				});
+
+		validateCreationPanel();
+		final ModifyListener validator = new ModifyListener() {
+			public void modifyText(final ModifyEvent e) {
+				validateCreationPanel();
+			}
+		};
+		creationSrcCombo.addModifyListener(validator);
+		creationDstCombo.addModifyListener(validator);
+	}
+
+	private void createDeleteCreationPanel() {
+		final Group deletePanel = new Group(panel, SWT.NONE);
+		deletePanel.setText(UIText.RefSpecPanel_deletionGroup);
+		deletePanel
+				.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		final GridLayout layout = new GridLayout();
+		layout.numColumns = 3;
+		layout.horizontalSpacing = 10;
+		deletePanel.setLayout(layout);
+
+		final Label label = new Label(deletePanel, SWT.NONE);
+		label.setText(UIText.RefSpecPanel_deletionRef);
+		label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
+
+		deleteRefDecoration = createAssistedDecoratedCombo(deletePanel,
+				getRefsProposalProvider(false), null);
+		deleteRefCombo = (Combo) deleteRefDecoration.getControl();
+		deleteRefCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
+				false));
+		deleteRefCombo
+				.setToolTipText(UIText.RefSpecPanel_dstDeletionDescription);
+		deleteRefComboSupport = new ComboLabelingSupport(deleteRefCombo, null);
+
+		deleteButton = new Button(deletePanel, SWT.PUSH);
+		deleteButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false,
+				false));
+		deleteButton.setImage(imageRegistry.get(IMAGE_DELETE));
+		deleteButton.setText(UIText.RefSpecPanel_deletionButton);
+		deleteButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				RefSpec spec = new RefSpec(':' + deleteRefCombo.getText());
+				addRefSpec(spec);
+				deleteRefCombo.setText(""); //$NON-NLS-1$
+			}
+		});
+		deleteButton
+				.setToolTipText(UIText.RefSpecPanel_deletionButtonDescription);
+		validateDeleteCreationPanel();
+
+		deleteRefCombo.addModifyListener(new ModifyListener() {
+			public void modifyText(final ModifyEvent e) {
+				validateDeleteCreationPanel();
+			}
+		});
+	}
+
+	private void createPredefinedCreationPanel() {
+		final Group predefinedPanel = new Group(panel, SWT.NONE);
+		predefinedPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
+				false));
+		predefinedPanel.setText(UIText.RefSpecPanel_predefinedGroup);
+		final GridLayout layout = new GridLayout();
+		layout.numColumns = 3;
+		predefinedPanel.setLayout(layout);
+
+		addConfiguredButton = new Button(predefinedPanel, SWT.PUSH);
+		addConfiguredButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER,
+				true, false));
+		addConfiguredButton.setText(NLS.bind(
+				UIText.RefSpecPanel_predefinedConfigured, typeString()));
+		addConfiguredButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				addPredefinedRefSpecs(predefinedConfigured);
+			}
+		});
+		addConfiguredButton
+				.setToolTipText(UIText.RefSpecPanel_predefinedConfiguredDescription);
+		updateAddPredefinedButton(addConfiguredButton, predefinedConfigured);
+
+		addBranchesButton = new Button(predefinedPanel, SWT.PUSH);
+		addBranchesButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER,
+				true, false));
+		addBranchesButton.setText(UIText.RefSpecPanel_predefinedAll);
+		addBranchesButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				addPredefinedRefSpecs(predefinedBranches);
+			}
+		});
+		addBranchesButton
+				.setToolTipText(UIText.RefSpecPanel_predefinedAllDescription);
+		updateAddPredefinedButton(addBranchesButton, predefinedBranches);
+
+		addTagsButton = new Button(predefinedPanel, SWT.PUSH);
+		addTagsButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
+				false));
+		addTagsButton.setText(UIText.RefSpecPanel_predefinedTags);
+		addTagsButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				addPredefinedRefSpecs(Transport.REFSPEC_TAGS);
+			}
+		});
+		addTagsButton
+				.setToolTipText(UIText.RefSpecPanel_predefinedTagsDescription);
+		updateAddPredefinedButton(addTagsButton, Transport.REFSPEC_TAGS);
+
+		addRefSpecTableListener(new SelectionChangeListener() {
+			public void selectionChanged() {
+				updateAddPredefinedButton(addConfiguredButton,
+						predefinedConfigured);
+				updateAddPredefinedButton(addBranchesButton, predefinedBranches);
+				updateAddPredefinedButton(addTagsButton, Transport.REFSPEC_TAGS);
+			}
+		});
+	}
+
+	private ControlDecoration createAssistedDecoratedCombo(
+			final Composite parent,
+			final IContentProposalProvider proposalProvider,
+			final IContentProposalListener listener) {
+		// FIXME: VERY ANNOYING! reported as 243991 in eclipse bugzilla
+		// when typing, pressing arrow-down key opens combo box drop-down
+		// instead of moving within autocompletion list (Mac 10.4&10.5, Eclipse
+		// 3.4)
+		final Combo combo = new Combo(parent, SWT.DROP_DOWN);
+		final ControlDecoration decoration = new ControlDecoration(combo,
+				SWT.BOTTOM | SWT.LEFT);
+		final ContentAssistCommandAdapter proposal = new ContentAssistCommandAdapter(
+				combo, new ComboContentAdapter(), proposalProvider, null, null,
+				true);
+		proposal
+				.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
+		if (listener != null)
+			proposal.addContentProposalListener(listener);
+		return decoration;
+	}
+
+	private void createTableGroup() {
+		final Group tableGroup = new Group(panel, SWT.NONE);
+		tableGroup.setText(NLS.bind(UIText.RefSpecPanel_specifications,
+				typeString()));
+		tableGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+		tableGroup.setLayout(new GridLayout());
+
+		createTable(tableGroup);
+		createSpecsButtonsPanel(tableGroup);
+	}
+
+	private void createTable(final Group tableGroup) {
+		final Composite tablePanel = new Composite(tableGroup, SWT.NONE);
+		final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
+		layoutData.heightHint = TABLE_PREFERRED_HEIGHT;
+		layoutData.widthHint = TABLE_PREFERRED_WIDTH;
+		tablePanel.setLayoutData(layoutData);
+
+		tableViewer = new TableViewer(tablePanel, SWT.FULL_SELECTION
+				| SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
+		ColumnViewerToolTipSupport.enableFor(tableViewer);
+		final Table table = tableViewer.getTable();
+		table.setLinesVisible(true);
+		table.setHeaderVisible(true);
+
+		createTableColumns(tablePanel);
+		createCellEditors(table);
+
+		tableViewer.setContentProvider(new IStructuredContentProvider() {
+			public Object[] getElements(final Object inputElement) {
+				return ((List) inputElement).toArray();
+			}
+
+			public void dispose() {
+				// nothing to dispose
+			}
+
+			public void inputChanged(Viewer viewer, Object oldInput,
+					Object newInput) {
+				// input is hard coded
+			}
+		});
+		tableViewer.setInput(specs);
+
+		tableViewer.setComparer(new IElementComparer() {
+			public boolean equals(Object a, Object b) {
+				// need that as viewers are not designed to support 2 equals
+				// object, while we have RefSpec#equals implemented
+				return a == b;
+			}
+
+			public int hashCode(Object element) {
+				return element.hashCode();
+			}
+		});
+	}
+
+	private void createTableColumns(final Composite tablePanel) {
+		final TableColumnLayout columnLayout = new TableColumnLayout();
+		tablePanel.setLayout(columnLayout);
+
+		createDummyColumn(columnLayout);
+		createModeColumn(columnLayout);
+		createSrcColumn(columnLayout);
+		createDstColumn(columnLayout);
+		createForceColumn(columnLayout);
+		createRemoveColumn(columnLayout);
+	}
+
+	private void createDummyColumn(final TableColumnLayout columnLayout) {
+		final TableViewerColumn viewerColumn = new TableViewerColumn(
+				tableViewer, SWT.LEFT);
+		final TableColumn column = viewerColumn.getColumn();
+		columnLayout.setColumnData(column, new ColumnWeightData(0, 0, false));
+		viewerColumn.setLabelProvider(new ColumnLabelProvider());
+		// FIXME: first cell is left aligned on Mac OS X 10.4, Eclipse 3.4
+	}
+
+	private void createModeColumn(final TableColumnLayout columnLayout) {
+		final TableViewerColumn column = createColumn(columnLayout,
+				UIText.RefSpecPanel_columnMode, COLUMN_MODE_WEIGHT, SWT.CENTER);
+		column.setLabelProvider(new ColumnLabelProvider() {
+			@Override
+			public String getText(final Object element) {
+				return (isDeleteRefSpec(element) ? UIText.RefSpecPanel_modeDelete
+						: UIText.RefSpecPanel_modeUpdate);
+			}
+
+			@Override
+			public Image getImage(Object element) {
+				return (isDeleteRefSpec(element) ? imageRegistry
+						.get(IMAGE_DELETE) : imageRegistry.get(IMAGE_ADD));
+			}
+
+			@Override
+			public String getToolTipText(Object element) {
+				if (isDeleteRefSpec(element))
+					return UIText.RefSpecPanel_modeDeleteDescription + '\n'
+							+ UIText.RefSpecPanel_clickToChange;
+				return UIText.RefSpecPanel_modeUpdateDescription + '\n'
+						+ UIText.RefSpecPanel_clickToChange;
+			}
+		});
+		column.setEditingSupport(new EditingSupport(tableViewer) {
+			@Override
+			protected boolean canEdit(final Object element) {
+				return true;
+			}
+
+			@Override
+			protected CellEditor getCellEditor(final Object element) {
+				return modeCellEditor;
+			}
+
+			@Override
+			protected Object getValue(final Object element) {
+				return isDeleteRefSpec(element);
+			}
+
+			@Override
+			protected void setValue(final Object element, final Object value) {
+				final RefSpec oldSpec = (RefSpec) element;
+				final RefSpec newSpec;
+				if ((Boolean) value) {
+					newSpec = setRefSpecSource(oldSpec, null);
+					setRefSpec(oldSpec, newSpec);
+				} else {
+					newSpec = setRefSpecSource(oldSpec,
+							UIText.RefSpecPanel_refChooseSome);
+					setRefSpec(oldSpec, newSpec);
+					tableViewer.getControl().getDisplay().asyncExec(
+							new Runnable() {
+								public void run() {
+									tableViewer.editElement(newSpec,
+											srcColumnIndex);
+								}
+							});
+				}
+			}
+		});
+	}
+
+	private void createSrcColumn(final TableColumnLayout columnLayout) {
+		final TableViewerColumn column = createColumn(columnLayout,
+				UIText.RefSpecPanel_columnSrc, COLUMN_SRC_WEIGHT, SWT.LEFT);
+		column.setLabelProvider(new ColumnLabelProvider() {
+			@Override
+			public String getText(final Object element) {
+				return ((RefSpec) element).getSource();
+			}
+
+			@Override
+			public String getToolTipText(Object element) {
+				if (isInvalidSpec(element))
+					return errorMessage;
+				if (isDeleteRefSpec(element))
+					return UIText.RefSpecPanel_srcDeleteDescription;
+				if (pushSpecs)
+					return UIText.RefSpecPanel_srcPushDescription;
+				return UIText.RefSpecPanel_srcFetchDescription;
+			}
+
+			@Override
+			public Color getBackground(final Object element) {
+				if (isInvalidSpec(element))
+					return errorBackgroundColor;
+				return null;
+			}
+
+			@Override
+			public Color getToolTipForegroundColor(Object element) {
+				if (isInvalidSpec(element))
+					return errorTextColor;
+				return null;
+			}
+		});
+		column.setEditingSupport(new EditingSupport(tableViewer) {
+			@Override
+			protected boolean canEdit(final Object element) {
+				return !isDeleteRefSpec(element);
+			}
+
+			@Override
+			protected CellEditor getCellEditor(final Object element) {
+				return (pushSpecs ? localRefCellEditor : remoteRefCellEditor);
+			}
+
+			@Override
+			protected Object getValue(final Object element) {
+				return ((RefSpec) element).getSource();
+			}
+
+			@Override
+			protected void setValue(final Object element, final Object value) {
+				if (value == null || ((String) value).length() == 0
+						|| ObjectId.zeroId().toString().equals(value)) {
+					// Ignore empty strings or null objects - do not set them in
+					// model.User won't loose any information if we just fall
+					// back to the old value.
+					// If user want to delete ref, let change the mode.
+					return;
+				}
+
+				final RefSpec oldSpec = (RefSpec) element;
+				final RefSpec newSpec = setRefSpecSource(oldSpec,
+						(String) value);
+				setRefSpec(oldSpec, newSpec);
+			}
+		});
+
+		// find index of this column - for later usage
+		final TableColumn[] columns = tableViewer.getTable().getColumns();
+		for (srcColumnIndex = 0; srcColumnIndex < columns.length; srcColumnIndex++)
+			if (columns[srcColumnIndex] == column.getColumn())
+				break;
+
+	}
+
+	private void createDstColumn(final TableColumnLayout columnLayout) {
+		final TableViewerColumn column = createColumn(columnLayout,
+				UIText.RefSpecPanel_columnDst, COLUMN_DST_WEIGHT, SWT.LEFT);
+		column.setLabelProvider(new ColumnLabelProvider() {
+			@Override
+			public String getText(final Object element) {
+				return ((RefSpec) element).getDestination();
+			}
+
+			@Override
+			public String getToolTipText(Object element) {
+				if (isInvalidSpec(element))
+					return errorMessage;
+				if (isDeleteRefSpec(element))
+					return UIText.RefSpecPanel_dstDeletionDescription;
+				if (pushSpecs)
+					return UIText.RefSpecPanel_dstPushDescription;
+				return UIText.RefSpecPanel_dstFetchDescription;
+			}
+
+			@Override
+			public Color getBackground(final Object element) {
+				if (isInvalidSpec(element))
+					return errorBackgroundColor;
+				return null;
+			}
+
+			@Override
+			public Color getToolTipForegroundColor(Object element) {
+				if (isInvalidSpec(element))
+					return errorTextColor;
+				return null;
+			}
+		});
+		column.setEditingSupport(new EditingSupport(tableViewer) {
+			@Override
+			protected boolean canEdit(final Object element) {
+				return true;
+			}
+
+			@Override
+			protected CellEditor getCellEditor(final Object element) {
+				return (pushSpecs ? remoteRefCellEditor : localRefCellEditor);
+			}
+
+			@Override
+			protected Object getValue(final Object element) {
+				return ((RefSpec) element).getDestination();
+			}
+
+			@Override
+			protected void setValue(final Object element, final Object value) {
+				if (value == null || ((String) value).length() == 0) {
+					// Ignore empty strings - do not set them in model.
+					// User won't loose any information if we just fall back
+					// to the old value.
+					return;
+				}
+
+				final RefSpec oldSpec = (RefSpec) element;
+				final RefSpec newSpec = setRefSpecDestination(oldSpec,
+						(String) value);
+				setRefSpec(oldSpec, newSpec);
+			}
+		});
+	}
+
+	private void createForceColumn(final TableColumnLayout columnLayout) {
+		final TableViewerColumn column = createColumn(columnLayout,
+				UIText.RefSpecPanel_columnForce, COLUMN_FORCE_WEIGHT,
+				SWT.CENTER);
+		column.setLabelProvider(new CheckboxLabelProvider(tableViewer
+				.getControl()) {
+			@Override
+			protected boolean isChecked(final Object element) {
+				return ((RefSpec) element).isForceUpdate();
+			}
+
+			@Override
+			protected boolean isEnabled(Object element) {
+				return !isDeleteRefSpec(element);
+			}
+
+			@Override
+			public String getToolTipText(Object element) {
+				if (!isEnabled(element))
+					return UIText.RefSpecPanel_forceDeleteDescription;
+				if (isChecked(element))
+					return UIText.RefSpecPanel_forceTrueDescription + '\n'
+							+ UIText.RefSpecPanel_clickToChange;
+				return UIText.RefSpecPanel_forceFalseDescription + '\n'
+						+ UIText.RefSpecPanel_clickToChange;
+			}
+		});
+		column.setEditingSupport(new EditingSupport(tableViewer) {
+			@Override
+			protected boolean canEdit(final Object element) {
+				return !isDeleteRefSpec(element);
+			}
+
+			@Override
+			protected CellEditor getCellEditor(final Object element) {
+				return forceUpdateCellEditor;
+			}
+
+			@Override
+			protected Object getValue(final Object element) {
+				return ((RefSpec) element).isForceUpdate();
+			}
+
+			@Override
+			protected void setValue(final Object element, final Object value) {
+				final RefSpec oldSpec = (RefSpec) element;
+				final RefSpec newSpec = oldSpec.setForceUpdate((Boolean) value);
+				setRefSpec(oldSpec, newSpec);
+			}
+		});
+	}
+
+	private void createRemoveColumn(TableColumnLayout columnLayout) {
+		final TableViewerColumn column = createColumn(columnLayout,
+				UIText.RefSpecPanel_columnRemove, COLUMN_REMOVE_WEIGHT,
+				SWT.CENTER);
+		column.setLabelProvider(new CenteredImageLabelProvider() {
+			@Override
+			public Image getImage(Object element) {
+				return imageRegistry.get(IMAGE_TRASH);
+			}
+
+			@Override
+			public String getToolTipText(Object element) {
+				return NLS.bind(UIText.RefSpecPanel_removeDescription,
+						typeString());
+			}
+		});
+		column.setEditingSupport(new EditingSupport(tableViewer) {
+			@Override
+			protected boolean canEdit(Object element) {
+				return true;
+			}
+
+			@Override
+			protected CellEditor getCellEditor(Object element) {
+				return removeSpecCellEditor;
+			}
+
+			@Override
+			protected Object getValue(Object element) {
+				return null;
+			}
+
+			@Override
+			protected void setValue(Object element, Object value) {
+				removeRefSpec((RefSpec) element);
+			}
+		});
+	}
+
+	private TableViewerColumn createColumn(
+			final TableColumnLayout columnLayout, final String text,
+			final int weight, final int style) {
+		final TableViewerColumn viewerColumn = new TableViewerColumn(
+				tableViewer, style);
+		final TableColumn column = viewerColumn.getColumn();
+		column.setText(text);
+		columnLayout.setColumnData(column, new ColumnWeightData(weight));
+		return viewerColumn;
+	}
+
+	private void createCellEditors(final Table table) {
+		modeCellEditor = new CheckboxCellEditor(table);
+		localRefCellEditor = createLocalRefCellEditor(table);
+		remoteRefCellEditor = createRemoteRefCellEditor(table);
+		forceUpdateCellEditor = new CheckboxCellEditor(table);
+		removeSpecCellEditor = new ClickableCellEditor(table);
+	}
+
+	private CellEditor createLocalRefCellEditor(final Table table) {
+		return createRefCellEditor(table, getRefsProposalProvider(true));
+	}
+
+	private CellEditor createRemoteRefCellEditor(final Table table) {
+		return createRefCellEditor(table, getRefsProposalProvider(false));
+	}
+
+	private CellEditor createRefCellEditor(final Table table,
+			final IContentProposalProvider proposalProvider) {
+		final CellEditor cellEditor = new TextCellEditor(table);
+
+		final Text text = (Text) cellEditor.getControl();
+		final ContentAssistCommandAdapter assist = new ContentAssistCommandAdapter(
+				text, new TextContentAdapter(), proposalProvider, null, null,
+				true);
+		assist
+				.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
+
+		return cellEditor;
+	}
+
+	private void createSpecsButtonsPanel(final Composite parent) {
+		final Composite specsPanel = new Composite(parent, SWT.NONE);
+		specsPanel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true,
+				false));
+		final RowLayout layout = new RowLayout();
+		layout.spacing = 10;
+		specsPanel.setLayout(layout);
+
+		forceUpdateAllButton = new Button(specsPanel, SWT.PUSH);
+		forceUpdateAllButton.setText(UIText.RefSpecPanel_forceAll);
+		forceUpdateAllButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				final List<RefSpec> specsCopy = new ArrayList<RefSpec>(specs);
+				for (final RefSpec spec : specsCopy) {
+					if (!isDeleteRefSpec(spec))
+						setRefSpec(spec, spec.setForceUpdate(true));
+				}
+			}
+		});
+		forceUpdateAllButton
+				.setToolTipText(UIText.RefSpecPanel_forceAllDescription);
+		updateForceUpdateAllButton();
+
+		removeAllSpecButton = new Button(specsPanel, SWT.PUSH);
+		removeAllSpecButton.setImage(imageRegistry.get(IMAGE_CLEAR));
+		removeAllSpecButton.setText(UIText.RefSpecPanel_removeAll);
+		removeAllSpecButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				clearRefSpecs();
+			}
+		});
+		removeAllSpecButton
+				.setToolTipText(UIText.RefSpecPanel_removeAllDescription);
+		updateRemoveAllSpecButton();
+
+		addRefSpecTableListener(new SelectionChangeListener() {
+			public void selectionChanged() {
+				updateForceUpdateAllButton();
+				updateRemoveAllSpecButton();
+			}
+		});
+	}
+
+	private void tryAutoCompleteSrcToDst() {
+		final String src = creationSrcCombo.getText();
+		final String dst = creationDstCombo.getText();
+
+		if (src == null || src.length() == 0)
+			return;
+
+		if (dst != null && dst.length() > 0) {
+			// dst is already there, just fix wildcards if needed
+			final String newDst;
+			if (RefSpec.isWildcard(src))
+				newDst = wildcardSpecComponent(dst);
+			else
+				newDst = unwildcardSpecComponent(dst, src);
+			creationDstCombo.setText(newDst);
+			return;
+		}
+
+		if (!isValidRefExpression(src)) {
+			// no way to be smarter than user here
+			return;
+		}
+
+		// dst is empty, src is ref or wildcard, so we can rewrite it as user
+		// would perhaps
+		if (pushSpecs)
+			creationDstCombo.setText(src);
+		else {
+			for (final RefSpec spec : predefinedConfigured) {
+				if (spec.matchSource(src)) {
+					final String newDst = spec.expandFromSource(src)
+							.getDestination();
+					creationDstCombo.setText(newDst);
+					return;
+				}
+			}
+			if (remoteName != null && src.startsWith(HEADS_PREFIX_S)) {
+				final String newDst = REMOTES_PREFIX_S + remoteName + '/'
+						+ src.substring(HEADS_PREFIX_S.length());
+				creationDstCombo.setText(newDst);
+			}
+		}
+	}
+
+	private void tryAutoCompleteDstToSrc() {
+		final String src = creationSrcCombo.getText();
+		final String dst = creationDstCombo.getText();
+
+		if (dst == null || dst.length() == 0)
+			return;
+
+		if (src != null && src.length() > 0) {
+			// src is already there, fix wildcards if needed
+			final String newSrc;
+			if (RefSpec.isWildcard(dst))
+				newSrc = wildcardSpecComponent(src);
+			else
+				newSrc = unwildcardSpecComponent(src, dst);
+			creationSrcCombo.setText(newSrc);
+			return;
+		}
+	}
+
+	private void validateCreationPanel() {
+		final String src = creationSrcCombo.getText();
+		final String dst = creationDstCombo.getText();
+
+		// check src ref field
+		boolean srcOk = false;
+		final boolean srcWildcard = RefSpec.isWildcard(src);
+		if (src == null || src.length() == 0)
+			setControlDecoration(creationSrcDecoration,
+					FieldDecorationRegistry.DEC_REQUIRED,
+					UIText.RefSpecPanel_validationSrcUpdateRequired);
+		else if (pushSpecs) {
+			if (!srcWildcard && !isLocalRef(src))
+				setControlDecoration(creationSrcDecoration,
+						FieldDecorationRegistry.DEC_ERROR, NLS.bind(
+								UIText.RefSpecPanel_validationRefInvalidLocal,
+								src));
+			else if (srcWildcard && !isValidRefExpression(src))
+				setControlDecoration(
+						creationSrcDecoration,
+						FieldDecorationRegistry.DEC_ERROR,
+						NLS
+								.bind(
+										UIText.RefSpecPanel_validationRefInvalidExpression,
+										src));
+			else {
+				srcOk = true;
+				if (srcWildcard && !isMatchingAny(src, localRefNames))
+					setControlDecoration(
+							creationSrcDecoration,
+							FieldDecorationRegistry.DEC_WARNING,
+							NLS
+									.bind(
+											UIText.RefSpecPanel_validationRefNonMatchingLocal,
+											src));
+				else
+					creationSrcDecoration.hide();
+			}
+		} else {
+			if (!srcWildcard && !isRemoteRef(src))
+				setControlDecoration(
+						creationSrcDecoration,
+						FieldDecorationRegistry.DEC_ERROR,
+						NLS
+								.bind(
+										UIText.RefSpecPanel_validationRefNonExistingRemote,
+										src));
+			else if (srcWildcard && !isMatchingAny(src, remoteRefNames)) {
+				setControlDecoration(
+						creationSrcDecoration,
+						FieldDecorationRegistry.DEC_WARNING,
+						NLS
+								.bind(
+										UIText.RefSpecPanel_validationRefNonMatchingRemote,
+										src));
+				srcOk = true;
+			} else {
+				srcOk = true;
+				creationSrcDecoration.hide();
+			}
+		}
+
+		// check dst ref field
+		boolean dstOk = false;
+		if (dst == null || dst.length() == 0)
+			setControlDecoration(creationDstDecoration,
+					FieldDecorationRegistry.DEC_REQUIRED,
+					UIText.RefSpecPanel_validationDstRequired);
+		else if (!isValidRefExpression(dst))
+			setControlDecoration(creationDstDecoration,
+					FieldDecorationRegistry.DEC_ERROR, NLS.bind(
+							UIText.RefSpecPanel_validationDstInvalidExpression,
+							dst));
+		else {
+			creationDstDecoration.hide();
+			dstOk = true;
+		}
+		// leave duplicates dst checking for validateSpecs()
+
+		// check the wildcard synergy
+		boolean wildcardOk = true;
+		if (srcOk && dstOk && (srcWildcard ^ RefSpec.isWildcard(dst))) {
+			setControlDecoration(creationSrcDecoration,
+					FieldDecorationRegistry.DEC_ERROR,
+					UIText.RefSpecPanel_validationWildcardInconsistent);
+			setControlDecoration(creationDstDecoration,
+					FieldDecorationRegistry.DEC_ERROR,
+					UIText.RefSpecPanel_validationWildcardInconsistent);
+			wildcardOk = false;
+		}
+
+		creationButton.setEnabled(srcOk && dstOk && wildcardOk);
+	}
+
+	private void validateDeleteCreationPanel() {
+		final String ref = deleteRefCombo.getText();
+
+		deleteButton.setEnabled(false);
+		if (ref == null || ref.length() == 0)
+			setControlDecoration(deleteRefDecoration,
+					FieldDecorationRegistry.DEC_REQUIRED,
+					UIText.RefSpecPanel_validationRefDeleteRequired);
+		else if (!isValidRefExpression(ref))
+			setControlDecoration(deleteRefDecoration,
+					FieldDecorationRegistry.DEC_ERROR, NLS.bind(
+							UIText.RefSpecPanel_validationRefInvalidExpression,
+							ref));
+		else if (RefSpec.isWildcard(ref))
+			setControlDecoration(deleteRefDecoration,
+					FieldDecorationRegistry.DEC_ERROR,
+					UIText.RefSpecPanel_validationRefDeleteWildcard);
+		else if (!isRemoteRef(ref))
+			setControlDecoration(
+					deleteRefDecoration,
+					FieldDecorationRegistry.DEC_ERROR,
+					NLS
+							.bind(
+									UIText.RefSpecPanel_validationRefNonExistingRemoteDelete,
+									ref));
+		else {
+			deleteRefDecoration.hide();
+			deleteButton.setEnabled(true);
+		}
+	}
+
+	private void validateSpecs() {
+		// validate spec; display max. 1 error message for user at time
+		final RefSpec oldInvalidSpec = invalidSpec;
+		final RefSpec oldInvalidSpecSameDst = invalidSpecSameDst;
+		errorMessage = null;
+		invalidSpec = null;
+		invalidSpecSameDst = null;
+		for (final RefSpec spec : specs) {
+			errorMessage = validateSpec(spec);
+			if (errorMessage != null) {
+				invalidSpec = spec;
+				break;
+			}
+		}
+		if (errorMessage == null)
+			validateSpecsCrossDst();
+		if (invalidSpec != oldInvalidSpec
+				|| invalidSpecSameDst != oldInvalidSpecSameDst)
+			tableViewer.refresh();
+	}
+
+	private String validateSpec(final RefSpec spec) {
+		final String src = spec.getSource();
+		final String dst = spec.getDestination();
+		final boolean wildcard = spec.isWildcard();
+
+		// check src
+		if (pushSpecs) {
+			if (!isDeleteRefSpec(spec)) {
+				if (src.length() == 0)
+					return UIText.RefSpecPanel_validationSrcUpdateRequired;
+				else if (!wildcard && !isLocalRef(src))
+					return NLS.bind(
+							UIText.RefSpecPanel_validationRefInvalidLocal, src);
+				else if (wildcard && !isValidRefExpression(src))
+					return NLS.bind(
+							UIText.RefSpecPanel_validationRefInvalidExpression,
+							src);
+				// ignore non-matching wildcard specs
+			}
+		} else {
+			if (src == null || src.length() == 0)
+				return UIText.RefSpecPanel_validationSrcUpdateRequired;
+			else if (!wildcard && !isRemoteRef(src))
+				return NLS
+						.bind(
+								UIText.RefSpecPanel_validationRefNonExistingRemote,
+								src);
+			// ignore non-matching wildcard specs
+		}
+
+		// check dst
+		if (dst == null || dst.length() == 0) {
+			if (isDeleteRefSpec(spec))
+				return UIText.RefSpecPanel_validationRefDeleteRequired;
+			return UIText.RefSpecPanel_validationDstRequired;
+		} else if (!isValidRefExpression(dst))
+			return NLS.bind(UIText.RefSpecPanel_validationRefInvalidExpression,
+					dst);
+		else if (isDeleteRefSpec(spec) && !isRemoteRef(dst))
+			return NLS.bind(
+					UIText.RefSpecPanel_validationRefNonExistingRemoteDelete,
+					dst);
+
+		return null;
+	}
+
+	private boolean isInvalidSpec(Object element) {
+		return element == invalidSpec || element == invalidSpecSameDst;
+	}
+
+	private void validateSpecsCrossDst() {
+		final Map<String, RefSpec> dstsSpecsMap = new HashMap<String, RefSpec>();
+		try {
+			for (final RefSpec spec : specs) {
+				if (!spec.isWildcard()) {
+					if (!tryAddDestination(dstsSpecsMap, spec.getDestination(),
+							spec))
+						return;
+				} else {
+					final Collection<String> srcNames;
+					if (pushSpecs)
+						srcNames = localRefNames;
+					else
+						srcNames = remoteRefNames;
+
+					for (final String src : srcNames) {
+						if (spec.matchSource(src)) {
+							final String dst = spec.expandFromSource(src)
+									.getDestination();
+							if (!tryAddDestination(dstsSpecsMap, dst, spec))
+								return;
+						}
+					}
+				}
+			}
+		} finally {
+			matchingAnyRefs = !dstsSpecsMap.isEmpty();
+		}
+	}
+
+	private boolean tryAddDestination(final Map<String, RefSpec> dstsSpecsMap,
+			final String dst, final RefSpec spec) {
+		final RefSpec other = dstsSpecsMap.put(dst, spec);
+		if (other != null) {
+			errorMessage = NLS
+					.bind(
+							UIText.RefSpecPanel_validationSpecificationsOverlappingDestination,
+							dst);
+			invalidSpec = other;
+			invalidSpecSameDst = spec;
+			return false;
+		}
+		return true;
+	}
+
+	private void updateAddPredefinedButton(final Button button,
+			final List<RefSpec> predefined) {
+		boolean enable = false;
+		for (final RefSpec pre : predefined) {
+			if (!specs.contains(pre)) {
+				enable = true;
+				break;
+			}
+		}
+		button.setEnabled(enable);
+	}
+
+	private void updateAddPredefinedButton(Button button,
+			final RefSpec predefined) {
+		button.setEnabled(!specs.contains(predefined));
+	}
+
+	private void updateForceUpdateAllButton() {
+		boolean enable = false;
+		for (final RefSpec spec : specs) {
+			if (!isDeleteRefSpec(spec) && !spec.isForceUpdate()) {
+				enable = true;
+				break;
+			}
+		}
+		forceUpdateAllButton.setEnabled(enable);
+	}
+
+	private void updateRemoveAllSpecButton() {
+		removeAllSpecButton.setEnabled(!specs.isEmpty());
+	}
+
+	private String typeString() {
+		return (pushSpecs ? UIText.RefSpecPanel_push
+				: UIText.RefSpecPanel_fetch);
+	}
+
+	private void addPredefinedRefSpecs(final RefSpec predefined) {
+		addPredefinedRefSpecs(Collections.singletonList(predefined));
+	}
+
+	private void addPredefinedRefSpecs(final List<RefSpec> predefined) {
+		for (final RefSpec pre : predefined) {
+			if (!specs.contains(pre))
+				addRefSpec(pre);
+		}
+	}
+
+	private List<RefContentProposal> createContentProposals(
+			final Collection<Ref> refs, final Ref HEAD) {
+		final TreeSet<Ref> set = new TreeSet<Ref>(new Comparator<Ref>() {
+			public int compare(Ref o1, Ref o2) {
+				// lexicographical ordering by name seems to be fine
+				return o1.getName().compareTo(o2.getName());
+			}
+		});
+		set.addAll(refs);
+		if (HEAD != null)
+			set.add(HEAD);
+
+		final List<RefContentProposal> result = new ArrayList<RefContentProposal>(
+				set.size());
+		for (final Ref r : set)
+			result.add(new RefContentProposal(localDb, r));
+		return result;
+	}
+
+	private List<RefContentProposal> createProposalsFilteredLocal(
+			final List<RefContentProposal> proposals) {
+		final List<RefContentProposal> result = new ArrayList<RefContentProposal>();
+		for (final RefContentProposal p : proposals) {
+			final String content = p.getContent();
+			if (pushSpecs) {
+				if (content.equals(Constants.HEAD)
+						|| content.startsWith(HEADS_PREFIX_S))
+					result.add(p);
+			} else {
+				if (content.startsWith(REMOTES_PREFIX_S))
+					result.add(p);
+			}
+		}
+		return result;
+	}
+
+	private boolean isRemoteRef(String ref) {
+		return remoteRefNames.contains(ref);
+	}
+
+	private boolean isLocalRef(final String ref) {
+		return tryResolveLocalRef(ref) != null;
+	}
+
+	private boolean isMatchingAny(final String ref,
+			final Collection<String> names) {
+		// strip wildcard sign
+		final String prefix = ref.substring(0, ref.length() - 1);
+		for (final String name : names)
+			if (name.startsWith(prefix))
+				return true;
+		return false;
+	}
+
+	private ObjectId tryResolveLocalRef(final String ref) {
+		try {
+			return localDb.resolve(ref);
+		} catch (final IOException e) {
+			Activator.logError(
+					"I/O error occurred during resolving expression: " //$NON-NLS-1$
+							+ ref, e);
+			return null;
+		}
+	}
+
+	private class RefContentProposalProvider implements
+			IContentProposalProvider {
+		private List<RefContentProposal> proposals = Collections.emptyList();
+
+		private final boolean tryResolvingLocally;
+
+		private RefContentProposalProvider(final boolean tryResolvingLocally) {
+			this.tryResolvingLocally = tryResolvingLocally;
+		}
+
+		private void setProposals(final List<RefContentProposal> proposals) {
+			this.proposals = proposals;
+		}
+
+		public IContentProposal[] getProposals(final String contents,
+				int position) {
+			final List<RefContentProposal> result = new ArrayList<RefContentProposal>();
+
+			if (contents.indexOf('*') != -1 || contents.indexOf('?') != -1) {
+				// contents contains wildcards
+
+				// check if contents can be safely added as wildcard spec
+				if (isValidRefExpression(contents))
+					result.add(new RefContentProposal(localDb, contents, null));
+
+				// let's expand wildcards
+				final String regex = ".*" //$NON-NLS-1$
+						+ contents.replace("*", ".*").replace("?", ".?") + ".*"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
+				final Pattern p = Pattern.compile(regex);
+				for (final RefContentProposal prop : proposals)
+					if (p.matcher(prop.getContent()).matches())
+						result.add(prop);
+			} else {
+				for (final RefContentProposal prop : proposals)
+					if (prop.getContent().contains(contents))
+						result.add(prop);
+
+				if (tryResolvingLocally && result.isEmpty()) {
+					final ObjectId id = tryResolveLocalRef(contents);
+					if (id != null)
+						result
+								.add(new RefContentProposal(localDb, contents,
+										id));
+				}
+			}
+			return result.toArray(new IContentProposal[0]);
+		}
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index 55f6c87..f2be3c1 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -76,6 +76,77 @@ CloneDestinationPage_fieldRequired={0} is required.
 CloneDestinationPage_browseButton=Browse
 CloneDestinationPage_errorNotEmptyDir={0} is not an empty directory.
 
+RefSpecPanel_clickToChange=[Click to change]
+RefSpecPanel_columnDst=Destination Ref
+RefSpecPanel_columnForce=Force Update
+RefSpecPanel_columnMode=Mode
+RefSpecPanel_columnRemove=Remove
+RefSpecPanel_columnSrc=Source Ref
+RefSpecPanel_creationButton=Add spec
+RefSpecPanel_creationButtonDescription=Add this create/update specification to set of {0} specifications. 
+RefSpecPanel_creationDst=Destination ref:
+RefSpecPanel_creationGroup=Add create/update specificaton
+RefSpecPanel_creationSrc=Source ref:
+RefSpecPanel_deletionButton=Add spec
+RefSpecPanel_deletionButtonDescription=Add this delete specification to set of push specifications.
+RefSpecPanel_deletionGroup=Add delete ref specificaton
+RefSpecPanel_deletionRef=Remote ref to delete:
+RefSpecPanel_dstFetchDescription=Local destination ref(s) to fetch to - create or update.
+RefSpecPanel_dstPushDescription=Remote destination ref(s) to push to - create or update.
+RefSpecPanel_dstDeletionDescription=Remote ref to delete.
+RefSpecPanel_errorRemoteConfigDescription=Can't parse remote configuration URIs.
+RefSpecPanel_errorRemoteConfigTitle=Remote configuration problem
+RefSpecPanel_fetch=fetch
+RefSpecPanel_forceAll=Force update all specs
+RefSpecPanel_forceAllDescription=Set force update setting to all speficications.
+RefSpecPanel_forceDeleteDescription=Delete specification is always unconditional.
+RefSpecPanel_forceFalseDescription=Allow only fast-forward update: old object must merge into new object.
+RefSpecPanel_forceTrueDescription=Allow non-fast-forward update: old object doesn't have to merge to new object.
+RefSpecPanel_modeDelete=Delete
+RefSpecPanel_modeDeleteDescription=This is a delete specification.
+RefSpecPanel_modeUpdate=Update
+RefSpecPanel_modeUpdateDescription=This is a create/update specification.
+RefSpecPanel_predefinedAll=Add all branches spec
+RefSpecPanel_predefinedAllDescription=Add specification covering all branches.
+RefSpecPanel_predefinedConfigured=Add configured {0} specs 
+RefSpecPanel_predefinedConfiguredDescription=Add previously configured specifications for this configured remote (if available).
+RefSpecPanel_predefinedGroup=Add predefined specification
+RefSpecPanel_predefinedTags=Add all tags spec
+RefSpecPanel_predefinedTagsDescription=Add specification covering all tags.
+RefSpecPanel_push=push
+RefSpecPanel_refChooseSome=choose/some/ref
+RefSpecPanel_refChooseSomeWildcard=choose/some/ref/*
+RefSpecPanel_removeAll=Remove all specs
+RefSpecPanel_removeAllDescription=Remove all speficications.
+RefSpecPanel_removeDescription=Click to remove this specification.
+RefSpecPanel_specifications=Specifications for {0}
+RefSpecPanel_srcFetchDescription=Remote source ref(s) to fetch from.
+RefSpecPanel_srcPushDescription=Local source ref(s) to push from.
+RefSpecPanel_srcDeleteDescription=Delete specification always has an empty source ref.
+RefSpecPanel_validationDstInvalidExpression={0} is not a valid ref expression for destination.
+RefSpecPanel_validationDstRequired=Destination ref is required.
+RefSpecPanel_validationRefDeleteRequired=Ref name to delete is required.
+RefSpecPanel_validationRefDeleteWildcard=Delete ref cannot be a wildcard.
+RefSpecPanel_validationRefInvalidExpression={0} is not a valid ref expression.
+RefSpecPanel_validationRefInvalidLocal={0} is not a valid ref in local repository.
+RefSpecPanel_validationRefNonExistingRemote={0} does not exist in remote repository.
+RefSpecPanel_validationRefNonExistingRemoteDelete={0} already does not exist in remote repository.
+RefSpecPanel_validationRefNonMatchingLocal={0} does not match any ref in local repository.
+RefSpecPanel_validationRefNonMatchingRemote={0} does not match any ref in remote repository.
+RefSpecPanel_validationSpecificationsOverlappingDestination=Two or more specifications point to {0} (the same destination).  
+RefSpecPanel_validationSrcUpdateRequired=Source ref is required for update/create specification.
+RefSpecPanel_validationWildcardInconsistent=Wildcard must be set either on both source and destination or on none of them.
+
+RefSpecPage_descriptionFetch=Select refs to fetch.
+RefSpecPage_descriptionPush=Select refs to push.
+RefSpecPage_errorDontMatchSrc=Specifications don't match any existing refs in source repository.
+RefSpecPage_errorTransportDialogMessage=Cannot get remote repository refs.
+RefSpecPage_errorTransportDialogTitle=Transport Error
+RefSpecPage_operationCancelled=Operation cancelled.
+RefSpecPage_saveSpecifications=Save specifications in "{0}" configuration
+RefSpecPage_titleFetch=Fetch Ref Specifications
+RefSpecPage_titlePush=Push Ref Specifications
+
 Decorator_failedLazyLoading=Resource decorator failed to load tree contents on demand.
 QuickDiff_failedLoading=Quick diff failed to obtain file data.
 
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 30/31] Add PushOperation to plugin
  2008-08-17 20:44                                                         ` [EGIT PATCH 29/31] Universal GUI for specifications edition: RefSpecPanel and related Marek Zawirski
@ 2008-08-17 20:44                                                           ` Marek Zawirski
  2008-08-17 20:44                                                             ` [EGIT PATCH 31/31] Push GUI Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

PushOperation is designed to handle push-to-one and push-to-many URIs,
using some supporting classes. Failure of connection to any of
repositories doesn't cause to fail whole operation - they are
independent that way. It is also possible to specify different expected
old object id for each remote repository.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/egit/core/CoreText.java        |   15 +
 .../src/org/spearce/egit/core/coretext.properties  |    6 +
 .../org/spearce/egit/core/op/PushOperation.java    |  148 +++++++++++
 .../spearce/egit/core/op/PushOperationResult.java  |  273 ++++++++++++++++++++
 .../egit/core/op/PushOperationSpecification.java   |   82 ++++++
 5 files changed, 524 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperation.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationResult.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationSpecification.java

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/CoreText.java b/org.spearce.egit.core/src/org/spearce/egit/core/CoreText.java
index 5974a5f..35e17b9 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/CoreText.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/CoreText.java
@@ -98,6 +98,21 @@ public class CoreText extends NLS {
 	/** */
 	public static String ListRemoteOperation_title;
 
+	/** */
+	public static String PushOperation_resultCancelled;
+
+	/** */
+	public static String PushOperation_resultNotSupported;
+
+	/** */
+	public static String PushOperation_resultTransportError;
+
+	/** */
+	public static String PushOperation_taskNameDryRun;
+
+	/** */
+	public static String PushOperation_taskNameNormalRun;
+
 	static {
 		final Class c = CoreText.class;
 		initializeMessages(c.getPackage().getName() + ".coretext", c);
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/coretext.properties b/org.spearce.egit.core/src/org/spearce/egit/core/coretext.properties
index c412161..94cf4aa 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/coretext.properties
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/coretext.properties
@@ -57,3 +57,9 @@ CheckpointJob_failed=Failed to write modified objects.
 CloneOperation_title=Cloning from {0}
 
 ListRemoteOperation_title=Getting remote branches information
+
+PushOperation_resultCancelled=Operation was cancelled.
+PushOperation_resultNotSupported=Can't push to {0}
+PushOperation_resultTransportError=Transport error occured during push operation: {0}
+PushOperation_taskNameDryRun=Trying pushing to remote repositories
+PushOperation_taskNameNormalRun=Pushing to remote repositories
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperation.java b/org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperation.java
new file mode 100644
index 0000000..853bcfc
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperation.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.op;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.osgi.util.NLS;
+import org.spearce.egit.core.CoreText;
+import org.spearce.egit.core.EclipseGitProgressTransformer;
+import org.spearce.jgit.errors.NotSupportedException;
+import org.spearce.jgit.errors.TransportException;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.transport.PushResult;
+import org.spearce.jgit.transport.RemoteConfig;
+import org.spearce.jgit.transport.Transport;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * Push operation: pushing from local repository to one or many remote ones.
+ */
+public class PushOperation implements IRunnableWithProgress {
+	private static final int WORK_UNITS_PER_TRANSPORT = 10;
+
+	private final Repository localDb;
+
+	private final PushOperationSpecification specification;
+
+	private final boolean dryRun;
+
+	private final RemoteConfig rc;
+
+	private final PushOperationResult operationResult = new PushOperationResult();
+
+	/**
+	 * Create push operation for provided specification.
+	 * <p>
+	 * Operation is not performed within constructor,
+	 * {@link #run(IProgressMonitor)} method must be called for that.
+	 * 
+	 * @param localDb
+	 *            local repository.
+	 * @param specification
+	 *            specification of ref updates for remote repositories.
+	 * @param rc
+	 *            optional remote config to apply on used transports. May be
+	 *            null.
+	 * @param dryRun
+	 *            true if push operation should just check for possible result
+	 *            and not really update remote refs, false otherwise - when push
+	 *            should act normally.
+	 */
+	public PushOperation(final Repository localDb,
+			final PushOperationSpecification specification,
+			final boolean dryRun, final RemoteConfig rc) {
+		this.localDb = localDb;
+		this.specification = specification;
+		this.dryRun = dryRun;
+		this.rc = rc;
+	}
+
+	/**
+	 * @return push operation result.
+	 */
+	public PushOperationResult getOperationResult() {
+		return operationResult;
+	}
+
+	/**
+	 * @return operation specification, as provided in constructor.
+	 */
+	public PushOperationSpecification getSpecification() {
+		return specification;
+	}
+
+	/**
+	 * Execute operation and store result. Operation is executed independently
+	 * on each remote repository.
+	 * <p>
+	 * 
+	 * @throws InvocationTargetException
+	 *             Cause of this exceptions may include
+	 *             {@link TransportException}, {@link NotSupportedException} or
+	 *             some unexpected {@link RuntimeException}.
+	 * @see IRunnableWithProgress#run(IProgressMonitor)
+	 */
+	public void run(IProgressMonitor monitor) throws InvocationTargetException {
+		if (monitor == null)
+			monitor = new NullProgressMonitor();
+
+		final int totalWork = specification.getURIsNumber()
+				* WORK_UNITS_PER_TRANSPORT;
+		if (dryRun)
+			monitor.beginTask(CoreText.PushOperation_taskNameDryRun, totalWork);
+		else
+			monitor.beginTask(CoreText.PushOperation_taskNameNormalRun,
+					totalWork);
+
+		for (final URIish uri : specification.getURIs()) {
+			final SubProgressMonitor subMonitor = new SubProgressMonitor(
+					monitor, WORK_UNITS_PER_TRANSPORT,
+					SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
+			Transport transport = null;
+			try {
+				if (monitor.isCanceled()) {
+					operationResult.addOperationResult(uri,
+							CoreText.PushOperation_resultCancelled);
+					continue;
+				}
+				transport = Transport.open(localDb, uri);
+
+				if (rc != null)
+					transport.applyConfig(rc);
+				transport.setDryRun(dryRun);
+				final EclipseGitProgressTransformer gitSubMonitor = new EclipseGitProgressTransformer(
+						subMonitor);
+				final PushResult pr = transport.push(gitSubMonitor,
+						specification.getRefUpdates(uri));
+				operationResult.addOperationResult(uri, pr);
+			} catch (final TransportException e) {
+				operationResult.addOperationResult(uri, NLS.bind(
+						CoreText.PushOperation_resultTransportError, e
+								.getMessage()));
+			} catch (final NotSupportedException e) {
+				operationResult.addOperationResult(uri, NLS.bind(
+						CoreText.PushOperation_resultNotSupported, e
+								.getMessage()));
+			} finally {
+				if (transport != null) {
+					transport.close();
+				}
+				// Dirty trick to get things always working.
+				subMonitor.beginTask("", WORK_UNITS_PER_TRANSPORT); //$NON-NLS-1$
+				subMonitor.done();
+				subMonitor.done();
+			}
+		}
+		monitor.done();
+	}
+}
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationResult.java b/org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationResult.java
new file mode 100644
index 0000000..d78b79c
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationResult.java
@@ -0,0 +1,273 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.op;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Set;
+
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.transport.PushResult;
+import org.spearce.jgit.transport.RemoteRefUpdate;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * Data class for storing push operation results for each remote repository/URI
+ * being part of push operation.
+ * <p>
+ * One instance of this class is dedicated for result of one push operation:
+ * either to one URI or to many URIs.
+ * 
+ * @see PushOperation
+ */
+public class PushOperationResult {
+	private LinkedHashMap<URIish, Entry> urisEntries;
+
+	/**
+	 * Construct empty push operation result.
+	 */
+	public PushOperationResult() {
+		this.urisEntries = new LinkedHashMap<URIish, Entry>();
+	}
+
+	/**
+	 * Add push result for the repository (URI) with successful connection.
+	 * 
+	 * @param uri
+	 *            remote repository URI.
+	 * @param result
+	 *            push result.
+	 */
+	public void addOperationResult(final URIish uri, final PushResult result) {
+		urisEntries.put(uri, new Entry(result));
+	}
+
+	/**
+	 * Add error message for the repository (URI) with unsuccessful connection.
+	 * 
+	 * @param uri
+	 *            remote repository URI.
+	 * @param errorMessage
+	 *            failure error message.
+	 */
+	public void addOperationResult(final URIish uri, final String errorMessage) {
+		urisEntries.put(uri, new Entry(errorMessage));
+	}
+
+	/**
+	 * @return set of remote repositories URIis. Set is ordered in addition
+	 *         sequence, which is usually the same as that from
+	 *         {@link PushOperationSpecification}.
+	 */
+	public Set<URIish> getURIs() {
+		return Collections.unmodifiableSet(urisEntries.keySet());
+	}
+
+	/**
+	 * @param uri
+	 *            remote repository URI.
+	 * @return true if connection was successful for this repository (URI),
+	 *         false if this operation ended with unsuccessful connection.
+	 */
+	public boolean isSuccessfulConnection(final URIish uri) {
+		return urisEntries.get(uri).isSuccessfulConnection();
+	}
+
+	/**
+	 * @return true if connection was successful for any repository (URI), false
+	 *         otherwise.
+	 */
+	public boolean isSuccessfulConnectionForAnyURI() {
+		for (final URIish uri : getURIs()) {
+			if (isSuccessfulConnection(uri))
+				return true;
+		}
+		return false;
+	}
+
+	/**
+	 * @param uri
+	 *            remote repository URI.
+	 * @return push result for this repository (URI) or null if operation ended
+	 *         with unsuccessful connection for this URI.
+	 */
+	public PushResult getPushResult(final URIish uri) {
+		return urisEntries.get(uri).getResult();
+	}
+
+	/**
+	 * @param uri
+	 *            remote repository URI.
+	 * @return error message for this repository (URI) or null if operation
+	 *         ended with successful connection for this URI.
+	 */
+	public String getErrorMessage(final URIish uri) {
+		return urisEntries.get(uri).getErrorMessage();
+	}
+
+	/**
+	 * @return string being list of failed URIs with their error messages.
+	 */
+	public String getErrorStringForAllURis() {
+		final StringBuilder sb = new StringBuilder();
+		boolean first = true;
+		for (final URIish uri : getURIs()) {
+			if (first)
+				first = false;
+			else
+				sb.append(", ");
+			sb.append(uri);
+			sb.append(" (");
+			sb.append(getErrorMessage(uri));
+			sb.append(")");
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Derive push operation specification from this push operation result.
+	 * <p>
+	 * Specification is created basing on URIs of remote repositories in this
+	 * result that completed without connection errors, and remote ref updates
+	 * from push results.
+	 * <p>
+	 * This method is targeted to provide support for 2-stage push, where first
+	 * operation is dry run for user confirmation and second one is a real
+	 * operation.
+	 * 
+	 * @param requireUnchanged
+	 *            if true, newly created copies of remote ref updates have
+	 *            expected old object id set to previously advertised ref value
+	 *            (remote ref won't be updated if it change in the mean time),
+	 *            if false, newly create copies of remote ref updates have
+	 *            expected object id set up as in this result source
+	 *            specification.
+	 * @return derived specification for another push operation.
+	 * @throws IOException
+	 *             when some previously locally available source ref is not
+	 *             available anymore, or some error occurred during creation
+	 *             locally tracking ref update.
+	 * 
+	 */
+	public PushOperationSpecification deriveSpecification(
+			final boolean requireUnchanged) throws IOException {
+		final PushOperationSpecification spec = new PushOperationSpecification();
+		for (final URIish uri : getURIs()) {
+			final PushResult pr = getPushResult(uri);
+			if (pr == null)
+				continue;
+
+			final Collection<RemoteRefUpdate> oldUpdates = pr
+					.getRemoteUpdates();
+			final ArrayList<RemoteRefUpdate> newUpdates = new ArrayList<RemoteRefUpdate>(
+					oldUpdates.size());
+			for (final RemoteRefUpdate rru : oldUpdates) {
+				final ObjectId expectedOldObjectId;
+				if (requireUnchanged) {
+					final Ref advertisedRef = getPushResult(uri)
+							.getAdvertisedRef(rru.getRemoteName());
+					if (advertisedRef == null)
+						expectedOldObjectId = ObjectId.zeroId();
+					else
+						expectedOldObjectId = advertisedRef.getObjectId();
+				} else
+					expectedOldObjectId = rru.getExpectedOldObjectId();
+				final RemoteRefUpdate newRru = new RemoteRefUpdate(rru,
+						expectedOldObjectId);
+				newUpdates.add(newRru);
+			}
+			spec.addURIRefUpdates(uri, newUpdates);
+		}
+		return spec;
+	}
+
+	/**
+	 * This implementation returns true if all following conditions are met:
+	 * <ul>
+	 * <li>both objects result have the same set successfully connected
+	 * repositories (URIs) - unsuccessful connections are discarded, AND <li>
+	 * remote ref updates must match for each successful connection in sense of
+	 * equal remoteName, equal status and equal newObjectId value.</li>
+	 * </ul>
+	 * 
+	 * @see Object#equals(Object)
+	 * @param obj
+	 *            other push operation result to compare to.
+	 * @return true if object is equal to this one in terms of conditions
+	 *         described above, false otherwise.
+	 */
+	@Override
+	public boolean equals(final Object obj) {
+		if (!(obj instanceof PushOperationResult))
+			return false;
+
+		final PushOperationResult other = (PushOperationResult) obj;
+
+		// Check successful connections/URIs two-ways:
+		final Set<URIish> otherURIs = other.getURIs();
+		for (final URIish uri : getURIs()) {
+			if (isSuccessfulConnection(uri)
+					&& (!otherURIs.contains(uri) || !other
+							.isSuccessfulConnection(uri)))
+				return false;
+		}
+		for (final URIish uri : other.getURIs()) {
+			if (other.isSuccessfulConnection(uri)
+					&& (!urisEntries.containsKey(uri) || !isSuccessfulConnection(uri)))
+				return false;
+		}
+
+		for (final URIish uri : getURIs()) {
+			if (!isSuccessfulConnection(uri))
+				continue;
+
+			final PushResult otherPushResult = other.getPushResult(uri);
+			for (final RemoteRefUpdate rru : getPushResult(uri)
+					.getRemoteUpdates()) {
+				final RemoteRefUpdate otherRru = otherPushResult
+						.getRemoteUpdate(rru.getRemoteName());
+				if (otherRru == null)
+					return false;
+				if (otherRru.getStatus() != rru.getStatus()
+						|| otherRru.getNewObjectId() != rru.getNewObjectId())
+					return false;
+			}
+		}
+		return true;
+	}
+
+	private static class Entry {
+		private String errorMessage;
+
+		private PushResult result;
+
+		Entry(final PushResult result) {
+			this.result = result;
+		}
+
+		Entry(final String errorMessage) {
+			this.errorMessage = errorMessage;
+		}
+
+		boolean isSuccessfulConnection() {
+			return result != null;
+		}
+
+		String getErrorMessage() {
+			return errorMessage;
+		}
+
+		PushResult getResult() {
+			return result;
+		}
+	}
+}
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationSpecification.java b/org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationSpecification.java
new file mode 100644
index 0000000..0e3f3b4
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/op/PushOperationSpecification.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core.op;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Set;
+
+import org.spearce.jgit.transport.RemoteRefUpdate;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * Data class storing push operation update specifications for each remote
+ * repository.
+ * <p>
+ * One instance is dedicated for one push operation: either push to one URI or
+ * to many URIs.
+ * 
+ * @see PushOperation
+ */
+public class PushOperationSpecification {
+	private LinkedHashMap<URIish, Collection<RemoteRefUpdate>> urisRefUpdates;
+
+	/**
+	 * Create empty instance of specification.
+	 * <p>
+	 * URIs and ref updates should be configured
+	 * {@link #addURIRefUpdates(URIish, Collection)} method.
+	 */
+	public PushOperationSpecification() {
+		this.urisRefUpdates = new LinkedHashMap<URIish, Collection<RemoteRefUpdate>>();
+	}
+
+	/**
+	 * Add remote repository URI with ref updates specification.
+	 * <p>
+	 * Ref updates are not in constructor - pay attention to not share them
+	 * between different URIs ref updates or push operations.
+	 * <p>
+	 * Note that refUpdates can differ between URIs <b>only</b> by expected old
+	 * object id field: {@link RemoteRefUpdate#getExpectedOldObjectId()}.
+	 * 
+	 * @param uri
+	 *            remote repository URI.
+	 * @param refUpdates
+	 *            collection of remote ref updates specifications.
+	 */
+	public void addURIRefUpdates(final URIish uri,
+			Collection<RemoteRefUpdate> refUpdates) {
+		urisRefUpdates.put(uri, refUpdates);
+	}
+
+	/**
+	 * @return set of remote repositories URIis. Set is ordered in addition
+	 *         sequence.
+	 */
+	public Set<URIish> getURIs() {
+		return Collections.unmodifiableSet(urisRefUpdates.keySet());
+	}
+
+	/**
+	 * @return number of remote repositories URI for this push operation.
+	 */
+	public int getURIsNumber() {
+		return urisRefUpdates.keySet().size();
+	}
+
+	/**
+	 * @param uri
+	 *            remote repository URI.
+	 * @return remote ref updates as specified by user for this URI.
+	 */
+	public Collection<RemoteRefUpdate> getRefUpdates(final URIish uri) {
+		return Collections.unmodifiableCollection(urisRefUpdates.get(uri));
+	}
+}
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 31/31] Push GUI
  2008-08-17 20:44                                                           ` [EGIT PATCH 30/31] Add PushOperation to plugin Marek Zawirski
@ 2008-08-17 20:44                                                             ` Marek Zawirski
  0 siblings, 0 replies; 62+ messages in thread
From: Marek Zawirski @ 2008-08-17 20:44 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Push operation in GUI is implemented as wizard consisting of 3 pages:
-repository selection
-ref specifications selection
-confirmation page with dry run results (optional - can be skipped)

Wizard finish-action starts Job with PushOperation. Dialog with results
is shown when job ends. It can be configured to be displayed only if
results change since confirmation.

With confirmation page, it is possible to perform 2-stage push, allowing
user to preview updates and mark them to be performed only if
advertised refs don't change.

Eventually, results Dialog can be replaced with Result/Transport view
some day, as this idea arose recently.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 org.spearce.egit.ui/plugin.properties              |    3 +
 org.spearce.egit.ui/plugin.xml                     |   15 +
 .../src/org/spearce/egit/ui/UIText.java            |  129 ++++++++
 .../egit/ui/internal/actions/PushAction.java       |   47 +++
 .../egit/ui/internal/push/ConfirmationPage.java    |  211 +++++++++++++
 .../egit/ui/internal/push/PushResultTable.java     |  327 ++++++++++++++++++++
 .../spearce/egit/ui/internal/push/PushWizard.java  |  250 +++++++++++++++
 .../ui/internal/push/RefUpdateContentProvider.java |   62 ++++
 .../egit/ui/internal/push/RefUpdateElement.java    |   67 ++++
 .../egit/ui/internal/push/ResultDialog.java        |   65 ++++
 .../src/org/spearce/egit/ui/uitext.properties      |   47 +++
 11 files changed, 1223 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/PushAction.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ConfirmationPage.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushResultTable.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushWizard.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateContentProvider.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateElement.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ResultDialog.java

diff --git a/org.spearce.egit.ui/plugin.properties b/org.spearce.egit.ui/plugin.properties
index ce3b058..8ac3474 100644
--- a/org.spearce.egit.ui/plugin.properties
+++ b/org.spearce.egit.ui/plugin.properties
@@ -41,6 +41,9 @@ ResetAction_tooltip=Reset the current branch to the same or another commit
 BranchAction_label=&Branch...
 BranchAction_tooltip=Switch to another branch
 
+PushAction_label=&Push To...
+PushAction_tooltip=Push to another repository
+
 GitActions_label=Git
 GitMenu_label=&Git
 
diff --git a/org.spearce.egit.ui/plugin.xml b/org.spearce.egit.ui/plugin.xml
index 0b59a6e..ca93f5a 100644
--- a/org.spearce.egit.ui/plugin.xml
+++ b/org.spearce.egit.ui/plugin.xml
@@ -40,6 +40,12 @@
                id="org.spearce.egit.ui.internal.actions.Disconnect">
          </action>
          <action
+               class="org.spearce.egit.ui.internal.actions.PushAction"
+               id="org.spearce.egit.ui.internal.actions.PushAction"
+               label="%PushAction_label"
+               menubarPath="team.main/projectGroup"
+               tooltip="%PushAction_tooltip"/>         
+         <action
                class="org.spearce.egit.ui.internal.actions.ResetAction"
                id="org.spearce.egit.ui.internal.actions.ResetAction"
                label="%ResetAction_label"
@@ -253,6 +259,15 @@
         </separator>
 	    </menu>
 		<action
+		       class="org.spearce.egit.ui.internal.actions.PushAction"
+		       id="org.spearce.egit.ui.actionpush"
+		       label="%PushAction_label"
+		       style="push"
+		       menubarPath="org.spearce.egit.ui.gitmenu/repo"
+		       toolbarPath="org.spearce.egit.ui"
+		       tooltip="%PushAction_tooltip">
+		</action>
+		<action
 		       class="org.spearce.egit.ui.internal.actions.BranchAction"
 		       disabledIcon="icons/toolbar/checkoutd.png"
 		       icon="icons/toolbar/checkoute.png"
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index ff2b541..cc785f7 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -464,6 +464,135 @@ public class UIText extends NLS {
 	public static String HistoryPreferencePage_title;
 
 	/** */
+	public static String PushWizard_cantConnectToAny;
+
+	/** */
+	public static String PushWizard_cantPrepareUpdatesMessage;
+
+	/** */
+	public static String PushWizard_cantPrepareUpdatesTitle;
+
+	/** */
+	public static String PushWizard_cantSaveMessage;
+
+	/** */
+	public static String PushWizard_cantSaveTitle;
+
+	/** */
+	public static String PushWizard_jobName;
+
+	/** */
+	public static String PushWizard_missingRefsMessage;
+
+	/** */
+	public static String PushWizard_missingRefsTitle;
+
+	/** */
+	public static String PushWizard_unexpectedError;
+
+	/** */
+	public static String PushWizard_windowTitleDefault;
+
+	/** */
+	public static String PushWizard_windowTitleWithDestination;
+
+	/** */
+	public static String ConfirmationPage_cantConnectToAny;
+
+	/** */
+	public static String ConfirmationPage_description;
+
+	/** */
+	public static String ConfirmationPage_errorCantResolveSpecs;
+
+	/** */
+	public static String ConfirmationPage_errorInterrupted;
+
+	/** */
+	public static String ConfirmationPage_errorRefsChangedNoMatch;
+
+	/** */
+	public static String ConfirmationPage_errorUnexpected;
+
+	/** */
+	public static String ConfirmationPage_requireUnchangedButton;
+
+	/** */
+	public static String ConfirmationPage_showOnlyIfChanged;
+
+	/** */
+	public static String ConfirmationPage_title;
+
+	/** */
+	public static String PushResultTable_columnStatusRepo;
+
+	/** */
+	public static String PushResultTable_columnDst;
+
+	/** */
+	public static String PushResultTable_columnSrc;
+
+	/** */
+	public static String PushResultTable_columnMode;
+
+	/** */
+	public static String PushResultTable_statusUnexpected;
+
+	/** */
+	public static String PushResultTable_statusConnectionFailed;
+
+	/** */
+	public static String PushResultTable_statusDetailChanged;
+
+	/** */
+	public static String PushResultTable_refNonExisting;
+
+	/** */
+	public static String PushResultTable_statusDetailDeleted;
+
+	/** */
+	public static String PushResultTable_statusDetailNonFastForward;
+
+	/** */
+	public static String PushResultTable_statusDetailNoDelete;
+
+	/** */
+	public static String PushResultTable_statusDetailNonExisting;
+
+	/** */
+	public static String PushResultTable_statusDetailForcedUpdate;
+
+	/** */
+	public static String PushResultTable_statusDetailFastForward;
+
+	/** */
+	public static String PushResultTable_statusRemoteRejected;
+
+	/** */
+	public static String PushResultTable_statusRejected;
+
+	/** */
+	public static String PushResultTable_statusNoMatch;
+
+	/** */
+	public static String PushResultTable_statusUpToDate;
+
+	/** */
+	public static String PushResultTable_statusOkDeleted;
+
+	/** */
+	public static String PushResultTable_statusOkNewBranch;
+
+	/** */
+	public static String PushResultTable_statusOkNewTag;
+
+	/** */
+	public static String ResultDialog_title;
+
+	/** */
+	public static String ResultDialog_label;
+
+	/** */
 	public static String WindowCachePreferencePage_title;
 
 	/** */
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/PushAction.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/PushAction.java
new file mode 100644
index 0000000..61833d0
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/PushAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.actions;
+
+import java.net.URISyntaxException;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.wizard.WizardDialog;
+import org.spearce.egit.ui.internal.push.PushWizard;
+import org.spearce.jgit.lib.Repository;
+
+/**
+ * Action for choosing specifications for push, and pushing out to another
+ * repository.
+ */
+public class PushAction extends RepositoryAction {
+
+	@Override
+	public void run(IAction action) {
+		final Repository repository = getRepository(true);
+		if (repository == null)
+			return;
+
+		final PushWizard pushWizard;
+		try {
+			pushWizard = new PushWizard(repository);
+		} catch (URISyntaxException x) {
+			MessageDialog.openError(getShell(), "Corrupted configuration",
+					"Remote repositories URLs configuration is corrupted: "
+							+ x.getMessage());
+			return;
+		}
+		final WizardDialog dialog = new WizardDialog(getShell(), pushWizard);
+		dialog.open();
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return getRepository(false) != null;
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ConfirmationPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ConfirmationPage.java
new file mode 100644
index 0000000..38109d2
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ConfirmationPage.java
@@ -0,0 +1,211 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.push;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.spearce.egit.core.op.PushOperation;
+import org.spearce.egit.core.op.PushOperationResult;
+import org.spearce.egit.core.op.PushOperationSpecification;
+import org.spearce.egit.ui.UIText;
+import org.spearce.egit.ui.internal.components.RefSpecPage;
+import org.spearce.egit.ui.internal.components.RepositorySelection;
+import org.spearce.egit.ui.internal.components.RepositorySelectionPage;
+import org.spearce.egit.ui.internal.components.SelectionChangeListener;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.transport.RefSpec;
+import org.spearce.jgit.transport.RemoteRefUpdate;
+import org.spearce.jgit.transport.Transport;
+import org.spearce.jgit.transport.URIish;
+
+class ConfirmationPage extends WizardPage {
+	static Collection<RemoteRefUpdate> copyUpdates(
+			final Collection<RemoteRefUpdate> refUpdates) throws IOException {
+		final Collection<RemoteRefUpdate> copy = new ArrayList<RemoteRefUpdate>(
+				refUpdates.size());
+		for (final RemoteRefUpdate rru : refUpdates)
+			copy.add(new RemoteRefUpdate(rru, null));
+		return copy;
+	}
+
+	private final Repository local;
+
+	private final RepositorySelectionPage repoPage;
+
+	private final RefSpecPage refSpecPage;
+
+	private RepositorySelection displayedRepoSelection;
+
+	private List<RefSpec> displayedRefSpecs;
+
+	private PushOperationResult confirmedResult;
+
+	private PushResultTable resultPanel;
+
+	private Button requireUnchangedButton;
+
+	private Button showOnlyIfChanged;
+
+	public ConfirmationPage(final Repository local,
+			final RepositorySelectionPage repoPage,
+			final RefSpecPage refSpecPage) {
+		super(ConfirmationPage.class.getName());
+		this.local = local;
+		this.repoPage = repoPage;
+		this.refSpecPage = refSpecPage;
+
+		setTitle(UIText.ConfirmationPage_title);
+		setDescription(UIText.ConfirmationPage_description);
+
+		final SelectionChangeListener listener = new SelectionChangeListener() {
+			public void selectionChanged() {
+				checkPreviousPagesSelections();
+			}
+		};
+		repoPage.addSelectionListener(listener);
+		refSpecPage.addSelectionListener(listener);
+	}
+
+	public void createControl(final Composite parent) {
+		final Composite panel = new Composite(parent, SWT.NONE);
+		panel.setLayout(new GridLayout());
+
+		resultPanel = new PushResultTable(panel);
+		final Control tableControl = resultPanel.getControl();
+		tableControl
+				.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+		requireUnchangedButton = new Button(panel, SWT.CHECK);
+		requireUnchangedButton
+				.setText(UIText.ConfirmationPage_requireUnchangedButton);
+
+		showOnlyIfChanged = new Button(panel, SWT.CHECK);
+		showOnlyIfChanged.setText(UIText.ConfirmationPage_showOnlyIfChanged);
+
+		setControl(panel);
+	}
+
+	@Override
+	public void setVisible(final boolean visible) {
+		if (visible)
+			revalidate();
+		super.setVisible(visible);
+	}
+
+	boolean isConfirmed() {
+		return confirmedResult != null;
+	}
+
+	PushOperationResult getConfirmedResult() {
+		return confirmedResult;
+	}
+
+	boolean isRequireUnchangedSelected() {
+		return requireUnchangedButton.getSelection();
+	}
+
+	boolean isShowOnlyIfChangedSelected() {
+		return showOnlyIfChanged.getSelection();
+	}
+
+	private void checkPreviousPagesSelections() {
+		if (!repoPage.selectionEquals(displayedRepoSelection)
+				|| !refSpecPage.specsSelectionEquals(displayedRefSpecs)) {
+			// Allow user to finish by skipping confirmation...
+			setPageComplete(true);
+		} else {
+			// ... but if user doesn't skip confirmation, allow only when no
+			// critical errors occurred
+			setPageComplete(confirmedResult != null);
+		}
+	}
+
+	private void revalidate() {
+		// always update this page
+		resultPanel.setData(local, new PushOperationResult());
+		confirmedResult = null;
+		displayedRepoSelection = repoPage.getSelection();
+		displayedRefSpecs = refSpecPage.getRefSpecs();
+		setErrorMessage(null);
+		setPageComplete(false);
+		getControl().getDisplay().asyncExec(new Runnable() {
+			public void run() {
+				revalidateImpl();
+			}
+		});
+	}
+
+	private void revalidateImpl() {
+		if (getControl().isDisposed() || !isCurrentPage())
+			return;
+		
+		final List<RefSpec> fetchSpecs;
+		if (displayedRepoSelection.isConfigSelected())
+			fetchSpecs = displayedRepoSelection.getConfig().getFetchRefSpecs();
+		else
+			fetchSpecs = null;
+
+		final PushOperation operation;
+		try {
+			final Collection<RemoteRefUpdate> updates = Transport
+					.findRemoteRefUpdatesFor(local, displayedRefSpecs,
+							fetchSpecs);
+			if (updates.isEmpty()) {
+				// It can happen only when local refs changed in the mean time.
+				setErrorMessage(UIText.ConfirmationPage_errorRefsChangedNoMatch);
+				setPageComplete(false);
+				return;
+			}
+
+			final PushOperationSpecification spec = new PushOperationSpecification();
+			for (final URIish uri : displayedRepoSelection.getAllURIs())
+				spec.addURIRefUpdates(uri, copyUpdates(updates));
+
+			operation = new PushOperation(local, spec, true,
+					displayedRepoSelection.getConfig());
+			getContainer().run(true, true, operation);
+		} catch (final IOException e) {
+			setErrorMessage(NLS.bind(
+					UIText.ConfirmationPage_errorCantResolveSpecs, e
+							.getMessage()));
+			return;
+		} catch (final InvocationTargetException e) {
+			setErrorMessage(NLS.bind(UIText.ConfirmationPage_errorUnexpected, e
+					.getCause().getMessage()));
+			return;
+		} catch (final InterruptedException e) {
+			setErrorMessage(UIText.ConfirmationPage_errorInterrupted);
+			setPageComplete(true);
+			displayedRefSpecs = null;
+			displayedRepoSelection = null;
+			return;
+		}
+
+		final PushOperationResult result = operation.getOperationResult();
+		resultPanel.setData(local, result);
+		if (result.isSuccessfulConnectionForAnyURI()) {
+			setPageComplete(true);
+			confirmedResult = result;
+		} else {
+			setErrorMessage(NLS.bind(UIText.ConfirmationPage_cantConnectToAny,
+					result.getErrorStringForAllURis()));
+		}
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushResultTable.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushResultTable.java
new file mode 100644
index 0000000..5daf153
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushResultTable.java
@@ -0,0 +1,327 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.push;
+
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.resource.ColorRegistry;
+import org.eclipse.jface.resource.ImageRegistry;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.IElementComparer;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.spearce.egit.core.op.PushOperationResult;
+import org.spearce.egit.ui.UIIcons;
+import org.spearce.egit.ui.UIText;
+import org.spearce.egit.ui.internal.components.CenteredImageLabelProvider;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.transport.RemoteRefUpdate;
+import org.spearce.jgit.transport.URIish;
+import org.spearce.jgit.transport.RemoteRefUpdate.Status;
+
+/**
+ * Table displaying push operation results.
+ */
+class PushResultTable {
+	private static final int TABLE_PREFERRED_WIDTH = 650;
+
+	private static final int TABLE_PREFERRED_HEIGHT = 300;
+
+	private static final int COLUMN_STATUS_WEIGHT = 40;
+
+	private static final int COLUMN_DST_WEIGHT = 40;
+
+	private static final int COLUMN_SRC_WEIGHT = 40;
+
+	private static final int COLUMN_MODE_WEIGHT = 15;
+
+	private static final String IMAGE_DELETE = "MODE_DELETE";
+
+	private static final String IMAGE_ADD = "MODE_ADD";
+
+	private static final String COLOR_REJECTED_KEY = "REJECTED";
+
+	private static final RGB COLOR_REJECTED = new RGB(255, 0, 0);
+
+	private static final String COLOR_UPDATED_KEY = "UPDATED";
+
+	private static final RGB COLOR_UPDATED = new RGB(0, 255, 0);
+
+	private static final String COLOR_UP_TO_DATE_KEY = "UP_TO_DATE";
+
+	private static final RGB COLOR_UP_TO_DATE = new RGB(245, 245, 245);
+
+	private final TableViewer tableViewer;
+
+	private final Composite tablePanel;
+
+	private final ImageRegistry imageRegistry;
+
+	private final ColorRegistry colorRegistry;
+
+	PushResultTable(final Composite parent) {
+		tablePanel = new Composite(parent, SWT.NONE);
+		tablePanel.setLayout(new GridLayout());
+		final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
+		layoutData.heightHint = TABLE_PREFERRED_HEIGHT;
+		layoutData.widthHint = TABLE_PREFERRED_WIDTH;
+		tableViewer = new TableViewer(tablePanel);
+		ColumnViewerToolTipSupport.enableFor(tableViewer);
+		final Table table = tableViewer.getTable();
+		table.setLinesVisible(true);
+		table.setHeaderVisible(true);
+
+		imageRegistry = new ImageRegistry(table.getDisplay());
+		imageRegistry.put(IMAGE_ADD, UIIcons.ELCL16_ADD);
+		imageRegistry.put(IMAGE_DELETE, UIIcons.ELCL16_DELETE);
+
+		colorRegistry = new ColorRegistry(table.getDisplay());
+		colorRegistry.put(COLOR_REJECTED_KEY, COLOR_REJECTED);
+		colorRegistry.put(COLOR_UPDATED_KEY, COLOR_UPDATED);
+		colorRegistry.put(COLOR_UP_TO_DATE_KEY, COLOR_UP_TO_DATE);
+
+		tablePanel.addDisposeListener(new DisposeListener() {
+			public void widgetDisposed(DisposeEvent e) {
+				imageRegistry.dispose();
+			}
+		});
+
+		tableViewer.setComparer(new IElementComparer() {
+			// we need this to keep refresh() working while having custom
+			// equals() in PushOperationResult
+			public boolean equals(Object a, Object b) {
+				return a == b;
+			}
+
+			public int hashCode(Object element) {
+				return element.hashCode();
+			}
+		});
+		tableViewer.setContentProvider(new RefUpdateContentProvider());
+		tableViewer.setInput(new PushOperationResult());
+	}
+
+	void setData(final Repository localDb, final PushOperationResult result) {
+		// We have to recreate columns.
+		for (final TableColumn tc : tableViewer.getTable().getColumns())
+			tc.dispose();
+		// Set empty result for a while.
+		tableViewer.setInput(new PushOperationResult());
+
+		// Layout should be recreated to work properly.
+		final TableColumnLayout layout = new TableColumnLayout();
+		tablePanel.setLayout(layout);
+
+		final TableViewerColumn modeViewer = createColumn(layout,
+				UIText.PushResultTable_columnMode, COLUMN_MODE_WEIGHT,
+				SWT.CENTER);
+		modeViewer.setLabelProvider(new CenteredImageLabelProvider() {
+			@Override
+			public Image getImage(Object element) {
+				if (((RefUpdateElement) element).isDelete())
+					return imageRegistry.get(IMAGE_DELETE);
+				return imageRegistry.get(IMAGE_ADD);
+			}
+
+			@Override
+			public String getToolTipText(Object element) {
+				if (((RefUpdateElement) element).isDelete())
+					return UIText.RefSpecPanel_modeDeleteDescription;
+				return UIText.RefSpecPanel_modeUpdateDescription;
+			}
+		});
+
+		final TableViewerColumn srcViewer = createColumn(layout,
+				UIText.PushResultTable_columnSrc, COLUMN_SRC_WEIGHT, SWT.LEFT);
+		srcViewer.setLabelProvider(new ColumnLabelProvider() {
+			@Override
+			public String getText(Object element) {
+				return ((RefUpdateElement) element).getSrcRefName();
+			}
+		});
+
+		final TableViewerColumn dstViewer = createColumn(layout,
+				UIText.PushResultTable_columnDst, COLUMN_DST_WEIGHT, SWT.LEFT);
+		dstViewer.setLabelProvider(new ColumnLabelProvider() {
+			@Override
+			public String getText(Object element) {
+				return ((RefUpdateElement) element).getDstRefName();
+			}
+		});
+
+		int i = 0;
+		for (final URIish uri : result.getURIs()) {
+			final TableViewerColumn statusViewer = createColumn(layout, NLS
+					.bind(UIText.PushResultTable_columnStatusRepo, Integer
+							.toString(++i)), COLUMN_STATUS_WEIGHT, SWT.CENTER);
+			statusViewer.getColumn().setToolTipText(uri.toString());
+			statusViewer.setLabelProvider(new UpdateStatusLabelProvider(
+					localDb, uri));
+		}
+		tableViewer.setInput(result);
+		tablePanel.layout();
+	}
+
+	Control getControl() {
+		return tablePanel;
+	}
+
+	private TableViewerColumn createColumn(
+			final TableColumnLayout columnLayout, final String text,
+			final int weight, final int style) {
+		final TableViewerColumn viewerColumn = new TableViewerColumn(
+				tableViewer, style);
+		final TableColumn column = viewerColumn.getColumn();
+		column.setText(text);
+		columnLayout.setColumnData(column, new ColumnWeightData(weight));
+		return viewerColumn;
+	}
+
+	private class UpdateStatusLabelProvider extends ColumnLabelProvider {
+		private Repository localDb;
+
+		private final URIish uri;
+
+		UpdateStatusLabelProvider(final Repository localDb, final URIish uri) {
+			this.localDb = localDb;
+			this.uri = uri;
+		}
+
+		@Override
+		public String getText(Object element) {
+			final RefUpdateElement rue = (RefUpdateElement) element;
+			if (!rue.isSuccessfulConnection(uri))
+				return UIText.PushResultTable_statusConnectionFailed;
+
+			final RemoteRefUpdate rru = rue.getRemoteRefUpdate(uri);
+			switch (rru.getStatus()) {
+			case OK:
+				if (rru.isDelete())
+					return UIText.PushResultTable_statusOkDeleted;
+
+				final Ref oldRef = rue.getAdvertisedRemoteRef(uri);
+				if (oldRef == null) {
+					if (rue.getDstRefName().startsWith(
+							Constants.TAGS_PREFIX + "/"))
+						return UIText.PushResultTable_statusOkNewTag;
+					return UIText.PushResultTable_statusOkNewBranch;
+				}
+
+				return oldRef.getObjectId().abbreviate(localDb)
+						+ (rru.isFastForward() ? ".." : "...")
+						+ rru.getNewObjectId().abbreviate(localDb);
+			case UP_TO_DATE:
+				return UIText.PushResultTable_statusUpToDate;
+			case NON_EXISTING:
+				return UIText.PushResultTable_statusNoMatch;
+			case REJECTED_NODELETE:
+			case REJECTED_NONFASTFORWARD:
+			case REJECTED_REMOTE_CHANGED:
+				return UIText.PushResultTable_statusRejected;
+			case REJECTED_OTHER_REASON:
+				return UIText.PushResultTable_statusRemoteRejected;
+			default:
+				throw new IllegalArgumentException(NLS.bind(
+						UIText.PushResultTable_statusUnexpected, rru
+								.getStatus()));
+			}
+		}
+
+		@Override
+		public Color getBackground(Object element) {
+			final RefUpdateElement rue = (RefUpdateElement) element;
+			if (!rue.isSuccessfulConnection(uri))
+				return colorRegistry.get(COLOR_REJECTED_KEY);
+
+			final Status status = rue.getRemoteRefUpdate(uri).getStatus();
+			switch (status) {
+			case OK:
+				return colorRegistry.get(COLOR_UPDATED_KEY);
+			case UP_TO_DATE:
+			case NON_EXISTING:
+				return colorRegistry.get(COLOR_UP_TO_DATE_KEY);
+			case REJECTED_NODELETE:
+			case REJECTED_NONFASTFORWARD:
+			case REJECTED_REMOTE_CHANGED:
+			case REJECTED_OTHER_REASON:
+				return colorRegistry.get(COLOR_REJECTED_KEY);
+			default:
+				throw new IllegalArgumentException(NLS.bind(
+						UIText.PushResultTable_statusUnexpected, status));
+			}
+		}
+
+		@Override
+		public String getToolTipText(Object element) {
+			final RefUpdateElement rue = (RefUpdateElement) element;
+			if (!rue.isSuccessfulConnection(uri))
+				return rue.getErrorMessage(uri);
+
+			final RemoteRefUpdate rru = rue.getRemoteRefUpdate(uri);
+			final Ref oldRef = rue.getAdvertisedRemoteRef(uri);
+			switch (rru.getStatus()) {
+			case OK:
+				if (rru.isDelete())
+					return NLS.bind(UIText.PushResultTable_statusDetailDeleted,
+							oldRef.getObjectId().abbreviate(localDb));
+				if (oldRef == null)
+					return null;
+				if (rru.isFastForward())
+					return UIText.PushResultTable_statusDetailFastForward;
+				return UIText.PushResultTable_statusDetailForcedUpdate;
+			case UP_TO_DATE:
+				return null;
+			case NON_EXISTING:
+				return UIText.PushResultTable_statusDetailNonExisting;
+			case REJECTED_NODELETE:
+				return UIText.PushResultTable_statusDetailNoDelete;
+			case REJECTED_NONFASTFORWARD:
+				return UIText.PushResultTable_statusDetailNonFastForward;
+			case REJECTED_REMOTE_CHANGED:
+				final Ref remoteRef = oldRef;
+				final String currentValue;
+				if (remoteRef == null)
+					currentValue = UIText.PushResultTable_refNonExisting;
+				else
+					currentValue = remoteRef.getObjectId().abbreviate(localDb);
+				final ObjectId expectedOldObjectId = rru
+						.getExpectedOldObjectId();
+				final String expectedValue;
+				if (expectedOldObjectId.equals(ObjectId.zeroId()))
+					expectedValue = UIText.PushResultTable_refNonExisting;
+				else
+					expectedValue = expectedOldObjectId.abbreviate(localDb);
+				return NLS.bind(UIText.PushResultTable_statusDetailChanged,
+						currentValue, expectedValue);
+			case REJECTED_OTHER_REASON:
+				return rru.getMessage();
+			default:
+				throw new IllegalArgumentException(NLS.bind(
+						UIText.PushResultTable_statusUnexpected, rru
+								.getStatus()));
+			}
+		}
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushWizard.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushWizard.java
new file mode 100644
index 0000000..44060df
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/PushWizard.java
@@ -0,0 +1,250 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.push;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.PlatformUI;
+import org.spearce.egit.core.op.PushOperation;
+import org.spearce.egit.core.op.PushOperationResult;
+import org.spearce.egit.core.op.PushOperationSpecification;
+import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIIcons;
+import org.spearce.egit.ui.UIText;
+import org.spearce.egit.ui.internal.components.RefSpecPage;
+import org.spearce.egit.ui.internal.components.RepositorySelection;
+import org.spearce.egit.ui.internal.components.RepositorySelectionPage;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.RepositoryConfig;
+import org.spearce.jgit.transport.RefSpec;
+import org.spearce.jgit.transport.RemoteConfig;
+import org.spearce.jgit.transport.RemoteRefUpdate;
+import org.spearce.jgit.transport.Transport;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * Wizard allowing user to specify all needed data to push to another repository
+ * - including selection of remote repository and refs specifications.
+ * <p>
+ * Push operation is performed upon successful completion of this wizard.
+ */
+public class PushWizard extends Wizard {
+	private static String getURIsString(final Collection<URIish> uris) {
+		final StringBuilder sb = new StringBuilder();
+		boolean first = true;
+		for (final URIish uri : uris) {
+			if (first)
+				first = false;
+			else
+				sb.append(", "); //$NON-NLS-1$
+			sb.append(uri);
+		}
+		return sb.toString();
+	}
+
+	private Repository localDb;
+
+	private final RepositorySelectionPage repoPage;
+
+	private final RefSpecPage refSpecPage;
+
+	private ConfirmationPage confirmPage;
+
+	/**
+	 * Create push wizard for specified local repository.
+	 * 
+	 * @param localDb
+	 *            repository to push from.
+	 * @throws URISyntaxException
+	 *             when configuration of this repository contains illegal URIs.
+	 */
+	public PushWizard(final Repository localDb) throws URISyntaxException {
+		this.localDb = localDb;
+		final List<RemoteConfig> remotes = RemoteConfig
+				.getAllRemoteConfigs(localDb.getConfig());
+		repoPage = new RepositorySelectionPage(false, remotes);
+		refSpecPage = new RefSpecPage(localDb, true, repoPage);
+		confirmPage = new ConfirmationPage(localDb, repoPage, refSpecPage);
+		// TODO use/create another cool icon
+		setDefaultPageImageDescriptor(UIIcons.WIZBAN_IMPORT_REPO);
+		setNeedsProgressMonitor(true);
+	}
+
+	@Override
+	public void addPages() {
+		addPage(repoPage);
+		addPage(refSpecPage);
+		addPage(confirmPage);
+	}
+
+	@Override
+	public boolean performFinish() {
+		if (repoPage.getSelection().isConfigSelected()
+				&& refSpecPage.isSaveRequested()) {
+			saveRefSpecs();
+		}
+
+		final PushOperation operation = createPushOperation();
+		if (operation == null)
+			return false;
+		final PushOperationResult resultToCompare;
+		if (confirmPage.isShowOnlyIfChangedSelected())
+			resultToCompare = confirmPage.getConfirmedResult();
+		else
+			resultToCompare = null;
+		final Job job = new PushJob(operation, resultToCompare,
+				getDestinationString());
+
+		job.setUser(true);
+		job.schedule();
+
+		return true;
+	}
+
+	@Override
+	public String getWindowTitle() {
+		final IWizardPage currentPage = getContainer().getCurrentPage();
+		if (currentPage == repoPage || currentPage == null)
+			return UIText.PushWizard_windowTitleDefault;
+		final String destination = getDestinationString();
+		return NLS.bind(UIText.PushWizard_windowTitleWithDestination,
+				destination);
+	}
+
+	private void saveRefSpecs() {
+		final RemoteConfig rc = repoPage.getSelection().getConfig();
+		rc.setPushRefSpecs(refSpecPage.getRefSpecs());
+		final RepositoryConfig config = localDb.getConfig();
+		rc.update(config);
+		try {
+			config.save();
+		} catch (final IOException e) {
+			ErrorDialog.openError(getShell(), UIText.PushWizard_cantSaveTitle,
+					UIText.PushWizard_cantSaveMessage, new Status(
+							IStatus.WARNING, Activator.getPluginId(), e
+									.getMessage(), e));
+			// Continue, it's not critical.
+		}
+	}
+
+	private PushOperation createPushOperation() {
+		try {
+			final PushOperationSpecification spec;
+			final RemoteConfig config = repoPage.getSelection().getConfig();
+			if (confirmPage.isConfirmed()) {
+				final PushOperationResult confirmedResult = confirmPage
+						.getConfirmedResult();
+				spec = confirmedResult.deriveSpecification(confirmPage
+						.isRequireUnchangedSelected());
+			} else {
+				final Collection<RefSpec> fetchSpecs;
+				if (config != null)
+					fetchSpecs = config.getFetchRefSpecs();
+				else
+					fetchSpecs = null;
+
+				final Collection<RemoteRefUpdate> updates = Transport
+						.findRemoteRefUpdatesFor(localDb, refSpecPage
+								.getRefSpecs(), fetchSpecs);
+				if (updates.isEmpty()) {
+					ErrorDialog.openError(getShell(),
+							UIText.PushWizard_missingRefsTitle, null,
+							new Status(IStatus.ERROR, Activator.getPluginId(),
+									UIText.PushWizard_missingRefsMessage));
+					return null;
+				}
+
+				spec = new PushOperationSpecification();
+				for (final URIish uri : repoPage.getSelection().getAllURIs())
+					spec.addURIRefUpdates(uri, ConfirmationPage
+							.copyUpdates(updates));
+			}
+			return new PushOperation(localDb, spec, false, config);
+		} catch (final IOException e) {
+			ErrorDialog.openError(getShell(),
+					UIText.PushWizard_cantPrepareUpdatesTitle,
+					UIText.PushWizard_cantPrepareUpdatesMessage, new Status(
+							IStatus.ERROR, Activator.getPluginId(), e
+									.getMessage(), e));
+			return null;
+		}
+	}
+
+	private String getDestinationString() {
+		final RepositorySelection repoSelection = repoPage.getSelection();
+		final String destination;
+		if (repoSelection.isConfigSelected())
+			destination = repoSelection.getConfigName();
+		else
+			destination = repoSelection.getURI().toString();
+		return destination;
+	}
+
+	private class PushJob extends Job {
+		private final PushOperation operation;
+
+		private final PushOperationResult resultToCompare;
+
+		private final String destinationString;
+
+		public PushJob(final PushOperation operation,
+				final PushOperationResult resultToCompare,
+				final String destinationString) {
+			super(NLS.bind(UIText.PushWizard_jobName, getURIsString(operation
+					.getSpecification().getURIs())));
+			this.operation = operation;
+			this.resultToCompare = resultToCompare;
+			this.destinationString = destinationString;
+		}
+
+		@Override
+		protected IStatus run(final IProgressMonitor monitor) {
+			try {
+				operation.run(monitor);
+			} catch (final InvocationTargetException e) {
+				return new Status(IStatus.ERROR, Activator.getPluginId(),
+						UIText.PushWizard_unexpectedError, e.getCause());
+			}
+
+			final PushOperationResult result = operation.getOperationResult();
+			if (!result.isSuccessfulConnectionForAnyURI()) {
+				return new Status(IStatus.ERROR, Activator.getPluginId(), NLS
+						.bind(UIText.PushWizard_cantConnectToAny, result
+								.getErrorStringForAllURis()));
+			}
+
+			if (resultToCompare == null || !result.equals(resultToCompare)) {
+				PlatformUI.getWorkbench().getDisplay().asyncExec(
+						new Runnable() {
+							public void run() {
+								final Shell shell = PlatformUI.getWorkbench()
+										.getActiveWorkbenchWindow().getShell();
+								final Dialog dialog = new ResultDialog(shell,
+										localDb, result, destinationString);
+								dialog.open();
+							}
+						});
+			}
+			return Status.OK_STATUS;
+		}
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateContentProvider.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateContentProvider.java
new file mode 100644
index 0000000..ccc5340
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateContentProvider.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.push;
+
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+import org.spearce.egit.core.op.PushOperationResult;
+import org.spearce.jgit.transport.RemoteRefUpdate;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * Content provided for push result table viewer.
+ * <p>
+ * Input of this provided must be {@link PushOperationResult} instance, while
+ * returned elements are instances of {@link RefUpdateElement}.
+ * 
+ * @see PushOperationResult
+ * @see RefUpdateElement
+ */
+class RefUpdateContentProvider implements IStructuredContentProvider {
+	public Object[] getElements(final Object inputElement) {
+		final PushOperationResult result = (PushOperationResult) inputElement;
+
+		final SortedMap<String, String> dstToSrc = new TreeMap<String, String>();
+		for (final URIish uri : result.getURIs()) {
+			if (result.isSuccessfulConnection(uri)) {
+				for (final RemoteRefUpdate rru : result.getPushResult(uri)
+						.getRemoteUpdates())
+					dstToSrc.put(rru.getRemoteName(), rru.getSrcRef());
+				// Assuming that each repository received the same ref updates,
+				// we need only one to get these ref names.
+				break;
+			}
+		}
+
+		// Transforming PushOperationResult model to row-wise one.
+		final RefUpdateElement elements[] = new RefUpdateElement[dstToSrc
+				.size()];
+		int i = 0;
+		for (final Entry<String, String> entry : dstToSrc.entrySet())
+			elements[i++] = new RefUpdateElement(result, entry.getValue(),
+					entry.getKey());
+		return elements;
+	}
+
+	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+		// nothing to do
+	}
+
+	public void dispose() {
+		// nothing to dispose
+	}
+}
\ No newline at end of file
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateElement.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateElement.java
new file mode 100644
index 0000000..7b9bce0
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/RefUpdateElement.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.push;
+
+import org.spearce.egit.core.op.PushOperationResult;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.transport.RemoteRefUpdate;
+import org.spearce.jgit.transport.URIish;
+
+/**
+ * Data class representing row (element) of table with push results.
+ * <p>
+ * Each row is associated with one ref update, while each column is associated
+ * with one URI (remote repository).
+ * 
+ * @see PushOperationResult
+ * @see RefUpdateContentProvider
+ */
+class RefUpdateElement {
+	private final String srcRefName;
+
+	private final String dstRefName;
+
+	private final PushOperationResult result;
+
+	RefUpdateElement(final PushOperationResult result, final String srcRef,
+			final String dstRef) {
+		this.result = result;
+		this.srcRefName = srcRef;
+		this.dstRefName = dstRef;
+	}
+
+	String getSrcRefName() {
+		return srcRefName;
+	}
+
+	String getDstRefName() {
+		return dstRefName;
+	}
+
+	boolean isDelete() {
+		// Assuming that we never use ObjectId.zeroId() in GUI.
+		// (no need to compare to it).
+		return srcRefName == null;
+	}
+	
+	boolean isSuccessfulConnection(final URIish uri) {
+		return result.isSuccessfulConnection(uri);
+	}
+
+	String getErrorMessage(final URIish uri) {
+		return result.getErrorMessage(uri);
+	}
+
+	RemoteRefUpdate getRemoteRefUpdate(final URIish uri) {
+		return result.getPushResult(uri).getRemoteUpdate(dstRefName);
+	}
+
+	Ref getAdvertisedRemoteRef(final URIish uri) {
+		return result.getPushResult(uri).getAdvertisedRef(dstRefName);
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ResultDialog.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ResultDialog.java
new file mode 100644
index 0000000..920494f
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/push/ResultDialog.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.push;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.spearce.egit.core.op.PushOperationResult;
+import org.spearce.egit.ui.UIText;
+import org.spearce.jgit.lib.Repository;
+
+class ResultDialog extends Dialog {
+	private final Repository localDb;
+
+	private final PushOperationResult result;
+
+	private final String destinationString;
+
+	ResultDialog(final Shell parentShell, final Repository localDb,
+			final PushOperationResult result, final String destinationString) {
+		super(parentShell);
+		setShellStyle(getShellStyle() | SWT.RESIZE);
+		this.localDb = localDb;
+		this.result = result;
+		this.destinationString = destinationString;
+	}
+
+	@Override
+	protected void createButtonsForButtonBar(final Composite parent) {
+		createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL,
+				true);
+	}
+
+	@Override
+	protected Control createDialogArea(final Composite parent) {
+		final Composite composite = (Composite) super.createDialogArea(parent);
+
+		final Label label = new Label(composite, SWT.NONE);
+		label.setText(NLS.bind(UIText.ResultDialog_label, destinationString));
+		label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		final PushResultTable table = new PushResultTable(composite);
+		table.setData(localDb, result);
+		final Control tableControl = table.getControl();
+		final GridData tableLayout = new GridData(SWT.FILL, SWT.FILL, true,
+				true);
+		tableLayout.widthHint = 650;
+		tableLayout.heightHint = 300;
+		tableControl.setLayoutData(tableLayout);
+
+		getShell().setText(
+				NLS.bind(UIText.ResultDialog_title, destinationString));
+		return composite;
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index f2be3c1..4b0aea6 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -179,6 +179,53 @@ HistoryPage_findbar_notFound=String not found
 
 HistoryPreferencePage_title=Git
 
+PushWizard_cantConnectToAny=Can't connect to any repository: {0}
+PushWizard_cantPrepareUpdatesMessage=Can't resolve ref specifications locally (local refs changed?) or create tracking ref update.
+PushWizard_cantPrepareUpdatesTitle=Preparing Ref Updates Error
+PushWizard_cantSaveMessage=Couldn't save specified specifications in configuration file.
+PushWizard_cantSaveTitle=Configuration Storage Warning
+PushWizard_jobName=Pushing to...{0}
+PushWizard_missingRefsMessage=Ref specifications don't match any source ref (local refs changed?).
+PushWizard_missingRefsTitle=Missing Refs Error
+PushWizard_unexpectedError=Unexpected error occurred.
+PushWizard_windowTitleDefault=Push To Another Repositories
+PushWizard_windowTitleWithDestination=Push To: {0}
+
+ConfirmationPage_cantConnectToAny=Can't connect to any URI: {0}
+ConfirmationPage_description=Confirm following expected push result.
+ConfirmationPage_errorCantResolveSpecs=Can't resolve ref specifications locally or create tracking ref update: {0} 
+ConfirmationPage_errorInterrupted=Operation was interrupted.
+ConfirmationPage_errorRefsChangedNoMatch=Local refs changed, ref specifications don't match any source ref.
+ConfirmationPage_errorUnexpected=Unexpected error occurred: {0}
+ConfirmationPage_requireUnchangedButton=Push only if remote refs don't change in the mean time
+ConfirmationPage_showOnlyIfChanged=Show final report dialog only when it differs from this confirmation report
+ConfirmationPage_title=Push Confirmation
+
+PushResultTable_columnStatusRepo=Status: Repo #{0}
+PushResultTable_columnDst=Destination Ref
+PushResultTable_columnSrc=Source Ref
+PushResultTable_columnMode=Mode
+PushResultTable_statusUnexpected=Unexpected update status: {0} 
+PushResultTable_statusConnectionFailed=[connection failed]
+PushResultTable_statusDetailChanged=remote ref object changed,\nnow it's: {0},\nnot expected: {1}
+PushResultTable_refNonExisting=(non existing)
+PushResultTable_statusDetailDeleted=old value: {0} 
+PushResultTable_statusDetailNonFastForward=non-fast forward
+PushResultTable_statusDetailNoDelete=remote side does not support deleting refs
+PushResultTable_statusDetailNonExisting=remote ref already does not exist
+PushResultTable_statusDetailForcedUpdate=forced update (non-fast forward)
+PushResultTable_statusDetailFastForward=fast forward
+PushResultTable_statusRemoteRejected=[remote rejected]
+PushResultTable_statusRejected=[rejected]
+PushResultTable_statusNoMatch=[no match]
+PushResultTable_statusUpToDate=[up to date]
+PushResultTable_statusOkDeleted=[deleted]
+PushResultTable_statusOkNewBranch=[new branch]
+PushResultTable_statusOkNewTag=[new tag]
+	
+ResultDialog_title=Push Results: {0}
+ResultDialog_label=Pushed to {0}.
+
 WindowCachePreferencePage_title=Git Window Cache
 WindowCachePreferencePage_packedGitWindowSize=Window size:
 WindowCachePreferencePage_packedGitLimit=Window cache limit:
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 08/31] Add dryRun option to Transport and console push
  2008-08-17 20:43               ` [EGIT PATCH 08/31] Add dryRun option to Transport and console push Marek Zawirski
  2008-08-17 20:43                 ` [EGIT PATCH 09/31] Extract Transport findRemoteRefUpdatesFor() as static method Marek Zawirski
@ 2008-08-19 16:28                 ` Shawn O. Pearce
  1 sibling, 0 replies; 62+ messages in thread
From: Shawn O. Pearce @ 2008-08-19 16:28 UTC (permalink / raw)
  To: Marek Zawirski; +Cc: robin.rosenberg, git

Marek Zawirski <marek.zawirski@gmail.com> wrote:
> Implementation of C Git --dry-run behavior for push operation.
> It allows investigating possible push result, while not performing real
> push operation - not updating remote refs.
 
> diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
> index a952309..f5b24c6 100644
> --- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
> +++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
> @@ -85,6 +85,9 @@ class Push extends TextBuiltin {
>  
>  	@Option(name = "--receive-pack", metaVar = "path")
>  	private String receivePack;
> +	
> +	@Option(name = "--dry-run")
> +	private boolean dryRun = Transport.DEFAULT_DRY_RUN;

Having a boolean constant DEFAULT_DRY_RUN = false is sort of
overkill in my opinion.  Most people would assume a boolean with
no value assigned and not marked final will default to false.
Setting something like dryRun to false by default is reasonable,
as you usually want it to actually execute.

What's worse though is if one day someone changes
Transport.DEFAULT_DRY_RUN = true in the library.  When that happens
the push command line tool will never function as there is no
"--no-dry-run" to set dryRun = false.

So this assignment is likely not a good idea.

-- 
Shawn.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 12/31] Add another RemoteRefUpdate constructor, useful for 2-stage push
  2008-08-17 20:43                       ` [EGIT PATCH 12/31] Add another RemoteRefUpdate constructor, useful for 2-stage push Marek Zawirski
  2008-08-17 20:43                         ` [EGIT PATCH 13/31] Add getAllRemoteConfigs() to RemoteConfig Marek Zawirski
@ 2008-08-19 16:45                         ` Shawn O. Pearce
  1 sibling, 0 replies; 62+ messages in thread
From: Shawn O. Pearce @ 2008-08-19 16:45 UTC (permalink / raw)
  To: Marek Zawirski; +Cc: robin.rosenberg, git

Marek Zawirski <marek.zawirski@gmail.com> wrote:
> New constructor base on existing RemoteRefUpdate instance, providing
> deep copy of object, but allowing change of expectedOldObjectId.
 
> @@ -133,6 +133,8 @@ public class RemoteRefUpdate {
>  
>  	private String message;
>  
> +	private final Repository db;
> +

I think we should document that this is the *local* (source)
repository and not the remote (dest) repository.

-- 
Shawn.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-17 20:43 [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Marek Zawirski
  2008-08-17 20:43 ` [EGIT PATCH 01/31] Fix Repository.mapObject() for missing objects Marek Zawirski
@ 2008-08-19 17:59 ` Shawn O. Pearce
  2008-08-19 19:21   ` Robin Rosenberg
  1 sibling, 1 reply; 62+ messages in thread
From: Shawn O. Pearce @ 2008-08-19 17:59 UTC (permalink / raw)
  To: Marek Zawirski; +Cc: robin.rosenberg, git

Marek Zawirski <marek.zawirski@gmail.com> wrote:
> Robin, Tor, I know that you were already contributing some graphics to
> egit. If someone of you would like to do some icon for push/fetch with
> pleasure, you are welcome. Otherwise I'll have to do some crappy icon
> instead ;) Another matter are checkboxes screenshots. I'm not sure
> about legality status of including them. Any ideas if/how we co use
> them or some another set that we can for sure?

So my office-mate suggests that the output of a program (in this
case the checkbox icon) isn't covered by the same copyright as the
program that created it.  So we may be OK.  Or we just use some
other sort of icon for the checkbox.  Maybe Tor would be able to
come up with something useful here?

Anyway, most of this series looks pretty good to me.  I found a
few other annoying bugs in JGit and EGit as a result of testing
this series, but they are unrelated to the series and have been
there all along.  So I may try to fix them in the coming weeks.
 
>  55 files changed, 6471 insertions(+), 981 deletions(-)

Heh.  Been busy, hmm?  ;-)

-- 
Shawn.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 28/31] Checkbox images/screenshots
  2008-08-17 20:44                                                       ` [EGIT PATCH 28/31] Checkbox images/screenshots Marek Zawirski
  2008-08-17 20:44                                                         ` [EGIT PATCH 29/31] Universal GUI for specifications edition: RefSpecPanel and related Marek Zawirski
@ 2008-08-19 18:24                                                         ` Robin Rosenberg
  1 sibling, 0 replies; 62+ messages in thread
From: Robin Rosenberg @ 2008-08-19 18:24 UTC (permalink / raw)
  To: Marek Zawirski; +Cc: spearce, git

söndagen den 17 augusti 2008 22.44.09 skrev Marek Zawirski:
> Screenshots of checkboxes in various states, made at QT with Plastik
> style. These images may be used as workaround in cases when we can
> only display images and handle buttons pressed, but can't display Button
> directly.
> 
> I'm somewhat uncertain about license issues regarding these images.
> Recently, I realized that they are LGPL licensed perhaps. Is it possible
> to license that small stuff? If so, we may have to replace them by some
> other images or create ours.

"Stealing" artwork is not good, though there are bordercases like this. It
is also the wrong look on my box which uses check marks instead of X.

Are all options exhauested wrt to using the native L&F, commented out in 
patch 31. For example toucher caching of generated images so mac users
would only see less flicker. I see no problems with GTK when using the
out-commented portion. Windows (XP) seems fine too. A really ugly, codewise,
would be to detect OSX and use custom images only for it until we find a
fix.

-- robin

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-19 17:59 ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Shawn O. Pearce
@ 2008-08-19 19:21   ` Robin Rosenberg
  2008-08-20  2:42     ` Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Robin Rosenberg @ 2008-08-19 19:21 UTC (permalink / raw)
  To: Shawn O. Pearce; +Cc: Marek Zawirski, git

tisdagen den 19 augusti 2008 19.59.31 skrev Shawn O. Pearce:
> Marek Zawirski <marek.zawirski@gmail.com> wrote:
> > Robin, Tor, I know that you were already contributing some graphics to
> > egit. If someone of you would like to do some icon for push/fetch with
> > pleasure, you are welcome. Otherwise I'll have to do some crappy icon
> > instead ;) Another matter are checkboxes screenshots. I'm not sure
> > about legality status of including them. Any ideas if/how we co use
> > them or some another set that we can for sure?
> 
> So my office-mate suggests that the output of a program (in this
> case the checkbox icon) isn't covered by the same copyright as the
> program that created it.  So we may be OK. 
It's not that simple. That obviously may be the case, but I'm certain
it does not apply to individual well defined pieces of artwork (or text for
that matter) included in the output. 

> Or we just use some 
> other sort of icon for the checkbox.  Maybe Tor would be able to
> come up with something useful here?
Most annoying is that we'd need one (four) for each LAF supported.

> 
> Anyway, most of this series looks pretty good to me.  I found a
> few other annoying bugs in JGit and EGit as a result of testing
> this series, but they are unrelated to the series and have been
> there all along.  So I may try to fix them in the coming weeks.
>  
> >  55 files changed, 6471 insertions(+), 981 deletions(-)
> 
> Heh.  Been busy, hmm?  ;-)

The weather in and around the Baltic Sea has been horrible for much
of August so far. Maybe that helped him. :)

-- robin

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-19 19:21   ` Robin Rosenberg
@ 2008-08-20  2:42     ` Marek Zawirski
  2008-08-20  2:57       ` [EGIT PATCH 1/6] Remove DEFAULT_DRY_RUN constant from Transport Marek Zawirski
                         ` (2 more replies)
  0 siblings, 3 replies; 62+ messages in thread
From: Marek Zawirski @ 2008-08-20  2:42 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: Shawn O. Pearce, git

Robin Rosenberg wrote:
> tisdagen den 19 augusti 2008 19.59.31 skrev Shawn O. Pearce:
>> Marek Zawirski <marek.zawirski@gmail.com> wrote:
>>> Robin, Tor, I know that you were already contributing some graphics to
>>> egit. If someone of you would like to do some icon for push/fetch with
>>> pleasure, you are welcome. Otherwise I'll have to do some crappy icon
>>> instead ;) Another matter are checkboxes screenshots. I'm not sure
>>> about legality status of including them. Any ideas if/how we co use
>>> them or some another set that we can for sure?
>> So my office-mate suggests that the output of a program (in this
>> case the checkbox icon) isn't covered by the same copyright as the
>> program that created it.  So we may be OK. 
> It's not that simple. That obviously may be the case, but I'm certain
> it does not apply to individual well defined pieces of artwork (or text for
> that matter) included in the output. 
> 
>> Or we just use some 
>> other sort of icon for the checkbox.  Maybe Tor would be able to
>> come up with something useful here?
> Most annoying is that we'd need one (four) for each LAF supported.

Well, consider that they are even more of them. GTK+ has configurable 
themes (in my case it is delegation to QT with Plastik theme, where 
screenshot was made). I don't know about other platforms, but always 
having hyper-UI Mac OS X and Windows Vista may also have some 
customization options.
No matter how icons we would prepare, always there will be somebody who 
can say that these icons are somehow inconsistent with his/her LAF.

So I think, it'd better to have this mentioned ugly code workaround just 
for Mac OS (only there flashing window is so annoying). Less people 
suffering.

Or as Shawn said, we can try to make our own funny icon for Force Update 
(red "F"-enabled and gray "F"-disabled?) that is equally (in)consistent 
for everybody.

>> Anyway, most of this series looks pretty good to me.  I found a
>> few other annoying bugs in JGit and EGit as a result of testing
>> this series, but they are unrelated to the series and have been
>> there all along.  So I may try to fix them in the coming weeks.

Nice testing then, as I have spend hours on testing UI tricks and 
haven't noticed more bugs;) Beside of some indexing problems in old API.

>>>  55 files changed, 6471 insertions(+), 981 deletions(-)
>> Heh.  Been busy, hmm?  ;-)
> 
> The weather in and around the Baltic Sea has been horrible for much
> of August so far. Maybe that helped him. :)

I swear guys, I wasn't sailing for over 1 month;)
Actually, I'm now leaving for few days, so I'm sending out just fixes 
for issues pointed out by Shawn and found by me in the mean time. I'll 
squash these patches with existing commits in push branch, as it is 
probably worth nothing to keep this in history.

Fetch UI and dynamic team menu entries for push and fetch (for 
configured remotes) are underway, I'll come back to them after coming 
back home.

BTW, as another developers are getting involved in jgit/egit coding, 
maybe we could use (update) some wiki page for marking who is working on 
some topic currently? Now it's not obvious for me, and as we're not so 
numerous it would be pity to waste our time and do some redundant stuff 
one day.

-- 
Marek Zawirski [zawir]
marek.zawirski@gmail.com

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [EGIT PATCH 1/6] Remove DEFAULT_DRY_RUN constant from Transport
  2008-08-20  2:42     ` Marek Zawirski
@ 2008-08-20  2:57       ` Marek Zawirski
  2008-08-20  2:57         ` [EGIT PATCH 2/6] Emphasize that db is a local one in RemoteRefUpdate Marek Zawirski
  2008-08-20 14:13       ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Shawn O. Pearce
  2008-08-21 20:12       ` Robin Rosenberg
  2 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-20  2:57 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

This constant was pointless as changing it would brake code in the same
way as changing hardcoded value instead.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/jgit/pgm/Push.java             |    2 +-
 .../src/org/spearce/jgit/transport/Transport.java  |    7 +------
 2 files changed, 2 insertions(+), 7 deletions(-)

diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
index b61071c..d36bf4f 100644
--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java
@@ -87,7 +87,7 @@ void nothin(final boolean ignored) {
 	private String receivePack;
 	
 	@Option(name = "--dry-run")
-	private boolean dryRun = Transport.DEFAULT_DRY_RUN;
+	private boolean dryRun;
 
 	private boolean shownURI;
 
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
index 939347e..ca68ca6 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java
@@ -313,11 +313,6 @@ private static String findTrackingRefName(final String remoteName,
 	public static final RefSpec REFSPEC_PUSH_ALL = new RefSpec(
 			"refs/heads/*:refs/heads/*");
 
-	/**
-	 * Default setting for {@link #dryRun} option.
-	 */
-	public static final boolean DEFAULT_DRY_RUN = false;
-
 	/** The repository this transport fetches into, or pushes out of. */
 	protected final Repository local;
 
@@ -354,7 +349,7 @@ private static String findTrackingRefName(final String remoteName,
 	private boolean pushThin = DEFAULT_PUSH_THIN;
 
 	/** Should push just check for operation result, not really push. */
-	private boolean dryRun = DEFAULT_DRY_RUN;
+	private boolean dryRun;
 
 	/**
 	 * Create a new transport instance.
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 2/6] Emphasize that db is a local one in RemoteRefUpdate
  2008-08-20  2:57       ` [EGIT PATCH 1/6] Remove DEFAULT_DRY_RUN constant from Transport Marek Zawirski
@ 2008-08-20  2:57         ` Marek Zawirski
  2008-08-20  2:57           ` [EGIT PATCH 3/6] Handle URIs parsing errors in PushAction better Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-20  2:57 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../spearce/jgit/transport/RemoteRefUpdate.java    |   20 ++++++++++----------
 1 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java
index 42588c1..66fe6a1 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteRefUpdate.java
@@ -133,15 +133,15 @@
 
 	private String message;
 
-	private final Repository db;
+	private final Repository localDb;
 
 	/**
 	 * Construct remote ref update request by providing an update specification.
 	 * Object is created with default {@link Status#NOT_ATTEMPTED} status and no
 	 * message.
-	 *
-	 * @param db
-	 *            repository to push from.
+	 * 
+	 * @param localDb
+	 *            local repository to push from.
 	 * @param srcRef
 	 *            source revision - any string resolvable by
 	 *            {@link Repository#resolve(String)}. This resolves to the new
@@ -173,26 +173,26 @@
 	 * @throws IllegalArgumentException
 	 *             if some required parameter was null
 	 */
-	public RemoteRefUpdate(final Repository db, final String srcRef,
+	public RemoteRefUpdate(final Repository localDb, final String srcRef,
 			final String remoteName, final boolean forceUpdate,
 			final String localName, final ObjectId expectedOldObjectId)
 			throws IOException {
 		if (remoteName == null)
 			throw new IllegalArgumentException("Remote name can't be null.");
 		this.srcRef = srcRef;
-		this.newObjectId = (srcRef == null ? ObjectId.zeroId() : db
+		this.newObjectId = (srcRef == null ? ObjectId.zeroId() : localDb
 				.resolve(srcRef));
 		if (newObjectId == null)
 			throw new IOException("Source ref " + srcRef
 					+ " doesn't resolve to any object.");
 		this.remoteName = remoteName;
 		this.forceUpdate = forceUpdate;
-		if (localName != null && db != null)
-			trackingRefUpdate = new TrackingRefUpdate(db, localName,
+		if (localName != null && localDb != null)
+			trackingRefUpdate = new TrackingRefUpdate(localDb, localName,
 					remoteName, forceUpdate, newObjectId, "push");
 		else
 			trackingRefUpdate = null;
-		this.db = db;
+		this.localDb = localDb;
 		this.expectedOldObjectId = expectedOldObjectId;
 		this.status = Status.NOT_ATTEMPTED;
 	}
@@ -215,7 +215,7 @@ public RemoteRefUpdate(final Repository db, final String srcRef,
 	 */
 	public RemoteRefUpdate(final RemoteRefUpdate base,
 			final ObjectId newExpectedOldObjectId) throws IOException {
-		this(base.db, base.srcRef, base.remoteName, base.forceUpdate,
+		this(base.localDb, base.srcRef, base.remoteName, base.forceUpdate,
 				(base.trackingRefUpdate == null ? null : base.trackingRefUpdate
 						.getLocalName()), newExpectedOldObjectId);
 	}
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 3/6] Handle URIs parsing errors in PushAction better
  2008-08-20  2:57         ` [EGIT PATCH 2/6] Emphasize that db is a local one in RemoteRefUpdate Marek Zawirski
@ 2008-08-20  2:57           ` Marek Zawirski
  2008-08-20  2:57             ` [EGIT PATCH 4/6] Fix proposal provider for fetch in RefSpecPanel Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-20  2:57 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

ErrorDialog is used instead of MessageDialog (more information), strings
are externalized.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../src/org/spearce/egit/ui/UIText.java            |    6 ++++++
 .../egit/ui/internal/actions/PushAction.java       |   13 +++++++++----
 .../src/org/spearce/egit/ui/uitext.properties      |    3 +++
 3 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index cc785f7..b45d2e9 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -464,6 +464,12 @@
 	public static String HistoryPreferencePage_title;
 
 	/** */
+	public static String PushAction_wrongURIDescription;
+
+	/** */
+	public static String PushAction_wrongURITitle;
+
+	/** */
 	public static String PushWizard_cantConnectToAny;
 
 	/** */
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/PushAction.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/PushAction.java
index 61833d0..b4af3b5 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/PushAction.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/PushAction.java
@@ -9,9 +9,13 @@
 
 import java.net.URISyntaxException;
 
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.action.IAction;
-import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.wizard.WizardDialog;
+import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIText;
 import org.spearce.egit.ui.internal.push.PushWizard;
 import org.spearce.jgit.lib.Repository;
 
@@ -31,9 +35,10 @@ public void run(IAction action) {
 		try {
 			pushWizard = new PushWizard(repository);
 		} catch (URISyntaxException x) {
-			MessageDialog.openError(getShell(), "Corrupted configuration",
-					"Remote repositories URLs configuration is corrupted: "
-							+ x.getMessage());
+			ErrorDialog.openError(getShell(), UIText.PushAction_wrongURITitle,
+					UIText.PushAction_wrongURIDescription, new Status(
+							IStatus.ERROR, Activator.getPluginId(), x
+									.getMessage(), x));
 			return;
 		}
 		final WizardDialog dialog = new WizardDialog(getShell(), pushWizard);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index 4b0aea6..83be622 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -179,6 +179,9 @@ HistoryPage_findbar_notFound=String not found
 
 HistoryPreferencePage_title=Git
 
+PushAction_wrongURIDescription=Remote repositories URIs configuration is corrupted.
+PushAction_wrongURITitle=Corrupted Configuration
+
 PushWizard_cantConnectToAny=Can't connect to any repository: {0}
 PushWizard_cantPrepareUpdatesMessage=Can't resolve ref specifications locally (local refs changed?) or create tracking ref update.
 PushWizard_cantPrepareUpdatesTitle=Preparing Ref Updates Error
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 4/6] Fix proposal provider for fetch in RefSpecPanel
  2008-08-20  2:57           ` [EGIT PATCH 3/6] Handle URIs parsing errors in PushAction better Marek Zawirski
@ 2008-08-20  2:57             ` Marek Zawirski
  2008-08-20  2:57               ` [EGIT PATCH 5/6] Fix disappearing "save configuration" label in RefSpecPage Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-20  2:57 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Proposal provider for destination fields in fetch was inappropriately
proposing illegal local expression like HEAD^, while we should propose
there only refs or wildcard expressions.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../egit/ui/internal/components/RefSpecPanel.java  |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPanel.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPanel.java
index caef4d2..1621434 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPanel.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPanel.java
@@ -323,7 +323,7 @@ private static void setControlDecoration(final ControlDecoration control,
 	 */
 	public RefSpecPanel(final Composite parent, final boolean pushSpecs) {
 		this.pushSpecs = pushSpecs;
-		this.localProposalProvider = new RefContentProposalProvider(true);
+		this.localProposalProvider = new RefContentProposalProvider(pushSpecs);
 		this.remoteProposalProvider = new RefContentProposalProvider(false);
 		this.imageRegistry = new ImageRegistry(parent.getDisplay());
 
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 5/6] Fix disappearing "save configuration" label in RefSpecPage
  2008-08-20  2:57             ` [EGIT PATCH 4/6] Fix proposal provider for fetch in RefSpecPanel Marek Zawirski
@ 2008-08-20  2:57               ` Marek Zawirski
  2008-08-20  2:57                 ` [EGIT PATCH 6/6] Fix RefSpecPage formatting Marek Zawirski
  0 siblings, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-20  2:57 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

If we call only pack() after controls were layed out, label may disappear
on dialog resize. So we probably need to relayout whole panel when label
content changes.

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../egit/ui/internal/components/RefSpecPage.java   |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
index 4471e24..b526cf3 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
@@ -209,7 +209,7 @@ private void revalidateImpl(final RepositorySelection newRepoSelection) {
 			saveButton.setVisible(true);
 			saveButton.setText(NLS.bind(UIText.RefSpecPage_saveSpecifications,
 					remoteName));
-			saveButton.pack();
+			saveButton.getParent().layout();
 		}
 		checkPage();
 	}
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [EGIT PATCH 6/6] Fix RefSpecPage formatting
  2008-08-20  2:57               ` [EGIT PATCH 5/6] Fix disappearing "save configuration" label in RefSpecPage Marek Zawirski
@ 2008-08-20  2:57                 ` Marek Zawirski
  0 siblings, 0 replies; 62+ messages in thread
From: Marek Zawirski @ 2008-08-20  2:57 UTC (permalink / raw)
  To: robin.rosenberg, spearce; +Cc: git, Marek Zawirski

Signed-off-by: Marek Zawirski <marek.zawirski@gmail.com>
---
 .../egit/ui/internal/components/RefSpecPage.java   |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
index b526cf3..70856ca 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/components/RefSpecPage.java
@@ -191,7 +191,8 @@ private void revalidateImpl(final RepositorySelection newRepoSelection) {
 		} catch (InvocationTargetException e) {
 			final Throwable cause = e.getCause();
 			transportError(cause.getMessage());
-			ErrorDialog.openError(getShell(), UIText.RefSpecPage_errorTransportDialogTitle,
+			ErrorDialog.openError(getShell(),
+					UIText.RefSpecPage_errorTransportDialogTitle,
 					UIText.RefSpecPage_errorTransportDialogMessage, new Status(
 							IStatus.ERROR, Activator.getPluginId(), 0, cause
 									.getMessage(), cause));
-- 
1.5.6.3

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-20  2:42     ` Marek Zawirski
  2008-08-20  2:57       ` [EGIT PATCH 1/6] Remove DEFAULT_DRY_RUN constant from Transport Marek Zawirski
@ 2008-08-20 14:13       ` Shawn O. Pearce
  2008-08-20 15:23         ` Git-aware Issue Tracking? Petr Baudis
  2008-08-25 13:59         ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Marek Zawirski
  2008-08-21 20:12       ` Robin Rosenberg
  2 siblings, 2 replies; 62+ messages in thread
From: Shawn O. Pearce @ 2008-08-20 14:13 UTC (permalink / raw)
  To: Marek Zawirski; +Cc: Robin Rosenberg, git

Marek Zawirski <marek.zawirski@gmail.com> wrote:
> Actually, I'm now leaving for few days, so I'm sending out just fixes  
> for issues pointed out by Shawn and found by me in the mean time. I'll  
> squash these patches with existing commits in push branch, as it is  
> probably worth nothing to keep this in history.

Thanks.  These fixes look good to me, and they address my immediate
concerns with the series.

> BTW, as another developers are getting involved in jgit/egit coding,  
> maybe we could use (update) some wiki page for marking who is working on  
> some topic currently? Now it's not obvious for me, and as we're not so  
> numerous it would be pity to waste our time and do some redundant stuff  
> one day.

I've thought about starting a code.google.com project just to use
the issue tracking system there.  I'm using an internal tool to
keep of issues for myself, but that's not fair to the end-users or
other contributors...

-- 
Shawn.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Git-aware Issue Tracking?
  2008-08-20 14:13       ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Shawn O. Pearce
@ 2008-08-20 15:23         ` Petr Baudis
  2008-08-20 16:44           ` Shawn O. Pearce
                             ` (3 more replies)
  2008-08-25 13:59         ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Marek Zawirski
  1 sibling, 4 replies; 62+ messages in thread
From: Petr Baudis @ 2008-08-20 15:23 UTC (permalink / raw)
  To: Shawn O. Pearce; +Cc: Marek Zawirski, Robin Rosenberg, git

On Wed, Aug 20, 2008 at 07:13:26AM -0700, Shawn O. Pearce wrote:
> I've thought about starting a code.google.com project just to use
> the issue tracking system there.  I'm using an internal tool to
> keep of issues for myself, but that's not fair to the end-users or
> other contributors...

I have been thinking about issue tracking for some of my projects too,
but I'm wondering, does anyone have a comprehensive picture of the state
of the Git-supporting issue tracking tools, especially those that keep
the tracked issues in a Git repository as well?

	http://git.or.cz/gitwiki/InterfacesFrontendsAndTools#head-73b23f376ebd0222d1e4b08f09158172aa34c24f

has three, but two of them are in Ruby, which is rather discouraging.
But Cil (in Perl) is already "self-hosting", so it might be well usable?

-- 
				Petr "Pasky" Baudis
The next generation of interesting software will be done
on the Macintosh, not the IBM PC.  -- Bill Gates

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: Git-aware Issue Tracking?
  2008-08-20 15:23         ` Git-aware Issue Tracking? Petr Baudis
@ 2008-08-20 16:44           ` Shawn O. Pearce
  2008-08-20 20:25             ` Robin Rosenberg
  2008-08-21  8:47             ` Martin Langhoff
  2008-08-20 18:52           ` Jakub Narebski
                             ` (2 subsequent siblings)
  3 siblings, 2 replies; 62+ messages in thread
From: Shawn O. Pearce @ 2008-08-20 16:44 UTC (permalink / raw)
  To: Petr Baudis; +Cc: Marek Zawirski, Robin Rosenberg, git

Petr Baudis <pasky@suse.cz> wrote:
> On Wed, Aug 20, 2008 at 07:13:26AM -0700, Shawn O. Pearce wrote:
> > I've thought about starting a code.google.com project just to use
> > the issue tracking system there.
> 
> I have been thinking about issue tracking for some of my projects too,
> but I'm wondering, does anyone have a comprehensive picture of the state
> of the Git-supporting issue tracking tools, especially those that keep
> the tracked issues in a Git repository as well?
> 
> 	http://git.or.cz/gitwiki/InterfacesFrontendsAndTools#head-73b23f376ebd0222d1e4b08f09158172aa34c24f
> 
> has three, but two of them are in Ruby, which is rather discouraging.
> But Cil (in Perl) is already "self-hosting", so it might be well usable?

Cil is interesting.  I'm concerned about keeping the state in tree
with the repository though in a distributed development team.

If I mark the status of an issue in a branch that isn't ready
for mainline how do I share that status update with everyone else?
I have to put it into a branch somewhere, no big deal.  repo.or.cz is
pretty good at publishing things.

But do that now for 5 developers working on 10 or 20 different
branches at once.  We'll have status updates all over the place
and Marek's desire to see what we are each working on (to reduce
wasted effort and perhaps help each other out more) still isn't met.

This is the number one reason a DIT (distributed issue tracker)
isn't available.  Nobody has solved the hard technical problem of
making it easy to distribute the state changes, yet still provide
a reasonably current global view of the issue status.

Perhaps running Cil in its own egit-cil.git repository would get
us what we neeed.  I looked at the code and its pretty clean,
but I didn't see how merges of the .cil database work.

-- 
Shawn.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: Git-aware Issue Tracking?
  2008-08-20 15:23         ` Git-aware Issue Tracking? Petr Baudis
  2008-08-20 16:44           ` Shawn O. Pearce
@ 2008-08-20 18:52           ` Jakub Narebski
  2008-08-21  8:30             ` Pierre Habouzit
  2008-08-20 20:22           ` Mike Dalessio
  2008-08-21  8:55           ` Imran M Yousuf
  3 siblings, 1 reply; 62+ messages in thread
From: Jakub Narebski @ 2008-08-20 18:52 UTC (permalink / raw)
  To: Petr Baudis
  Cc: Shawn O. Pearce, Marek Zawirski, Robin Rosenberg, Pierre Habouzit,
	git

Petr Baudis <pasky@suse.cz> writes:

> On Wed, Aug 20, 2008 at 07:13:26AM -0700, Shawn O. Pearce wrote:
> > I've thought about starting a code.google.com project just to use
> > the issue tracking system there.  I'm using an internal tool to
> > keep of issues for myself, but that's not fair to the end-users or
> > other contributors...
> 
> I have been thinking about issue tracking for some of my projects too,
> but I'm wondering, does anyone have a comprehensive picture of the state
> of the Git-supporting issue tracking tools, especially those that keep
> the tracked issues in a Git repository as well?
> 
> 	http://git.or.cz/gitwiki/InterfacesFrontendsAndTools#head-73b23f376ebd0222d1e4b08f09158172aa34c24f
> 
> has three, but two of them are in Ruby, which is rather discouraging.
> But Cil (in Perl) is already "self-hosting", so it might be well usable?

There is also Bugs Everywhere, written in Python, which supposedly has
(some form of) Git support:
  http://git.or.cz/gitwiki/InterfacesFrontendsAndToolsWishlist#be

There was also 'grit' by Pierre Habouzit, also in Python, which got
abandoned and removed (also from wiki):
http://git.or.cz/gitwiki/InterfacesFrontendsAndTools?action=diff&rev2=203&rev1=202
If I remember correcly Pierre promised to write down what he learned
about distributed bug tracking from his work on grit[*1*], when he had
a bit of free time, but I don't remember him doing it...

See also "[RFC] git integrated bugtracking" thread on git mailing list
(http://thread.gmane.org/gmane.comp.version-control.git/48981) where
grit started, and later "[RFC] Idea for Git Bugtracking Tool"
(http://thread.gmane.org/gmane.comp.version-control.git/76411), where
you can find different distributed bug trackers, not all of them
thought supporting Git.

HTH.

Footnotes:
==========
[*1*] http://thread.gmane.org/gmane.comp.version-control.git/76411/focus=76565
-- 
Jakub Narebski
Poland
ShadeHawk on #git

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: Git-aware Issue Tracking?
  2008-08-20 15:23         ` Git-aware Issue Tracking? Petr Baudis
  2008-08-20 16:44           ` Shawn O. Pearce
  2008-08-20 18:52           ` Jakub Narebski
@ 2008-08-20 20:22           ` Mike Dalessio
  2008-08-21  8:55           ` Imran M Yousuf
  3 siblings, 0 replies; 62+ messages in thread
From: Mike Dalessio @ 2008-08-20 20:22 UTC (permalink / raw)
  To: Petr Baudis; +Cc: Shawn O. Pearce, Marek Zawirski, Robin Rosenberg, git

On Wed, Aug 20, 2008 at 11:23 AM, Petr Baudis <pasky@suse.cz> wrote:
> I have been thinking about issue tracking for some of my projects too,
> but I'm wondering, does anyone have a comprehensive picture of the state
> of the Git-supporting issue tracking tools, especially those that keep
> the tracked issues in a Git repository as well?

I haven't played with it, but this looks like it's along the lines of
what you're discussing:

     http://github.com/jwiegley/git-issues/tree/master

It apparently uses an unchecked-out branch in the project to manage its data,
which is stored in a hash directory structure similar to .git/objects.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: Git-aware Issue Tracking?
  2008-08-20 16:44           ` Shawn O. Pearce
@ 2008-08-20 20:25             ` Robin Rosenberg
  2008-08-21  8:47             ` Martin Langhoff
  1 sibling, 0 replies; 62+ messages in thread
From: Robin Rosenberg @ 2008-08-20 20:25 UTC (permalink / raw)
  To: Shawn O. Pearce; +Cc: Petr Baudis, Marek Zawirski, git

onsdagen den 20 augusti 2008 18.44.09 skrev Shawn O. Pearce:
> Petr Baudis <pasky@suse.cz> wrote:
> > On Wed, Aug 20, 2008 at 07:13:26AM -0700, Shawn O. Pearce wrote:
> > > I've thought about starting a code.google.com project just to use
> > > the issue tracking system there.
> > 
> > I have been thinking about issue tracking for some of my projects too,
> > but I'm wondering, does anyone have a comprehensive picture of the state
> > of the Git-supporting issue tracking tools, especially those that keep
> > the tracked issues in a Git repository as well?
> > 
> > 	http://git.or.cz/gitwiki/InterfacesFrontendsAndTools#head-73b23f376ebd0222d1e4b08f09158172aa34c24f
> > 
> > has three, but two of them are in Ruby, which is rather discouraging.
> > But Cil (in Perl) is already "self-hosting", so it might be well usable?
> 
> Cil is interesting.  I'm concerned about keeping the state in tree
> with the repository though in a distributed development team.
> 
> If I mark the status of an issue in a branch that isn't ready
> for mainline how do I share that status update with everyone else?
> I have to put it into a branch somewhere, no big deal.  repo.or.cz is
> pretty good at publishing things.
> 
> But do that now for 5 developers working on 10 or 20 different
> branches at once.  We'll have status updates all over the place
> and Marek's desire to see what we are each working on (to reduce
> wasted effort and perhaps help each other out more) still isn't met.
> 
> This is the number one reason a DIT (distributed issue tracker)
> isn't available.  Nobody has solved the hard technical problem of
> making it easy to distribute the state changes, yet still provide
> a reasonably current global view of the issue status.

Actually his name is Mik Kersten, the main figure behind Mylyn.
Mylyn is a plugin for Eclipse that actually does this. It connects to 
bugzilla, trac and som other trackers and caches changes locally
until it connect to the website and syncrhronize. It does a lot more,
than that. The objectives partially overlap what Git does. Having
Mylin and Git work together would be very sweet indeed.

> Perhaps running Cil in its own egit-cil.git repository would get
> us what we neeed.  I looked at the code and its pretty clean,
> but I didn't see how merges of the .cil database work.

-- robin

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: Git-aware Issue Tracking?
  2008-08-20 18:52           ` Jakub Narebski
@ 2008-08-21  8:30             ` Pierre Habouzit
  2008-08-21  9:23               ` Jakub Narebski
  0 siblings, 1 reply; 62+ messages in thread
From: Pierre Habouzit @ 2008-08-21  8:30 UTC (permalink / raw)
  To: Jakub Narebski
  Cc: Petr Baudis, Shawn O. Pearce, Marek Zawirski, Robin Rosenberg,
	git

[-- Attachment #1: Type: text/plain, Size: 1915 bytes --]

On Wed, Aug 20, 2008 at 06:52:24PM +0000, Jakub Narebski wrote:
> Petr Baudis <pasky@suse.cz> writes:
> 
> > On Wed, Aug 20, 2008 at 07:13:26AM -0700, Shawn O. Pearce wrote:
> > > I've thought about starting a code.google.com project just to use
> > > the issue tracking system there.  I'm using an internal tool to
> > > keep of issues for myself, but that's not fair to the end-users or
> > > other contributors...
> > 
> > I have been thinking about issue tracking for some of my projects too,
> > but I'm wondering, does anyone have a comprehensive picture of the state
> > of the Git-supporting issue tracking tools, especially those that keep
> > the tracked issues in a Git repository as well?
> > 
> > 	http://git.or.cz/gitwiki/InterfacesFrontendsAndTools#head-73b23f376ebd0222d1e4b08f09158172aa34c24f
> > 
> > has three, but two of them are in Ruby, which is rather discouraging.
> > But Cil (in Perl) is already "self-hosting", so it might be well usable?
> 
> There is also Bugs Everywhere, written in Python, which supposedly has
> (some form of) Git support:
>   http://git.or.cz/gitwiki/InterfacesFrontendsAndToolsWishlist#be
> 
> There was also 'grit' by Pierre Habouzit, also in Python, which got
> abandoned and removed (also from wiki):
> http://git.or.cz/gitwiki/InterfacesFrontendsAndTools?action=diff&rev2=203&rev1=202
> If I remember correcly Pierre promised to write down what he learned
> about distributed bug tracking from his work on grit[*1*], when he had
> a bit of free time, but I don't remember him doing it...

  Actually there is a list, bugs-dist@kitenet.net or sth similar where I
did it. One should look at the archives of that list. Though it's
somehow dead again.

-- 
·O·  Pierre Habouzit
··O                                                madcoder@debian.org
OOO                                                http://www.madism.org

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: Git-aware Issue Tracking?
  2008-08-20 16:44           ` Shawn O. Pearce
  2008-08-20 20:25             ` Robin Rosenberg
@ 2008-08-21  8:47             ` Martin Langhoff
  1 sibling, 0 replies; 62+ messages in thread
From: Martin Langhoff @ 2008-08-21  8:47 UTC (permalink / raw)
  To: Shawn O. Pearce; +Cc: Petr Baudis, Marek Zawirski, Robin Rosenberg, git, andy

On Thu, Aug 21, 2008 at 4:44 AM, Shawn O. Pearce <spearce@spearce.org> wrote:
> Cil is interesting.  I'm concerned about keeping the state in tree
> with the repository though in a distributed development team.

I'm not a Cil user, but I work with the original dev, and a few people
around here use it. Perhaps Andy can help answer questions about it...

cheers,



m
-- 
 martin.langhoff@gmail.com
 martin@laptop.org -- School Server Architect
 - ask interesting questions
 - don't get distracted with shiny stuff - working code first
 - http://wiki.laptop.org/go/User:Martinlanghoff

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: Git-aware Issue Tracking?
  2008-08-20 15:23         ` Git-aware Issue Tracking? Petr Baudis
                             ` (2 preceding siblings ...)
  2008-08-20 20:22           ` Mike Dalessio
@ 2008-08-21  8:55           ` Imran M Yousuf
  3 siblings, 0 replies; 62+ messages in thread
From: Imran M Yousuf @ 2008-08-21  8:55 UTC (permalink / raw)
  To: Petr Baudis; +Cc: Shawn O. Pearce, Marek Zawirski, Robin Rosenberg, git

On Wed, Aug 20, 2008 at 9:23 PM, Petr Baudis <pasky@suse.cz> wrote:
> On Wed, Aug 20, 2008 at 07:13:26AM -0700, Shawn O. Pearce wrote:
>> I've thought about starting a code.google.com project just to use
>> the issue tracking system there.  I'm using an internal tool to
>> keep of issues for myself, but that's not fair to the end-users or
>> other contributors...
>
> I have been thinking about issue tracking for some of my projects too,
> but I'm wondering, does anyone have a comprehensive picture of the state
> of the Git-supporting issue tracking tools, especially those that keep
> the tracked issues in a Git repository as well?
>
>        http://git.or.cz/gitwiki/InterfacesFrontendsAndTools#head-73b23f376ebd0222d1e4b08f09158172aa34c24f
>
> has three, but two of them are in Ruby, which is rather discouraging.
> But Cil (in Perl) is already "self-hosting", so it might be well usable?

I would also like to see FishEye support git. They have a issue for it
where they have asked interested personnel to place their comment or
vote.

http://jira.atlassian.com/browse/FE-337

Integration with FishEye would mean integration with JIRA as well.
IMO, that would be cool!

Best regards,

Imran

>
> --
>                                Petr "Pasky" Baudis
> The next generation of interesting software will be done
> on the Macintosh, not the IBM PC.  -- Bill Gates
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>



-- 
Imran M Yousuf
Email: imran@smartitengineering.com
Blog: http://imyousuf-tech.blogs.smartitengineering.com/
Mobile: +880-1711402557

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: Git-aware Issue Tracking?
  2008-08-21  8:30             ` Pierre Habouzit
@ 2008-08-21  9:23               ` Jakub Narebski
  0 siblings, 0 replies; 62+ messages in thread
From: Jakub Narebski @ 2008-08-21  9:23 UTC (permalink / raw)
  To: Pierre Habouzit
  Cc: Petr Baudis, Shawn O. Pearce, Marek Zawirski, Robin Rosenberg,
	git

On Thu, 21 Aug 2008, Pierre Habouzit wrote:

> Actually there is a list, bugs-dist@kitenet.net or sth similar where
> I did it. One should look at the archives of that list. Though it's
> somehow dead again.

Archives are at http://kitenet.net/pipermail/dist-bugs/ for what its
worth (only 39 messages there).

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-20  2:42     ` Marek Zawirski
  2008-08-20  2:57       ` [EGIT PATCH 1/6] Remove DEFAULT_DRY_RUN constant from Transport Marek Zawirski
  2008-08-20 14:13       ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Shawn O. Pearce
@ 2008-08-21 20:12       ` Robin Rosenberg
  2008-08-21 20:16         ` Shawn O. Pearce
  2 siblings, 1 reply; 62+ messages in thread
From: Robin Rosenberg @ 2008-08-21 20:12 UTC (permalink / raw)
  To: Marek Zawirski; +Cc: Shawn O. Pearce, git

onsdagen den 20 augusti 2008 04.42.42 skrev Marek Zawirski:
> Actually, I'm now leaving for few days, so I'm sending out just fixes 
> for issues pointed out by Shawn and found by me in the mean time. I'll 
> squash these patches with existing commits in push branch, as it is 
> probably worth nothing to keep this in history.

As I'm too eager to get this out (seems to work), I can squash it and
rearrange the checkbox-related code a little like stealing the icons
from the Eclipse CVS and grouping the checkbox classes with the
icons plus a tweak for selecting how the images for the checkbox
are created.

-- robin

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-21 20:12       ` Robin Rosenberg
@ 2008-08-21 20:16         ` Shawn O. Pearce
  2008-08-21 23:25           ` Robin Rosenberg
  0 siblings, 1 reply; 62+ messages in thread
From: Shawn O. Pearce @ 2008-08-21 20:16 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: Marek Zawirski, git

Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:
> onsdagen den 20 augusti 2008 04.42.42 skrev Marek Zawirski:
> > Actually, I'm now leaving for few days, so I'm sending out just fixes 
> > for issues pointed out by Shawn and found by me in the mean time. I'll 
> > squash these patches with existing commits in push branch, as it is 
> > probably worth nothing to keep this in history.
> 
> As I'm too eager to get this out (seems to work), I can squash it and
> rearrange the checkbox-related code a little like stealing the icons
> from the Eclipse CVS and grouping the checkbox classes with the
> icons plus a tweak for selecting how the images for the checkbox
> are created.

Yea.  Go for it.  Reusing icons from Eclipse CVS is a good way
around the icon issue.

-- 
Shawn.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-21 20:16         ` Shawn O. Pearce
@ 2008-08-21 23:25           ` Robin Rosenberg
  2008-08-22  3:05             ` Shawn O. Pearce
  2008-08-25 14:13             ` Marek Zawirski
  0 siblings, 2 replies; 62+ messages in thread
From: Robin Rosenberg @ 2008-08-21 23:25 UTC (permalink / raw)
  To: Shawn O. Pearce; +Cc: Marek Zawirski, git

torsdagen den 21 augusti 2008 22.16.36 skrev Shawn O. Pearce:
> Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:
> > onsdagen den 20 augusti 2008 04.42.42 skrev Marek Zawirski:
> > > Actually, I'm now leaving for few days, so I'm sending out just fixes 
> > > for issues pointed out by Shawn and found by me in the mean time. I'll 
> > > squash these patches with existing commits in push branch, as it is 
> > > probably worth nothing to keep this in history.
> > 
> > As I'm too eager to get this out (seems to work), I can squash it and
> > rearrange the checkbox-related code a little like stealing the icons
> > from the Eclipse CVS and grouping the checkbox classes with the
> > icons plus a tweak for selecting how the images for the checkbox
> > are created.
> 
> Yea.  Go for it.  Reusing icons from Eclipse CVS is a good way
> around the icon issue.

I pushed the cleanups to pu using the Eclipse plugin(!) built from that version. 
You may want to take a quick look before we declare it master. 

Nice work Marek!

-- robin

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-21 23:25           ` Robin Rosenberg
@ 2008-08-22  3:05             ` Shawn O. Pearce
  2008-08-25 14:13             ` Marek Zawirski
  1 sibling, 0 replies; 62+ messages in thread
From: Shawn O. Pearce @ 2008-08-22  3:05 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: Marek Zawirski, git

Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:
> 
> I pushed the cleanups to pu using the Eclipse plugin(!) built from that version. 
> You may want to take a quick look before we declare it master. 

Yay!  I'll take a quick look at the series tomorrow, and if
it looks good to me too I'll merge it over to master.  But I'm
not going to spend much time on EGit tomorrow if I can help it.
I need to focus on some work related stuff, and the Git in HTTP
RPC protocol specification.
 
-- 
Shawn.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-20 14:13       ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Shawn O. Pearce
  2008-08-20 15:23         ` Git-aware Issue Tracking? Petr Baudis
@ 2008-08-25 13:59         ` Marek Zawirski
  2008-08-25 14:24           ` Shawn O. Pearce
  1 sibling, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-25 13:59 UTC (permalink / raw)
  To: Shawn O. Pearce; +Cc: Robin Rosenberg, git

Shawn O. Pearce wrote:
>> BTW, as another developers are getting involved in jgit/egit coding,  
>> maybe we could use (update) some wiki page for marking who is working on  
>> some topic currently? Now it's not obvious for me, and as we're not so  
>> numerous it would be pity to waste our time and do some redundant stuff  
>> one day.
> 
> I've thought about starting a code.google.com project just to use
> the issue tracking system there.  I'm using an internal tool to
> keep of issues for myself, but that's not fair to the end-users or
> other contributors...

That seems to be nice idea.
The only downside I see is that using code.coogle.com with Mylyn may be 
hard[1]. As if we start to use issues system, it may be nice opportunity 
to start using Mylyn - if anybody want to?

[1] http://www.jroller.com/alexRuiz/entry/using_mylyn_with_google_code

-- 
Marek Zawirski [zawir]
marek.zawirski@gmail.com

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-21 23:25           ` Robin Rosenberg
  2008-08-22  3:05             ` Shawn O. Pearce
@ 2008-08-25 14:13             ` Marek Zawirski
  2008-08-25 14:15               ` Shawn O. Pearce
  1 sibling, 1 reply; 62+ messages in thread
From: Marek Zawirski @ 2008-08-25 14:13 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: Shawn O. Pearce, git

Robin Rosenberg wrote:
> torsdagen den 21 augusti 2008 22.16.36 skrev Shawn O. Pearce:
>> Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:
>>> onsdagen den 20 augusti 2008 04.42.42 skrev Marek Zawirski:
>>>> Actually, I'm now leaving for few days, so I'm sending out just fixes 
>>>> for issues pointed out by Shawn and found by me in the mean time. I'll 
>>>> squash these patches with existing commits in push branch, as it is 
>>>> probably worth nothing to keep this in history.
>>> As I'm too eager to get this out (seems to work), I can squash it and
>>> rearrange the checkbox-related code a little like stealing the icons
>>> from the Eclipse CVS and grouping the checkbox classes with the
>>> icons plus a tweak for selecting how the images for the checkbox
>>> are created.
>> Yea.  Go for it.  Reusing icons from Eclipse CVS is a good way
>> around the icon issue.

I'm back and looked at this solution - it is probably the best 
workaround for now, thanks!

> I pushed the cleanups to pu using the Eclipse plugin(!) built from that version. 
> You may want to take a quick look before we declare it master. 
> 
> Nice work Marek!

Nice to hear that:)

-- 
Marek Zawirski [zawir]
marek.zawirski@gmail.com

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-25 14:13             ` Marek Zawirski
@ 2008-08-25 14:15               ` Shawn O. Pearce
  0 siblings, 0 replies; 62+ messages in thread
From: Shawn O. Pearce @ 2008-08-25 14:15 UTC (permalink / raw)
  To: Marek Zawirski; +Cc: Robin Rosenberg, git

Marek Zawirski <marek.zawirski@gmail.com> wrote:
> Robin Rosenberg wrote:
>
>> I pushed the cleanups to pu using the Eclipse plugin(!) built from that 
>> version. You may want to take a quick look before we declare it master. 
>> 
>>
>> Nice work Marek!
>
> Nice to hear that:)

I used the plugin to push itself to master.  Your changes are in
the main tree.  Yay!

-- 
Shawn.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff
  2008-08-25 13:59         ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Marek Zawirski
@ 2008-08-25 14:24           ` Shawn O. Pearce
  0 siblings, 0 replies; 62+ messages in thread
From: Shawn O. Pearce @ 2008-08-25 14:24 UTC (permalink / raw)
  To: Marek Zawirski; +Cc: Robin Rosenberg, git

Marek Zawirski <marek.zawirski@gmail.com> wrote:
> Shawn O. Pearce wrote:
>>
>> I've thought about starting a code.google.com project just to use
>> the issue tracking system there.
>
> That seems to be nice idea.
> The only downside I see is that using code.coogle.com with Mylyn may be  
> hard[1]. As if we start to use issues system, it may be nice opportunity  
> to start using Mylyn - if anybody want to?
>
> [1] http://www.jroller.com/alexRuiz/entry/using_mylyn_with_google_code

A comment there suggests its gotten slightly better:

Posted by Christopher Barber on July 17, 2008 at 02:26 PM PDT:
> There was a regression introduced in the 3.0 version of mylyn,
> but it has been fixed in 3.0.1, which was just released.
> 
> Also, since Google Code now supports csv output, you are probably
> better off changing your Query URL to:
> 
> ${serverUrl}/csv?can=1&colspec=ID+Status+Type+Owner+Summary
> 
> and your Query Pattern to:
> 
> "({Id}[0-9]+?)","({Status}.+?)","({Type}.+?)","({Owner}.+?)","({Description}.+?)"\n
> 
> This should provide a faster sync, since it minimizes the amount
> of bytes downloaded.

I may give it a try later today.

I also know some of the folks behind code.google.com.  I can poke
them to see if Mylyn support is going to be coming along in the
near future.  Google uses Eclipse a lot internally, and we have
a number of open source projects using code.google.com as their
hosting/issue tracking, and Eclipse as their IDE.  We probably
should be making the issue tracking product more accessible to IDEs.

-- 
Shawn.

^ permalink raw reply	[flat|nested] 62+ messages in thread

end of thread, other threads:[~2008-08-25 14:25 UTC | newest]

Thread overview: 62+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-08-17 20:43 [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Marek Zawirski
2008-08-17 20:43 ` [EGIT PATCH 01/31] Fix Repository.mapObject() for missing objects Marek Zawirski
2008-08-17 20:43   ` [EGIT PATCH 02/31] Fix Repository isValidRefName() for empty names Marek Zawirski
2008-08-17 20:43     ` [EGIT PATCH 03/31] Fix Repository.resolve() to not throw runtime exceptions Marek Zawirski
2008-08-17 20:43       ` [EGIT PATCH 04/31] Document/fix Transport open method for specific case Marek Zawirski
2008-08-17 20:43         ` [EGIT PATCH 05/31] Fix RefSpec javadoc regarding spec expanding Marek Zawirski
2008-08-17 20:43           ` [EGIT PATCH 06/31] Make wildcard checking public in RefSpec Marek Zawirski
2008-08-17 20:43             ` [EGIT PATCH 07/31] Add openAll() and applyConfig() methods to Transport Marek Zawirski
2008-08-17 20:43               ` [EGIT PATCH 08/31] Add dryRun option to Transport and console push Marek Zawirski
2008-08-17 20:43                 ` [EGIT PATCH 09/31] Extract Transport findRemoteRefUpdatesFor() as static method Marek Zawirski
2008-08-17 20:43                   ` [EGIT PATCH 10/31] Improve javadoc of Transport push() Marek Zawirski
2008-08-17 20:43                     ` [EGIT PATCH 11/31] Clean up exception issues in RemoteRefUpdate Marek Zawirski
2008-08-17 20:43                       ` [EGIT PATCH 12/31] Add another RemoteRefUpdate constructor, useful for 2-stage push Marek Zawirski
2008-08-17 20:43                         ` [EGIT PATCH 13/31] Add getAllRemoteConfigs() to RemoteConfig Marek Zawirski
2008-08-17 20:43                           ` [EGIT PATCH 14/31] Add setFetchRefSpecs and setPushRefSpecs " Marek Zawirski
2008-08-17 20:43                             ` [EGIT PATCH 15/31] Add simple abbreviate() method to ObjectId Marek Zawirski
2008-08-17 20:43                               ` [EGIT PATCH 16/31] Remove debug/test console output from GitIndex Marek Zawirski
2008-08-17 20:43                                 ` [EGIT PATCH 17/31] Fix typo in uitext.properties message Marek Zawirski
2008-08-17 20:43                                   ` [EGIT PATCH 18/31] Refactor/rewrite CloneSourcePage to universal RepositorySelectionPage Marek Zawirski
2008-08-17 20:44                                     ` [EGIT PATCH 19/31] Clone wizard and related: refactor, clean-up, fixes or improvements Marek Zawirski
2008-08-17 20:44                                       ` [EGIT PATCH 20/31] Move clone logic away from GitCloneWizard to CloneOperation Marek Zawirski
2008-08-17 20:44                                         ` [EGIT PATCH 21/31] Add canCreateSubdir() heuristic in CloneDestinationPage Marek Zawirski
2008-08-17 20:44                                           ` [EGIT PATCH 22/31] Set FileDialog selection appropriately in clone wizard Marek Zawirski
2008-08-17 20:44                                             ` [EGIT PATCH 23/31] Allow selecting empty dir " Marek Zawirski
2008-08-17 20:44                                               ` [EGIT PATCH 24/31] Clone wizard: force dir to suggested path only if repo selection change Marek Zawirski
2008-08-17 20:44                                                 ` [EGIT PATCH 25/31] Create ListRemoteOperation for listing remote repo branches Marek Zawirski
2008-08-17 20:44                                                   ` [EGIT PATCH 26/31] Make Clone's SourceBranchPage more user-friendly Marek Zawirski
2008-08-17 20:44                                                     ` [EGIT PATCH 27/31] Add few EPL Eclipse icons Marek Zawirski
2008-08-17 20:44                                                       ` [EGIT PATCH 28/31] Checkbox images/screenshots Marek Zawirski
2008-08-17 20:44                                                         ` [EGIT PATCH 29/31] Universal GUI for specifications edition: RefSpecPanel and related Marek Zawirski
2008-08-17 20:44                                                           ` [EGIT PATCH 30/31] Add PushOperation to plugin Marek Zawirski
2008-08-17 20:44                                                             ` [EGIT PATCH 31/31] Push GUI Marek Zawirski
2008-08-19 18:24                                                         ` [EGIT PATCH 28/31] Checkbox images/screenshots Robin Rosenberg
2008-08-19 16:45                         ` [EGIT PATCH 12/31] Add another RemoteRefUpdate constructor, useful for 2-stage push Shawn O. Pearce
2008-08-19 16:28                 ` [EGIT PATCH 08/31] Add dryRun option to Transport and console push Shawn O. Pearce
2008-08-19 17:59 ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Shawn O. Pearce
2008-08-19 19:21   ` Robin Rosenberg
2008-08-20  2:42     ` Marek Zawirski
2008-08-20  2:57       ` [EGIT PATCH 1/6] Remove DEFAULT_DRY_RUN constant from Transport Marek Zawirski
2008-08-20  2:57         ` [EGIT PATCH 2/6] Emphasize that db is a local one in RemoteRefUpdate Marek Zawirski
2008-08-20  2:57           ` [EGIT PATCH 3/6] Handle URIs parsing errors in PushAction better Marek Zawirski
2008-08-20  2:57             ` [EGIT PATCH 4/6] Fix proposal provider for fetch in RefSpecPanel Marek Zawirski
2008-08-20  2:57               ` [EGIT PATCH 5/6] Fix disappearing "save configuration" label in RefSpecPage Marek Zawirski
2008-08-20  2:57                 ` [EGIT PATCH 6/6] Fix RefSpecPage formatting Marek Zawirski
2008-08-20 14:13       ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Shawn O. Pearce
2008-08-20 15:23         ` Git-aware Issue Tracking? Petr Baudis
2008-08-20 16:44           ` Shawn O. Pearce
2008-08-20 20:25             ` Robin Rosenberg
2008-08-21  8:47             ` Martin Langhoff
2008-08-20 18:52           ` Jakub Narebski
2008-08-21  8:30             ` Pierre Habouzit
2008-08-21  9:23               ` Jakub Narebski
2008-08-20 20:22           ` Mike Dalessio
2008-08-21  8:55           ` Imran M Yousuf
2008-08-25 13:59         ` [EGIT PATCH 00/31] Push GUI, GUI improvements, various jgit stuff Marek Zawirski
2008-08-25 14:24           ` Shawn O. Pearce
2008-08-21 20:12       ` Robin Rosenberg
2008-08-21 20:16         ` Shawn O. Pearce
2008-08-21 23:25           ` Robin Rosenberg
2008-08-22  3:05             ` Shawn O. Pearce
2008-08-25 14:13             ` Marek Zawirski
2008-08-25 14:15               ` Shawn O. Pearce

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).