1   /*
2    * Copyright 2017 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package com.linecorp.centraldogma.server.internal.mirror;
18  
19  import static java.util.Objects.requireNonNull;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.HashMap;
24  import java.util.Map;
25  import java.util.concurrent.locks.Lock;
26  import java.util.concurrent.locks.ReentrantLock;
27  import java.util.function.Consumer;
28  
29  import org.eclipse.jgit.api.FetchCommand;
30  import org.eclipse.jgit.api.GarbageCollectCommand;
31  import org.eclipse.jgit.api.Git;
32  import org.eclipse.jgit.api.LsRemoteCommand;
33  import org.eclipse.jgit.api.PushCommand;
34  import org.eclipse.jgit.api.TransportCommand;
35  import org.eclipse.jgit.lib.EmptyProgressMonitor;
36  import org.eclipse.jgit.lib.ProgressMonitor;
37  import org.eclipse.jgit.lib.Repository;
38  import org.eclipse.jgit.lib.RepositoryBuilder;
39  import org.eclipse.jgit.transport.URIish;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  import com.linecorp.centraldogma.server.internal.IsolatedSystemReader;
44  import com.linecorp.centraldogma.server.internal.JGitUtil;
45  
46  final class GitWithAuth extends Git {
47  
48      private static final Logger logger = LoggerFactory.getLogger(GitWithAuth.class);
49  
50      /**
51       * One of the Locks in this array is locked while a Git repository is accessed so that other GitMirrors
52       * that access the same repository cannot access it at the same time. The lock is chosen based on the
53       * hash code of the Git repository path. See {@link #getLock(File)} for more information.
54       *
55       * <p>The number of available locks is hard-coded, but it should be large enough for most use cases.
56       */
57      private static final Lock[] locks = new Lock[1024];
58  
59      static {
60          IsolatedSystemReader.install();
61  
62          for (int i = 0; i < locks.length; i++) {
63              locks[i] = new ReentrantLock();
64          }
65      }
66  
67      private static Lock getLock(File repoDir) {
68          final int h = repoDir.getPath().hashCode();
69          return locks[Math.abs((h ^ h >>> 16) % locks.length)];
70      }
71  
72      private final AbstractGitMirror mirror;
73      private final Lock lock;
74      private final URIish remoteUri;
75      private final Map<String, ProgressMonitor> progressMonitors = new HashMap<>();
76      private final Consumer<TransportCommand<?, ?>> configurator;
77  
78      GitWithAuth(AbstractGitMirror mirror, File repoDir, URIish remoteUri,
79                  Consumer<TransportCommand<?, ?>> configurator) throws IOException {
80          super(repo(repoDir));
81          this.mirror = mirror;
82          lock = getLock(repoDir);
83          this.remoteUri = remoteUri;
84          this.configurator = configurator;
85      }
86  
87      URIish remoteUri() {
88          return remoteUri;
89      }
90  
91      private static Repository repo(File repoDir) throws IOException {
92          final Lock lock = getLock(repoDir);
93          boolean success = false;
94          lock.lock();
95          try {
96              repoDir.getParentFile().mkdirs();
97              final Repository repo = new RepositoryBuilder().setGitDir(repoDir).setBare().build();
98              if (!repo.getObjectDatabase().exists()) {
99                  repo.create(true);
100             }
101 
102             JGitUtil.applyDefaultsAndSave(repo.getConfig());
103             success = true;
104             return repo;
105         } finally {
106             if (!success) {
107                 lock.unlock();
108             }
109         }
110     }
111 
112     @Override
113     public void close() {
114         try {
115             super.close();
116         } finally {
117             try {
118                 getRepository().close();
119             } finally {
120                 lock.unlock();
121             }
122         }
123     }
124 
125     private ProgressMonitor progressMonitor(String name) {
126         return progressMonitors.computeIfAbsent(name, MirrorProgressMonitor::new);
127     }
128 
129     @Override
130     public FetchCommand fetch() {
131         final FetchCommand command = super.fetch();
132         configurator.accept(command);
133         return command.setProgressMonitor(progressMonitor("fetch"));
134     }
135 
136     @Override
137     public PushCommand push() {
138         final PushCommand command = super.push();
139         configurator.accept(command);
140         return command.setProgressMonitor(progressMonitor("push"));
141     }
142 
143     @Override
144     public LsRemoteCommand lsRemote() {
145         final LsRemoteCommand command = super.lsRemote();
146         configurator.accept(command);
147         return command;
148     }
149 
150     @Override
151     public GarbageCollectCommand gc() {
152         return super.gc().setProgressMonitor(progressMonitor("gc"));
153     }
154 
155     private final class MirrorProgressMonitor extends EmptyProgressMonitor {
156 
157         private final String operationName;
158 
159         MirrorProgressMonitor(String operationName) {
160             this.operationName = requireNonNull(operationName, "operationName");
161         }
162 
163         @Override
164         public void beginTask(String title, int totalWork) {
165             if (totalWork > 0 && logger.isInfoEnabled()) {
166                 logger.info("[{}] {} ({}, total: {})", operationName, mirror.remoteRepoUri(), title, totalWork);
167             }
168         }
169     }
170 }