Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 88beaa0bf7cbe9b993ea1ab59578b60cfc864759 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
/*******************************************************************************
 * Copyright (c) 2011 Oak Ridge National Laboratory and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    John Eblen - initial implementation
 *******************************************************************************/
package org.eclipse.ptp.rdt.sync.git.core;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeCommand;
import org.eclipse.jgit.api.RmCommand;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.StatusCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.errors.UnmergedPathException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.RemoteSession;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.TransportGitSsh;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.QuotedString;
import org.eclipse.ptp.rdt.sync.core.RDTSyncCorePlugin;
import org.eclipse.ptp.rdt.sync.core.SyncFileFilter;
import org.eclipse.ptp.rdt.sync.git.core.CommandRunner.CommandResults;
import org.eclipse.ptp.rdt.sync.git.core.messages.Messages;
import org.eclipse.ptp.remote.core.AbstractRemoteProcess;
import org.eclipse.ptp.remote.core.IRemoteConnection;
import org.eclipse.ptp.remote.core.exception.RemoteConnectionException;

/**
 * 
 * This class implements a remote sync tool using git, as accessed through the
 * jgit library.
 * 
 */
public class GitRemoteSyncConnection {

	private final int MAX_FILES = 100;
	private static final String remoteProjectName = "eclipse_auto"; //$NON-NLS-1$
	private static final String commitMessage = Messages.GRSC_CommitMessage;
	public static final String gitDir = ".ptp-sync"; //$NON-NLS-1$
	private static final String gitCommand = "git --git-dir=" + gitDir + " --work-tree=."; //$NON-NLS-1$ //$NON-NLS-2$
	private static final String remotePushBranch = "ptp-push"; //$NON-NLS-1$
	private final IRemoteConnection connection;
	private SyncFileFilter fileFilter;
	private final String localDirectory;
	private final String remoteDirectory;
	private Git git;
	private TransportGitSsh transport;
	private final IProject project;

	/**
	 * Create a remote sync connection using git. Assumes that the local
	 * directory exists but not necessarily the remote directory. It is created
	 * if not.
	 * 
	 * @param conn
	 * @param localDir
	 * @param remoteDir
	 * @throws RemoteSyncException
	 *             on problems building the remote repository. Specific
	 *             exception nested. Upon such an exception, the instance is
	 *             invalid and should not be used.
	 */
	public GitRemoteSyncConnection(IProject proj, IRemoteConnection conn, String localDir, String remoteDir, SyncFileFilter filter,
			IProgressMonitor monitor) throws RemoteSyncException {
		SubMonitor subMon = SubMonitor.convert(monitor, 10);
		try {
			project = proj;
			connection = conn;
			fileFilter = filter;
			localDirectory = localDir;
			remoteDirectory = remoteDir;

			// Build repo, creating it if it is not already present.
			try {
				buildRepo(subMon.newChild(10));
			} catch (final IOException e) {
				throw new RemoteSyncException(e);
			} catch (final RemoteExecutionException e) {
				throw new RemoteSyncException(e);
			}

			// Build transport
			final RemoteConfig remoteConfig = buildRemoteConfig(git.getRepository().getConfig());
			buildTransport(remoteConfig);
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	/**
	 * Builds the remote configuration for the connection, setting up fetch and
	 * push operations between local and remote master branches.
	 * 
	 * @param config
	 *            configuration for the local repository
	 * @return the remote configuration
	 * @throws RuntimeException
	 *             if the URI in the passed configuration is not properly
	 *             formatted.
	 */
	private RemoteConfig buildRemoteConfig(StoredConfig config) {
		RemoteConfig rconfig = null;

		try {
			rconfig = new RemoteConfig(config, remoteProjectName);
		} catch (final URISyntaxException e) {
			throw new RuntimeException(e);
		}

		final RefSpec refSpecFetch = new RefSpec("+refs/heads/master:refs/remotes/" + //$NON-NLS-1$
				remoteProjectName + "/master"); //$NON-NLS-1$
		final RefSpec refSpecPush = new RefSpec("+master:" + remotePushBranch); //$NON-NLS-1$
		rconfig.addFetchRefSpec(refSpecFetch);
		rconfig.addPushRefSpec(refSpecPush);

		return rconfig;
	}

	/**
	 * 
	 * @param monitor
	 * @param localDirectory
	 * @param remoteHost
	 * @return the repository
	 * @throws IOException
	 *             on problems writing to the file system.
	 * @throws RemoteExecutionException
	 *             on failure to run remote commands.
	 * @throws RemoteSyncException
	 *             on problems with initial local commit. TODO: Consider the
	 *             consequences of exceptions that occur at various points,
	 *             which can leave the repo in a partial state. For example, if
	 *             the repo is created but the initial commit fails. TODO:
	 *             Consider evaluating the output of "git init".
	 */
	private Git buildRepo(IProgressMonitor monitor) throws IOException, RemoteExecutionException, RemoteSyncException {
		final SubMonitor subMon = SubMonitor.convert(monitor, 100);
		subMon.subTask(Messages.GitRemoteSyncConnection_building_repo);
		try {
			final File localDir = new File(localDirectory);
			final FileRepositoryBuilder repoBuilder = new FileRepositoryBuilder();
			File gitDirFile = new File(localDirectory + File.separator + gitDir);
			Repository repository = repoBuilder.setWorkTree(localDir).setGitDir(gitDirFile).build();
			git = new Git(repository);

			// Create and configure local repository if it is not already present
			if (!(gitDirFile.exists())) {
				repository.create(false);

				// An initial commit to create the master branch.
				doCommit();
			}
			
			// Refresh the workspace after creating new local files
			// Bug 374409 - run refresh in a separate thread to avoid possible deadlock from locking both the sync lock and the
			// workspace lock.
			Thread refreshWorkspaceThread = new Thread(new Runnable() {
				public void run() {
					try {
						project.refreshLocal(IResource.DEPTH_INFINITE, subMon.newChild(5));
					} catch (CoreException e) {
						RDTSyncCorePlugin.log(Messages.GitRemoteSyncConnection_0, e);
					}
				}
			}, "Refresh workspace thread"); //$NON-NLS-1$
			refreshWorkspaceThread.start();


			// Create remote directory if necessary.
			try {
				CommandRunner.createRemoteDirectory(connection, remoteDirectory, subMon.newChild(5));
			} catch (final CoreException e) {
				throw new RemoteSyncException(e);
			}

			// Initialize remote directory if necessary
			boolean existingGitRepo = doRemoteInit(subMon.newChild(5));

			// Prepare remote site for committing (stage files using git) and
			// then commit remote files if necessary
			// Include untracked files for new git
			boolean needToCommitRemote = prepareRemoteForCommit(subMon.newChild(85), !existingGitRepo);
			// repos
			if (needToCommitRemote) {
				commitRemoteFiles(subMon.newChild(5));
			}

			return git;
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	/**
	 * Create and configure remote repository if it is not already present. Note
	 * that "git init" is "safe" on a repo already created, so we can simply
	 * rerun it each time.
	 * 
	 * @param monitor
	 * @throws IOException
	 * @throws RemoteExecutionException
	 * @throws RemoteSyncException
	 * @return whether this repo already existed
	 */
	private boolean doRemoteInit(IProgressMonitor monitor) throws IOException, RemoteExecutionException, RemoteSyncException {
		SubMonitor subMon = SubMonitor.convert(monitor, 10);
		try {
			String command = "git --git-dir=" + gitDir + " init"; //$NON-NLS-1$ //$NON-NLS-2$
			CommandResults commandResults = null;

			try {
				commandResults = CommandRunner.executeRemoteCommand(connection, command, remoteDirectory, subMon.newChild(10));
			} catch (final InterruptedException e) {
				throw new RemoteExecutionException(e);
			} catch (RemoteConnectionException e) {
				throw new RemoteExecutionException(e);
			}

			if (commandResults.getExitCode() != 0) {
				throw new RemoteExecutionException(Messages.GRSC_GitInitFailure + commandResults.getStderr());
			}

			// Pattern matching is error prone, of course, so make this more
			// likely to return false. This will cause all files to be
			// added, which is better than leaving all files untracked. This is
			// better for users without knowledge of git, who would
			// likely not be connecting to a previous git repo.
			if (commandResults.getStdout().contains("existing")) { //$NON-NLS-1$
				return true;
			} else {
				return false;
			}
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	/*
	 * Run "git add" and "git rm" as needed to prepare remote repo for commit.
	 * Return whether or not anything needs to be committed. TODO: Modified
	 * files already added by "git add" will not be found by
	 * "getRemoteFileStatus". Thus, this may return false even though there are
	 * outstanding changes. Note that this can only occur by accessing the repo
	 * outside of Eclipse.
	 * 
	 * @return whether there are changes to be committed.
	 */

	private boolean prepareRemoteForCommit(IProgressMonitor monitor, boolean includeUntrackedFiles) throws RemoteSyncException {
		SubMonitor subMon = SubMonitor.convert(monitor, 100);
		try {
			Set<String> filesToAdd = new HashSet<String>();
			Set<String> filesToDelete = new HashSet<String>();
			boolean needToCommit = false;

			getRemoteFileStatus(filesToAdd, filesToDelete, subMon.newChild(5), includeUntrackedFiles);
			for (String fileName : filesToDelete) {
				if (filesToAdd.contains(fileName)) {
					filesToAdd.remove(fileName);
				}
			}
			if (filesToAdd.size() > 0) {
				addRemoteFiles(filesToAdd, subMon.newChild(90));
				needToCommit = true;
			}
			if (filesToDelete.size() > 0) {
				deleteRemoteFiles(filesToDelete, subMon.newChild(5));
				needToCommit = true;
			}

			return needToCommit;
		} catch (IOException e) {
			throw new RemoteSyncException(e);
		} catch (RemoteExecutionException e) {
			throw new RemoteSyncException(e);
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	/*
	 * Do a "git commit" on the remote host
	 */
	private void commitRemoteFiles(IProgressMonitor monitor) throws RemoteSyncException {
		SubMonitor subMon = SubMonitor.convert(monitor, 10);
		subMon.subTask(Messages.GitRemoteSyncConnection_committing_remote);
		try {
			final String command = gitCommand + " commit -m \"" + commitMessage + "\""; //$NON-NLS-1$ //$NON-NLS-2$
			CommandResults commandResults = null;

			try {
				commandResults = CommandRunner.executeRemoteCommand(connection, command, remoteDirectory, subMon.newChild(10));
			} catch (final InterruptedException e) {
				throw new RemoteSyncException(e);
			} catch (RemoteConnectionException e) {
				throw new RemoteSyncException(e);
			} catch (IOException e) {
				throw new RemoteSyncException(e);
			}
			if (commandResults.getExitCode() != 0) {
				throw new RemoteSyncException(Messages.GRSC_GitCommitFailure + commandResults.getStderr());
			}
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	/*
	 * Do a "git rm <Files>" on the remote host
	 */
	private void deleteRemoteFiles(Set<String> filesToDelete, IProgressMonitor monitor) throws IOException,
			RemoteExecutionException, RemoteSyncException {
		SubMonitor subMon = SubMonitor.convert(monitor, 10);
		try {
			List<String> command = stringToList(gitCommand + " rm --"); //$NON-NLS-1$
			for (String fileName : filesToDelete) {
				command.add(fileName);
			}

			CommandResults commandResults = null;
			try {
				commandResults = CommandRunner.executeRemoteCommand(connection, command, remoteDirectory, subMon.newChild(10));
			} catch (final InterruptedException e) {
				throw new RemoteExecutionException(e);
			} catch (RemoteConnectionException e) {
				throw new RemoteExecutionException(e);
			}
			if (commandResults.getExitCode() != 0) {
				throw new RemoteExecutionException(Messages.GRSC_GitRmFailure + commandResults.getStderr());
			}
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	private List<String> stringToList(String command) {
		return new ArrayList<String>(Arrays.asList(command.split(" "))); //$NON-NLS-1$
	}

	/*
	 * Do a "git add <Files>" on the remote host
	 */
	private void addRemoteFiles(Set<String> filesToAdd, IProgressMonitor monitor) throws IOException, RemoteExecutionException,
			RemoteSyncException {
		SubMonitor subMon = SubMonitor.convert(monitor, filesToAdd.size());
		subMon.subTask(Messages.GitRemoteSyncConnection_adding_files);
		try {
			while (!filesToAdd.isEmpty()) {
				List<String> commandList = stringToList(gitCommand + " add -f --"); //$NON-NLS-1$
				int count = 1;
				for (String fileName : filesToAdd.toArray(new String[0])) {
					if (count++ % MAX_FILES == 0) {
						break;
					}
					commandList.add(fileName);
					filesToAdd.remove(fileName);
				}

				CommandResults commandResults = null;
				try {
					commandResults = CommandRunner.executeRemoteCommand(connection, commandList, remoteDirectory,
							subMon.newChild(10));
				} catch (final InterruptedException e) {
					throw new RemoteExecutionException(e);
				} catch (RemoteConnectionException e) {
					throw new RemoteExecutionException(e);
				}
				if (commandResults.getExitCode() != 0) {
					throw new RemoteExecutionException(Messages.GRSC_GitAddFailure + commandResults.getStderr());
				}
				monitor.worked(count);
			}
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	/*
	 * Use "git ls-files" to obtain a list of files that need to be added or
	 * deleted from the git index.
	 */
	private void getRemoteFileStatus(Set<String> filesToAdd, Set<String> filesToDelete, IProgressMonitor monitor,
			boolean includeUntrackedFiles) throws IOException, RemoteExecutionException, RemoteSyncException {
		SubMonitor subMon = SubMonitor.convert(monitor, 10);
		subMon.subTask(Messages.GitRemoteSyncConnection_getting_remote_file_status);
		try {
			final String command;
			if (includeUntrackedFiles) {
				command = gitCommand + " ls-files -t --modified --others --deleted"; //$NON-NLS-1$
			} else {
				command = gitCommand + " ls-files -t --modified --deleted"; //$NON-NLS-1$
			}
			CommandResults commandResults = null;

			try {
				commandResults = CommandRunner.executeRemoteCommand(connection, command, remoteDirectory, subMon.newChild(10));
			} catch (final InterruptedException e) {
				throw new RemoteExecutionException(e);
			} catch (RemoteConnectionException e) {
				throw new RemoteExecutionException(e);
			}
			if (commandResults.getExitCode() != 0) {
				throw new RemoteExecutionException(Messages.GRSC_GitLsFilesFailure + commandResults.getStdout());
			}

			BufferedReader statusReader = new BufferedReader(new StringReader(commandResults.getStdout()));
			String line = null;
			while ((line = statusReader.readLine()) != null) {
				if (line.charAt(0) == ' ' || line.charAt(1) != ' ') {
					continue;
				}
				char status = line.charAt(0);
				String fn = line.substring(2);
				fn = QuotedString.GIT_PATH.dequote(fn);
				if (status == 'R') {
					filesToDelete.add(fn);
				} else if (!(fileFilter.shouldIgnore(project.getFile(fn)))) {
					filesToAdd.add(fn);
				}
			}
			statusReader.close();
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	/*
	 * Use "git ls-files" to obtain a list of files that need to be added or
	 * deleted from the git index.
	 */
	private void getFileStatus(Set<String> filesToAdd, Set<String> filesToDelete, boolean includeUntrackedFiles)
			throws RemoteSyncException {
		StatusCommand statusCommand = git.status();
		Status status;
		try {
			status = statusCommand.call();
			filesToAdd.addAll(status.getAdded());
			filesToAdd.addAll(status.getModified());
			if (includeUntrackedFiles) {
				filesToAdd.addAll(status.getUntracked());
			}
			filesToDelete.addAll(status.getMissing());
		} catch (NoWorkTreeException e) {
			throw new RemoteSyncException(e);
		} catch (IOException e) {
			throw new RemoteSyncException(e);
		}

		Set<String> filesToBeIgnored = new HashSet<String>();
		for (String fileName : filesToAdd) {
			if (fileFilter.shouldIgnore(project.getFile(fileName))) {
				filesToBeIgnored.add(fileName);
			}
		}
		filesToAdd.removeAll(filesToBeIgnored);
	}

	// Subclass JGit's generic RemoteSession to set up running of remote
	// commands using the available process builder.
	public class PTPSession implements RemoteSession {
		private final URIish uri;

		public PTPSession(URIish uri) {
			this.uri = uri;
		}

		public Process exec(String command, int timeout) throws TransportException {
			// TODO: Use a library for command splitting.
			List<String> commandList = new LinkedList<String>();
			commandList.add("sh"); //$NON-NLS-1$
			commandList.add("-c"); //$NON-NLS-1$
			commandList.add(command);

			try {
				if (!connection.isOpen()) {
					connection.open(null);
				}
				return (AbstractRemoteProcess) connection.getRemoteServices().getProcessBuilder(connection, commandList).start();
			} catch (IOException e) {
				throw new TransportException(uri, e.getMessage(), e);
			} catch (RemoteConnectionException e) {
				throw new TransportException(uri, e.getMessage(), e);
			}

		}

		public void disconnect() {
			// Nothing to do
		}
	}

	/**
	 * Creates the transport object that JGit uses for executing commands
	 * remotely.
	 * 
	 * @param remoteConfig
	 *            the remote configuration for our local Git repo
	 * @throws RuntimeException
	 *             if the requested transport is not supported by JGit.
	 */
	private void buildTransport(RemoteConfig remoteConfig) {
		final URIish uri = buildURI();
		try {
			transport = (TransportGitSsh) Transport.open(git.getRepository(), uri);
		} catch (NotSupportedException e) {
			throw new RuntimeException(e);
		} catch (TransportException e) {
			throw new RuntimeException(e);
		}

		// Set the transport to use our own means of executing remote commands.
		transport.setSshSessionFactory(new SshSessionFactory() {
			@Override
			public RemoteSession getSession(URIish uri, CredentialsProvider credentialsProvider, FS fs, int tms)
					throws TransportException {
				return new PTPSession(uri);
			}
		});

		transport.applyConfig(remoteConfig);
	}

	/**
	 * Build the URI for the remote host as needed by the transport. Since the
	 * transport will use an external SSH session, we do not need to provide
	 * user, host, or password. However, the function for opening a transport
	 * throws an exception if the host is null or empty length. So we set it to
	 * a dummy string.
	 * 
	 * @return URIish
	 */
	private URIish buildURI() {
		return new URIish()
				// .setUser(connection.getUsername())
				.setHost("none") //$NON-NLS-1$
				// .setPass("")
				.setScheme("ssh") //$NON-NLS-1$
				.setPath(remoteDirectory + "/" + gitDir); //$NON-NLS-1$  //Should use remote path seperator but first 315720 has to be fixed
	}

	public void close() {
		transport.close();
		git.getRepository().close();
	}

	/**
	 * Commits files in working directory. For now, we just commit all files. So
	 * adding ".", handles all files, including newly created files, and setting
	 * the all flag (-a) ensures that deleted files are updated. TODO: Figure
	 * out how to do this more efficiently, as was done remotely (using git
	 * ls-files)
	 * 
	 * @throws RemoteSyncException
	 *             on problems committing.
	 * @return whether any changes were committed
	 */
	private boolean doCommit() throws RemoteSyncException {
		Set<String> filesToAdd = new HashSet<String>();
		Set<String> filesToRemove = new HashSet<String>();
		this.getFileStatus(filesToAdd, filesToRemove, true);

		try {
			if (!filesToAdd.isEmpty()) {
				final AddCommand addCommand = git.add();
				for (String fileName : filesToAdd) {
					addCommand.addFilepattern(fileName);
				}
				addCommand.call();
			}

			if (!filesToRemove.isEmpty()) {
				final RmCommand rmCommand = git.rm();
				for (String fileName : filesToRemove) {
					rmCommand.addFilepattern(fileName);
				}
				rmCommand.call();
			}
			if (!filesToAdd.isEmpty() || !filesToRemove.isEmpty()) {
				final CommitCommand commitCommand = git.commit();
				commitCommand.setMessage(commitMessage);
				commitCommand.call();
				return true;
			} else {
				return false;
			}
		} catch (final GitAPIException e) {
			throw new RemoteSyncException(e);
		} catch (final UnmergedPathException e) {
			throw new RemoteSyncException(e);
		}
	}

	/**
	 * @return the connection (IRemoteConnection)
	 */
	public IRemoteConnection getConnection() {
		return connection;
	}

	/**
	 * @return the localDirectory
	 */
	public String getLocalDirectory() {
		return localDirectory;
	}

	/**
	 * @return the remoteDirectory
	 */
	public String getRemoteDirectory() {
		return remoteDirectory;
	}

	/**
	 * Many of the listed exceptions appear to be unrecoverable, caused by
	 * errors in the initial setup. It is vital, though, that failed syncs are
	 * reported and handled. So all exceptions are checked exceptions, embedded
	 * in a RemoteSyncException.
	 * 
	 * @param monitor
	 * @throws RemoteSyncException
	 *             for various problems sync'ing. The specific exception is
	 *             nested within the RemoteSyncException. TODO: Consider
	 *             possible platform dependency.
	 */
	public void syncLocalToRemote(IProgressMonitor monitor) throws RemoteSyncException {
		SubMonitor subMon = SubMonitor.convert(monitor, 10);
		subMon.subTask(Messages.GitRemoteSyncConnection_sync_local_to_remote);
		try {
			// First commit changes to the local repository.
			doCommit();

			// Then push them to the remote site.
			try {
				//TODO: we currently need to do this always because we don't keep track of failed commits. We first need to decide whether we want to do commits based on delta or (as currently) based on git searching modified files
				//Than we can either keep track of deltas not transported yet or we can compare SHA-numbers (HEAD to remote-ref from last pull) to see whether something needs to be transported
				//Than we can also get rid of this hack to check for existence of master 
				if (git.branchList().call().size()>0) { //check whether master was already created
					transport.push(new EclipseGitProgressTransformer(subMon.newChild(5)), null);
	
					// Now remotely merge changes with master branch
					CommandResults mergeResults;
					final String command = gitCommand + " merge " + remotePushBranch; //$NON-NLS-1$
	
					mergeResults = CommandRunner.executeRemoteCommand(connection, command, remoteDirectory, subMon.newChild(5));
	
					if (mergeResults.getExitCode() != 0) {
						throw new RemoteSyncException(new RemoteExecutionException(Messages.GRSC_GitMergeFailure
								+ mergeResults.getStdout()));
					}
				}
			} catch (final IOException e) {
				throw new RemoteSyncException(e);
			} catch (final InterruptedException e) {
				throw new RemoteSyncException(e);
			} catch (RemoteConnectionException e) {
				throw new RemoteSyncException(e);
			}
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	/**
	 * @param monitor
	 * @throws RemoteSyncException
	 *             for various problems sync'ing. The specific exception is
	 *             nested within the RemoteSyncException. Many of the listed
	 *             exceptions appear to be unrecoverable, caused by errors in
	 *             the initial setup. It is vital, though, that failed syncs are
	 *             reported and handled. So all exceptions are checked
	 *             exceptions, embedded in a RemoteSyncException.
	 */
	public void syncRemoteToLocal(IProgressMonitor monitor, boolean includeUntrackedFiles) throws RemoteSyncException {

		// TODO: Figure out why pull doesn't work and why we have to fetch and
		// merge instead.
		// PullCommand pullCommand = gitConnection.getGit().pull().
		// try {
		// pullCommand.call();
		// } catch (WrongRepositoryStateException e) {
		// throw new RemoteSyncException(e);
		// } catch (InvalidConfigurationException e) {
		// throw new RemoteSyncException(e);
		// } catch (DetachedHeadException e) {
		// throw new RemoteSyncException(e);
		// } catch (InvalidRemoteException e) {
		// throw new RemoteSyncException(e);
		// } catch (CanceledException e) {
		// throw new RemoteSyncException(e);
		// }
		SubMonitor subMon = SubMonitor.convert(monitor, 10);
		subMon.subTask(Messages.GitRemoteSyncConnection_sync_remote_to_local);
		try {
			// First, commit in case any changes have occurred remotely.
			if (prepareRemoteForCommit(subMon.newChild(5),includeUntrackedFiles)) {
				commitRemoteFiles(subMon.newChild(5));
            }
			// Next, fetch the remote repository 
			//TODO: we currently need to do this always because we don't keep track of failed commits. We first need to decide whether we want to do commits based on delta or (as currently) based on git searching modified files
			//Than we can either keep track of deltas not transported yet or we can compare SHA-numbers (HEAD to remote-ref from last pull) to see whether something needs to be transported
			transport.fetch(new EclipseGitProgressTransformer(subMon.newChild(5)), null);

			// Now merge. Before merging we set the head for merging to master.
			Ref masterRef = git.getRepository().getRef("refs/remotes/" + remoteProjectName + "/master"); //$NON-NLS-1$ //$NON-NLS-2$

			final MergeCommand mergeCommand = git.merge().include(masterRef);

			mergeCommand.call();
		} catch (TransportException e) {
			if (e.getMessage().startsWith("Remote does not have ")) { //$NON-NLS-1$
				//just means that the remote branch isn't set up yet (and thus nothing too fetch). Can be ignored.
			} else {
				throw new RemoteSyncException(e);
			}
		} catch (IOException e) {
			throw new RemoteSyncException(e);
		} catch (GitAPIException e) {
			throw new RemoteSyncException(e);
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}
	
	/**
	 * Set the file filter used. This method allows file filtering behavior to be changed after instance is created, which is
	 * necessary to support user changes to the filter.
	 * @param sff
	 */
	public void setFileFilter(SyncFileFilter sff) {
		fileFilter = sff;
	}
}

Back to the top