1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.api;
11
12 import java.io.IOException;
13 import java.io.OutputStream;
14 import java.net.URISyntaxException;
15 import java.text.MessageFormat;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 import org.eclipse.jgit.api.errors.DetachedHeadException;
25 import org.eclipse.jgit.api.errors.GitAPIException;
26 import org.eclipse.jgit.api.errors.InvalidRefNameException;
27 import org.eclipse.jgit.api.errors.InvalidRemoteException;
28 import org.eclipse.jgit.api.errors.JGitInternalException;
29 import org.eclipse.jgit.errors.NotSupportedException;
30 import org.eclipse.jgit.errors.TooLargeObjectInPackException;
31 import org.eclipse.jgit.errors.TooLargePackException;
32 import org.eclipse.jgit.errors.TransportException;
33 import org.eclipse.jgit.internal.JGitText;
34 import org.eclipse.jgit.lib.BranchConfig;
35 import org.eclipse.jgit.lib.Config;
36 import org.eclipse.jgit.lib.ConfigConstants;
37 import org.eclipse.jgit.lib.Constants;
38 import org.eclipse.jgit.lib.NullProgressMonitor;
39 import org.eclipse.jgit.lib.ProgressMonitor;
40 import org.eclipse.jgit.lib.Ref;
41 import org.eclipse.jgit.lib.Repository;
42 import org.eclipse.jgit.transport.PushConfig;
43 import org.eclipse.jgit.transport.PushConfig.PushDefault;
44 import org.eclipse.jgit.transport.PushResult;
45 import org.eclipse.jgit.transport.RefLeaseSpec;
46 import org.eclipse.jgit.transport.RefSpec;
47 import org.eclipse.jgit.transport.RemoteConfig;
48 import org.eclipse.jgit.transport.RemoteRefUpdate;
49 import org.eclipse.jgit.transport.Transport;
50
51
52
53
54
55
56
57
58
59 public class PushCommand extends
60 TransportCommand<PushCommand, Iterable<PushResult>> {
61
62 private String remote;
63
64 private final List<RefSpec> refSpecs;
65
66 private final Map<String, RefLeaseSpec> refLeaseSpecs;
67
68 private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
69
70 private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;
71
72 private boolean dryRun;
73 private boolean atomic;
74 private boolean force;
75 private boolean thin = Transport.DEFAULT_PUSH_THIN;
76
77 private OutputStream out;
78
79 private List<String> pushOptions;
80
81
82
83 private PushDefault pushDefault = PushDefault.CURRENT;
84
85
86
87
88
89
90
91
92
93 protected PushCommand(Repository repo) {
94 super(repo);
95 refSpecs = new ArrayList<>(3);
96 refLeaseSpecs = new HashMap<>();
97 }
98
99
100
101
102
103
104
105
106
107 @Override
108 public Iterable<PushResult> call() throws GitAPIException,
109 InvalidRemoteException,
110 org.eclipse.jgit.api.errors.TransportException {
111 checkCallable();
112 setCallable(false);
113
114 ArrayList<PushResult> pushResults = new ArrayList<>(3);
115
116 try {
117 Config config = repo.getConfig();
118 remote = determineRemote(config, remote);
119 if (refSpecs.isEmpty()) {
120 RemoteConfig rc = new RemoteConfig(config,
121 getRemote());
122 refSpecs.addAll(rc.getPushRefSpecs());
123 if (refSpecs.isEmpty()) {
124 determineDefaultRefSpecs(config);
125 }
126 }
127
128 if (force) {
129 for (int i = 0; i < refSpecs.size(); i++)
130 refSpecs.set(i, refSpecs.get(i).setForceUpdate(true));
131 }
132
133 List<Transport> transports = Transport.openAll(repo, remote,
134 Transport.Operation.PUSH);
135 for (@SuppressWarnings("resource")
136 final Transport transport : transports) {
137 transport.setPushThin(thin);
138 transport.setPushAtomic(atomic);
139 if (receivePack != null)
140 transport.setOptionReceivePack(receivePack);
141 transport.setDryRun(dryRun);
142 transport.setPushOptions(pushOptions);
143 configure(transport);
144
145 final Collection<RemoteRefUpdate> toPush = transport
146 .findRemoteRefUpdatesFor(refSpecs, refLeaseSpecs);
147
148 try {
149 PushResult result = transport.push(monitor, toPush, out);
150 pushResults.add(result);
151
152 } catch (TooLargePackException e) {
153 throw new org.eclipse.jgit.api.errors.TooLargePackException(
154 e.getMessage(), e);
155 } catch (TooLargeObjectInPackException e) {
156 throw new org.eclipse.jgit.api.errors.TooLargeObjectInPackException(
157 e.getMessage(), e);
158 } catch (TransportException e) {
159 throw new org.eclipse.jgit.api.errors.TransportException(
160 e.getMessage(), e);
161 } finally {
162 transport.close();
163 }
164 }
165
166 } catch (URISyntaxException e) {
167 throw new InvalidRemoteException(
168 MessageFormat.format(JGitText.get().invalidRemote, remote),
169 e);
170 } catch (TransportException e) {
171 throw new org.eclipse.jgit.api.errors.TransportException(
172 e.getMessage(), e);
173 } catch (NotSupportedException e) {
174 throw new JGitInternalException(
175 JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
176 e);
177 } catch (IOException e) {
178 throw new JGitInternalException(
179 JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
180 e);
181 }
182
183 return pushResults;
184 }
185
186 private String determineRemote(Config config, String remoteName)
187 throws IOException {
188 if (remoteName != null) {
189 return remoteName;
190 }
191 Ref head = repo.exactRef(Constants.HEAD);
192 String effectiveRemote = null;
193 BranchConfig branchCfg = null;
194 if (head != null && head.isSymbolic()) {
195 String currentBranch = head.getLeaf().getName();
196 branchCfg = new BranchConfig(config,
197 Repository.shortenRefName(currentBranch));
198 effectiveRemote = branchCfg.getPushRemote();
199 }
200 if (effectiveRemote == null) {
201 effectiveRemote = config.getString(
202 ConfigConstants.CONFIG_REMOTE_SECTION, null,
203 ConfigConstants.CONFIG_KEY_PUSH_DEFAULT);
204 if (effectiveRemote == null && branchCfg != null) {
205 effectiveRemote = branchCfg.getRemote();
206 }
207 }
208 if (effectiveRemote == null) {
209 effectiveRemote = Constants.DEFAULT_REMOTE_NAME;
210 }
211 return effectiveRemote;
212 }
213
214 private String getCurrentBranch()
215 throws IOException, DetachedHeadException {
216 Ref head = repo.exactRef(Constants.HEAD);
217 if (head != null && head.isSymbolic()) {
218 return head.getLeaf().getName();
219 }
220 throw new DetachedHeadException();
221 }
222
223 private void determineDefaultRefSpecs(Config config)
224 throws IOException, GitAPIException {
225 if (pushDefault == null) {
226 pushDefault = config.get(PushConfig::new).getPushDefault();
227 }
228 switch (pushDefault) {
229 case CURRENT:
230 refSpecs.add(new RefSpec(getCurrentBranch()));
231 break;
232 case MATCHING:
233 refSpecs.add(new RefSpec(":"));
234 break;
235 case NOTHING:
236 throw new InvalidRefNameException(
237 JGitText.get().pushDefaultNothing);
238 case SIMPLE:
239 case UPSTREAM:
240 String currentBranch = getCurrentBranch();
241 BranchConfig branchCfg = new BranchConfig(config,
242 Repository.shortenRefName(currentBranch));
243 String fetchRemote = branchCfg.getRemote();
244 if (fetchRemote == null) {
245 fetchRemote = Constants.DEFAULT_REMOTE_NAME;
246 }
247 boolean isTriangular = !fetchRemote.equals(remote);
248 if (isTriangular) {
249 if (PushDefault.UPSTREAM.equals(pushDefault)) {
250 throw new InvalidRefNameException(MessageFormat.format(
251 JGitText.get().pushDefaultTriangularUpstream,
252 remote, fetchRemote));
253 }
254
255
256
257
258 refSpecs.add(new RefSpec(currentBranch));
259 } else {
260 String trackedBranch = branchCfg.getMerge();
261 if (branchCfg.isRemoteLocal() || trackedBranch == null
262 || !trackedBranch.startsWith(Constants.R_HEADS)) {
263 throw new InvalidRefNameException(MessageFormat.format(
264 JGitText.get().pushDefaultNoUpstream,
265 currentBranch));
266 }
267 if (PushDefault.SIMPLE.equals(pushDefault)
268 && !trackedBranch.equals(currentBranch)) {
269 throw new InvalidRefNameException(MessageFormat.format(
270 JGitText.get().pushDefaultSimple, currentBranch,
271 trackedBranch));
272 }
273 refSpecs.add(new RefSpec(currentBranch + ':' + trackedBranch));
274 }
275 break;
276 default:
277 throw new InvalidRefNameException(MessageFormat
278 .format(JGitText.get().pushDefaultUnknown, pushDefault));
279 }
280 }
281
282
283
284
285
286
287
288
289
290
291
292 public PushCommand setRemote(String remote) {
293 checkCallable();
294 this.remote = remote;
295 return this;
296 }
297
298
299
300
301
302
303 public String getRemote() {
304 return remote;
305 }
306
307
308
309
310
311
312
313
314
315
316
317
318 public PushCommand setReceivePack(String receivePack) {
319 checkCallable();
320 this.receivePack = receivePack;
321 return this;
322 }
323
324
325
326
327
328
329 public String getReceivePack() {
330 return receivePack;
331 }
332
333
334
335
336
337
338 public int getTimeout() {
339 return timeout;
340 }
341
342
343
344
345
346
347 public ProgressMonitor getProgressMonitor() {
348 return monitor;
349 }
350
351
352
353
354
355
356
357
358
359
360 public PushCommand setProgressMonitor(ProgressMonitor monitor) {
361 checkCallable();
362 if (monitor == null) {
363 monitor = NullProgressMonitor.INSTANCE;
364 }
365 this.monitor = monitor;
366 return this;
367 }
368
369
370
371
372
373
374
375 public List<RefLeaseSpec> getRefLeaseSpecs() {
376 return new ArrayList<>(refLeaseSpecs.values());
377 }
378
379
380
381
382
383
384
385
386
387
388 public PushCommand setRefLeaseSpecs(RefLeaseSpec... specs) {
389 return setRefLeaseSpecs(Arrays.asList(specs));
390 }
391
392
393
394
395
396
397
398
399
400
401 public PushCommand setRefLeaseSpecs(List<RefLeaseSpec> specs) {
402 checkCallable();
403 this.refLeaseSpecs.clear();
404 for (RefLeaseSpec spec : specs) {
405 refLeaseSpecs.put(spec.getRef(), spec);
406 }
407 return this;
408 }
409
410
411
412
413
414
415 public List<RefSpec> getRefSpecs() {
416 return refSpecs;
417 }
418
419
420
421
422
423
424
425 public PushCommand setRefSpecs(RefSpec... specs) {
426 checkCallable();
427 this.refSpecs.clear();
428 Collections.addAll(refSpecs, specs);
429 return this;
430 }
431
432
433
434
435
436
437
438
439 public PushCommand setRefSpecs(List<RefSpec> specs) {
440 checkCallable();
441 this.refSpecs.clear();
442 this.refSpecs.addAll(specs);
443 return this;
444 }
445
446
447
448
449
450
451
452 public PushDefault getPushDefault() {
453 return pushDefault;
454 }
455
456
457
458
459
460
461
462
463
464
465
466
467
468 public PushCommand setPushDefault(PushDefault pushDefault) {
469 checkCallable();
470 this.pushDefault = pushDefault;
471 return this;
472 }
473
474
475
476
477
478
479 public PushCommand setPushAll() {
480 refSpecs.add(Transport.REFSPEC_PUSH_ALL);
481 return this;
482 }
483
484
485
486
487
488
489 public PushCommand setPushTags() {
490 refSpecs.add(Transport.REFSPEC_TAGS);
491 return this;
492 }
493
494
495
496
497
498
499
500
501 public PushCommand add(Ref ref) {
502 refSpecs.add(new RefSpec(ref.getLeaf().getName()));
503 return this;
504 }
505
506
507
508
509
510
511
512
513
514
515 public PushCommand add(String nameOrSpec) {
516 if (0 <= nameOrSpec.indexOf(':')) {
517 refSpecs.add(new RefSpec(nameOrSpec));
518 } else {
519 Ref src;
520 try {
521 src = repo.findRef(nameOrSpec);
522 } catch (IOException e) {
523 throw new JGitInternalException(
524 JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
525 e);
526 }
527 if (src != null)
528 add(src);
529 }
530 return this;
531 }
532
533
534
535
536
537
538 public boolean isDryRun() {
539 return dryRun;
540 }
541
542
543
544
545
546
547
548 public PushCommand setDryRun(boolean dryRun) {
549 checkCallable();
550 this.dryRun = dryRun;
551 return this;
552 }
553
554
555
556
557
558
559 public boolean isThin() {
560 return thin;
561 }
562
563
564
565
566
567
568
569
570
571
572 public PushCommand setThin(boolean thin) {
573 checkCallable();
574 this.thin = thin;
575 return this;
576 }
577
578
579
580
581
582
583
584
585 public boolean isAtomic() {
586 return atomic;
587 }
588
589
590
591
592
593
594
595
596
597
598
599 public PushCommand setAtomic(boolean atomic) {
600 checkCallable();
601 this.atomic = atomic;
602 return this;
603 }
604
605
606
607
608
609
610 public boolean isForce() {
611 return force;
612 }
613
614
615
616
617
618
619
620
621 public PushCommand setForce(boolean force) {
622 checkCallable();
623 this.force = force;
624 return this;
625 }
626
627
628
629
630
631
632
633
634
635 public PushCommand setOutputStream(OutputStream out) {
636 this.out = out;
637 return this;
638 }
639
640
641
642
643
644
645
646 public List<String> getPushOptions() {
647 return pushOptions;
648 }
649
650
651
652
653
654
655
656
657
658 public PushCommand setPushOptions(List<String> pushOptions) {
659 this.pushOptions = pushOptions;
660 return this;
661 }
662 }