22
22
import java .io .File ;
23
23
import java .io .IOException ;
24
24
import java .text .MessageFormat ;
25
+ import java .util .ArrayList ;
25
26
import java .util .Collection ;
27
+ import java .util .LinkedHashMap ;
26
28
import java .util .LinkedHashSet ;
27
29
import java .util .List ;
30
+ import java .util .Map ;
28
31
import java .util .Set ;
32
+ import java .util .SortedMap ;
33
+ import java .util .TreeMap ;
29
34
import java .util .concurrent .TimeUnit ;
30
35
36
+ import org .eclipse .jgit .lib .AnyObjectId ;
31
37
import org .eclipse .jgit .lib .BatchRefUpdate ;
32
38
import org .eclipse .jgit .lib .NullProgressMonitor ;
39
+ import org .eclipse .jgit .lib .ObjectId ;
33
40
import org .eclipse .jgit .lib .PersonIdent ;
34
41
import org .eclipse .jgit .lib .ProgressMonitor ;
42
+ import org .eclipse .jgit .lib .Ref ;
43
+ import org .eclipse .jgit .lib .RefUpdate ;
35
44
import org .eclipse .jgit .lib .Repository ;
36
45
import org .eclipse .jgit .revwalk .RevCommit ;
46
+ import org .eclipse .jgit .revwalk .RevWalk ;
37
47
import org .eclipse .jgit .transport .PostReceiveHook ;
38
48
import org .eclipse .jgit .transport .PreReceiveHook ;
39
49
import org .eclipse .jgit .transport .ReceiveCommand ;
50
60
import com .gitblit .extensions .ReceiveHook ;
51
61
import com .gitblit .manager .IGitblit ;
52
62
import com .gitblit .models .RepositoryModel ;
63
+ import com .gitblit .models .TicketModel ;
53
64
import com .gitblit .models .UserModel ;
65
+ import com .gitblit .models .TicketModel .Change ;
66
+ import com .gitblit .models .TicketModel .Field ;
67
+ import com .gitblit .models .TicketModel .Patchset ;
68
+ import com .gitblit .models .TicketModel .Status ;
69
+ import com .gitblit .models .TicketModel .TicketAction ;
70
+ import com .gitblit .models .TicketModel .TicketLink ;
54
71
import com .gitblit .tickets .BranchTicketService ;
72
+ import com .gitblit .tickets .ITicketService ;
73
+ import com .gitblit .tickets .TicketNotifier ;
55
74
import com .gitblit .utils .ArrayUtils ;
56
75
import com .gitblit .utils .ClientLogger ;
57
76
import com .gitblit .utils .CommitCache ;
58
77
import com .gitblit .utils .JGitUtils ;
59
78
import com .gitblit .utils .RefLogUtils ;
60
79
import com .gitblit .utils .StringUtils ;
80
+ import com .google .common .collect .Lists ;
61
81
62
82
63
83
/**
@@ -92,6 +112,11 @@ public class GitblitReceivePack extends ReceivePack implements PreReceiveHook, P
92
112
protected final IStoredSettings settings ;
93
113
94
114
protected final IGitblit gitblit ;
115
+
116
+ protected final ITicketService ticketService ;
117
+
118
+ protected final TicketNotifier ticketNotifier ;
119
+
95
120
96
121
public GitblitReceivePack (
97
122
IGitblit gitblit ,
@@ -114,6 +139,14 @@ public GitblitReceivePack(
114
139
} catch (IOException e ) {
115
140
}
116
141
142
+ if (gitblit .getTicketService ().isAcceptingTicketUpdates (repository )) {
143
+ this .ticketService = gitblit .getTicketService ();
144
+ this .ticketNotifier = this .ticketService .createNotifier ();
145
+ } else {
146
+ this .ticketService = null ;
147
+ this .ticketNotifier = null ;
148
+ }
149
+
117
150
// set advanced ref permissions
118
151
setAllowCreates (user .canCreateRef (repository ));
119
152
setAllowDeletes (user .canDeleteRef (repository ));
@@ -500,6 +533,104 @@ protected void executeCommands() {
500
533
}
501
534
}
502
535
}
536
+
537
+ //
538
+ // if there are ref update receive commands that were
539
+ // successfully processed and there is an active ticket service for the repository
540
+ // then process any referenced tickets
541
+ //
542
+ if (ticketService != null ) {
543
+ List <ReceiveCommand > allUpdates = ReceiveCommand .filter (batch .getCommands (), Result .OK );
544
+ if (!allUpdates .isEmpty ()) {
545
+ int ticketsProcessed = 0 ;
546
+ for (ReceiveCommand cmd : allUpdates ) {
547
+ switch (cmd .getType ()) {
548
+ case CREATE :
549
+ case UPDATE :
550
+ if (cmd .getRefName ().startsWith (Constants .R_HEADS )) {
551
+ Collection <TicketModel > tickets = processReferencedTickets (cmd );
552
+ ticketsProcessed += tickets .size ();
553
+ for (TicketModel ticket : tickets ) {
554
+ ticketNotifier .queueMailing (ticket );
555
+ }
556
+ }
557
+ break ;
558
+
559
+ case UPDATE_NONFASTFORWARD :
560
+ if (cmd .getRefName ().startsWith (Constants .R_HEADS )) {
561
+ String base = JGitUtils .getMergeBase (getRepository (), cmd .getOldId (), cmd .getNewId ());
562
+ List <TicketLink > deletedRefs = JGitUtils .identifyTicketsBetweenCommits (getRepository (), settings , base , cmd .getOldId ().name ());
563
+ for (TicketLink link : deletedRefs ) {
564
+ link .isDelete = true ;
565
+ }
566
+ Change deletion = new Change (user .username );
567
+ deletion .pendingLinks = deletedRefs ;
568
+ ticketService .updateTicket (repository , 0 , deletion );
569
+
570
+ Collection <TicketModel > tickets = processReferencedTickets (cmd );
571
+ ticketsProcessed += tickets .size ();
572
+ for (TicketModel ticket : tickets ) {
573
+ ticketNotifier .queueMailing (ticket );
574
+ }
575
+ }
576
+ break ;
577
+ case DELETE :
578
+ //Identify if the branch has been merged
579
+ SortedMap <Integer , String > bases = new TreeMap <Integer , String >();
580
+ try {
581
+ ObjectId dObj = cmd .getOldId ();
582
+ Collection <Ref > tips = getRepository ().getRefDatabase ().getRefs (Constants .R_HEADS ).values ();
583
+ for (Ref ref : tips ) {
584
+ ObjectId iObj = ref .getObjectId ();
585
+ String mergeBase = JGitUtils .getMergeBase (getRepository (), dObj , iObj );
586
+ if (mergeBase != null ) {
587
+ int d = JGitUtils .countCommits (getRepository (), getRevWalk (), mergeBase , dObj .name ());
588
+ bases .put (d , mergeBase );
589
+ //All commits have been merged into some other branch
590
+ if (d == 0 ) {
591
+ break ;
592
+ }
593
+ }
594
+ }
595
+
596
+ if (bases .isEmpty ()) {
597
+ //TODO: Handle orphan branch case
598
+ } else {
599
+ if (bases .firstKey () > 0 ) {
600
+ //Delete references from the remaining commits that haven't been merged
601
+ String mergeBase = bases .get (bases .firstKey ());
602
+ List <TicketLink > deletedRefs = JGitUtils .identifyTicketsBetweenCommits (getRepository (),
603
+ settings , mergeBase , dObj .name ());
604
+
605
+ for (TicketLink link : deletedRefs ) {
606
+ link .isDelete = true ;
607
+ }
608
+ Change deletion = new Change (user .username );
609
+ deletion .pendingLinks = deletedRefs ;
610
+ ticketService .updateTicket (repository , 0 , deletion );
611
+ }
612
+ }
613
+
614
+ } catch (IOException e ) {
615
+ LOGGER .error (null , e );
616
+ }
617
+ break ;
618
+
619
+ default :
620
+ break ;
621
+ }
622
+ }
623
+
624
+ if (ticketsProcessed == 1 ) {
625
+ sendInfo ("1 ticket updated" );
626
+ } else if (ticketsProcessed > 1 ) {
627
+ sendInfo ("{0} tickets updated" , ticketsProcessed );
628
+ }
629
+ }
630
+
631
+ // reset the ticket caches for the repository
632
+ ticketService .resetCaches (repository );
633
+ }
503
634
}
504
635
505
636
protected void setGitblitUrl (String url ) {
@@ -616,4 +747,116 @@ public RepositoryModel getRepositoryModel() {
616
747
public UserModel getUserModel () {
617
748
return user ;
618
749
}
750
+
751
+ /**
752
+ * Automatically closes open tickets and adds references to tickets if made in the commit message.
753
+ *
754
+ * @param cmd
755
+ */
756
+ private Collection <TicketModel > processReferencedTickets (ReceiveCommand cmd ) {
757
+ Map <Long , TicketModel > changedTickets = new LinkedHashMap <Long , TicketModel >();
758
+
759
+ final RevWalk rw = getRevWalk ();
760
+ try {
761
+ rw .reset ();
762
+ rw .markStart (rw .parseCommit (cmd .getNewId ()));
763
+ if (!ObjectId .zeroId ().equals (cmd .getOldId ())) {
764
+ rw .markUninteresting (rw .parseCommit (cmd .getOldId ()));
765
+ }
766
+
767
+ RevCommit c ;
768
+ while ((c = rw .next ()) != null ) {
769
+ rw .parseBody (c );
770
+ List <TicketLink > ticketLinks = JGitUtils .identifyTicketsFromCommitMessage (getRepository (), settings , c );
771
+ if (ticketLinks == null ) {
772
+ continue ;
773
+ }
774
+
775
+ for (TicketLink link : ticketLinks ) {
776
+
777
+ TicketModel ticket = ticketService .getTicket (repository , link .targetTicketId );
778
+ if (ticket == null ) {
779
+ continue ;
780
+ }
781
+
782
+ Change change = null ;
783
+ String commitSha = c .getName ();
784
+ String branchName = Repository .shortenRefName (cmd .getRefName ());
785
+
786
+ switch (link .action ) {
787
+ case Commit : {
788
+ //A commit can reference a ticket in any branch even if the ticket is closed.
789
+ //This allows developers to identify and communicate related issues
790
+ change = new Change (user .username );
791
+ change .referenceCommit (commitSha );
792
+ } break ;
793
+
794
+ case Close : {
795
+ // As this isn't a patchset theres no merging taking place when closing a ticket
796
+ if (ticket .isClosed ()) {
797
+ continue ;
798
+ }
799
+
800
+ change = new Change (user .username );
801
+ change .setField (Field .status , Status .Fixed );
802
+
803
+ if (StringUtils .isEmpty (ticket .responsible )) {
804
+ // unassigned tickets are assigned to the closer
805
+ change .setField (Field .responsible , user .username );
806
+ }
807
+ }
808
+
809
+ default : {
810
+ //No action
811
+ } break ;
812
+ }
813
+
814
+ if (change != null ) {
815
+ ticket = ticketService .updateTicket (repository , ticket .number , change );
816
+ }
817
+
818
+ if (ticket != null ) {
819
+ sendInfo ("" );
820
+ sendHeader ("#{0,number,0}: {1}" , ticket .number , StringUtils .trimString (ticket .title , Constants .LEN_SHORTLOG ));
821
+
822
+ switch (link .action ) {
823
+ case Commit : {
824
+ sendInfo ("referenced by push of {0} to {1}" , commitSha , branchName );
825
+ changedTickets .put (ticket .number , ticket );
826
+ } break ;
827
+
828
+ case Close : {
829
+ sendInfo ("closed by push of {0} to {1}" , commitSha , branchName );
830
+ changedTickets .put (ticket .number , ticket );
831
+ } break ;
832
+
833
+ default : { }
834
+ }
835
+
836
+ sendInfo (ticketService .getTicketUrl (ticket ));
837
+ sendInfo ("" );
838
+ } else {
839
+ switch (link .action ) {
840
+ case Commit : {
841
+ sendError ("FAILED to reference ticket {0} by push of {1}" , link .targetTicketId , commitSha );
842
+ } break ;
843
+
844
+ case Close : {
845
+ sendError ("FAILED to close ticket {0} by push of {1}" , link .targetTicketId , commitSha );
846
+ } break ;
847
+
848
+ default : { }
849
+ }
850
+ }
851
+ }
852
+ }
853
+
854
+ } catch (IOException e ) {
855
+ LOGGER .error ("Can't scan for changes to reference or close" , e );
856
+ } finally {
857
+ rw .reset ();
858
+ }
859
+
860
+ return changedTickets .values ();
861
+ }
619
862
}
0 commit comments