1
+ package com .englishtown .stash .hook ;
2
+
3
+ import com .atlassian .stash .hook .repository .AsyncPostReceiveRepositoryHook ;
4
+ import com .atlassian .stash .hook .repository .RepositoryHookContext ;
5
+ import com .atlassian .stash .i18n .I18nService ;
6
+ import com .atlassian .stash .internal .scm .git .GitCommandExitHandler ;
7
+ import com .atlassian .stash .repository .RefChange ;
8
+ import com .atlassian .stash .repository .Repository ;
9
+ import com .atlassian .stash .scm .CommandExitHandler ;
10
+ import com .atlassian .stash .scm .git .GitScm ;
11
+ import com .atlassian .stash .scm .git .GitScmCommandBuilder ;
12
+ import com .atlassian .stash .setting .RepositorySettingsValidator ;
13
+ import com .atlassian .stash .setting .Settings ;
14
+ import com .atlassian .stash .setting .SettingsValidationErrors ;
15
+ import org .slf4j .Logger ;
16
+ import org .slf4j .LoggerFactory ;
17
+
18
+ import javax .annotation .Nonnull ;
19
+ import java .net .URI ;
20
+ import java .net .URISyntaxException ;
21
+ import java .util .Collection ;
22
+
23
+ public class MirrorRepositoryHook implements AsyncPostReceiveRepositoryHook , RepositorySettingsValidator {
24
+
25
+ static final String SETTING_MIRROR_REPO_URL = "mirrorRepoUrl" ;
26
+ static final String SETTING_USERNAME = "username" ;
27
+ static final String SETTING_PASSWORD = "password" ;
28
+
29
+ private final GitScm gitScm ;
30
+ private final I18nService i18nService ;
31
+ private static final Logger logger = LoggerFactory .getLogger (MirrorRepositoryHook .class );
32
+
33
+ public MirrorRepositoryHook (GitScm gitScm , I18nService i18nService ) {
34
+ this .gitScm = gitScm ;
35
+ this .i18nService = i18nService ;
36
+ }
37
+
38
+ /**
39
+ * Calls the remote stash instance(s) to push the latest changes
40
+ * <p/>
41
+ * Callback method that is called just after a push is completed (or a pull request accepted).
42
+ * This hook executes <i>after</i> the processing of a push and will not block the user client.
43
+ * <p/>
44
+ * Despite being asynchronous, the user who initiated this change is still available from
45
+ *
46
+ * @param context the context which the hook is being run with
47
+ * @param refChanges the refs that have just been updated
48
+ */
49
+ @ Override
50
+ public void postReceive (
51
+ @ Nonnull RepositoryHookContext context ,
52
+ @ Nonnull Collection <RefChange > refChanges ) {
53
+
54
+ try {
55
+ logger .debug ("MirrorRepositoryHook: postReceive started." );
56
+
57
+ Settings settings = context .getSettings ();
58
+ String mirrorRepoUrl = settings .getString (SETTING_MIRROR_REPO_URL );
59
+ String username = settings .getString (SETTING_USERNAME );
60
+ String password = settings .getString (SETTING_PASSWORD );
61
+
62
+ URI authenticatedUrl = getAuthenticatedUrl (mirrorRepoUrl , username , password );
63
+ GitScmCommandBuilder builder = gitScm .getCommandBuilderFactory ().builder (context .getRepository ());
64
+ CommandExitHandler exitHandler = new GitCommandExitHandler (i18nService , context .getRepository ());
65
+ PasswordHandler passwordHandler = new PasswordHandler (password , exitHandler );
66
+
67
+ // Call push command with the mirror flag set
68
+ String result = builder
69
+ .command ("push" )
70
+ .argument ("--mirror" )
71
+ .argument (authenticatedUrl .toString ())
72
+ .errorHandler (passwordHandler )
73
+ .exitHandler (passwordHandler )
74
+ .build (passwordHandler )
75
+ .call ();
76
+
77
+ builder .defaultExitHandler ();
78
+ logger .debug ("MirrorRepositoryHook: postReceive completed with result '{}'." , result );
79
+
80
+ } catch (Exception e ) {
81
+ logger .error ("MirrorRepositoryHook: Error running mirror hook" , e );
82
+ }
83
+
84
+ }
85
+
86
+ URI getAuthenticatedUrl (String mirrorRepoUrl , String username , String password ) throws URISyntaxException {
87
+
88
+ URI uri = URI .create (mirrorRepoUrl );
89
+ String userInfo = username + ":" + password ;
90
+
91
+ return new URI (uri .getScheme (), userInfo , uri .getHost (), uri .getPort (),
92
+ uri .getPath (), uri .getQuery (), uri .getFragment ());
93
+
94
+ }
95
+
96
+ /**
97
+ * Validate the given {@code settings} before they are persisted.
98
+ *
99
+ * @param settings to be validated
100
+ * @param errors callback for reporting validation errors.
101
+ * @param repository the context {@code Repository} the settings will be associated with
102
+ */
103
+ @ Override
104
+ public void validate (
105
+ @ Nonnull Settings settings ,
106
+ @ Nonnull SettingsValidationErrors errors ,
107
+ @ Nonnull Repository repository ) {
108
+
109
+ try {
110
+ int count = 0 ;
111
+ logger .debug ("MirrorRepositoryHook: validate started." );
112
+
113
+ String mirrorRepoUrl = settings .getString (SETTING_MIRROR_REPO_URL , "" );
114
+ if (mirrorRepoUrl .isEmpty ()) {
115
+ count ++;
116
+ errors .addFieldError (SETTING_MIRROR_REPO_URL , "The mirror repo url is required." );
117
+ } else {
118
+ URI uri ;
119
+ try {
120
+ uri = URI .create (mirrorRepoUrl );
121
+ if (!uri .getScheme ().toLowerCase ().startsWith ("http" ) || mirrorRepoUrl .contains ("@" )) {
122
+ count ++;
123
+ errors .addFieldError (SETTING_MIRROR_REPO_URL , "The mirror repo url must be a valid http(s) " +
124
+ "URI and the user should be specified separately." );
125
+ }
126
+ } catch (Exception ex ) {
127
+ count ++;
128
+ errors .addFieldError (SETTING_MIRROR_REPO_URL , "The mirror repo url must be a valid http(s) URI." );
129
+ }
130
+ }
131
+
132
+ if (settings .getString (SETTING_USERNAME , "" ).isEmpty ()) {
133
+ count ++;
134
+ errors .addFieldError (SETTING_USERNAME , "The username is required." );
135
+ }
136
+
137
+ if (settings .getString (SETTING_PASSWORD , "" ).isEmpty ()) {
138
+ count ++;
139
+ errors .addFieldError (SETTING_PASSWORD , "The password is required." );
140
+ }
141
+
142
+ logger .debug ("MirrorRepositoryHook: validate completed with {} error(s)." , count );
143
+
144
+ } catch (Exception e ) {
145
+ logger .error ("Error running MirrorRepositoryHook validate." , e );
146
+ errors .addFormError (e .getMessage ());
147
+ }
148
+
149
+ }
150
+
151
+ }
0 commit comments