mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Better javadoc, improve handling of submodules
This commit is contained in:
parent
46d0f55781
commit
6d563ed40e
13 changed files with 156 additions and 13 deletions
|
@ -678,7 +678,7 @@ public class Bridge {
|
||||||
private void makeCommitsFromSnapshots(
|
private void makeCommitsFromSnapshots(
|
||||||
ProjectRepo repo,
|
ProjectRepo repo,
|
||||||
Collection<Snapshot> snapshots
|
Collection<Snapshot> snapshots
|
||||||
) throws IOException, SizeLimitExceededException {
|
) throws IOException, GitUserException {
|
||||||
String name = repo.getProjectName();
|
String name = repo.getProjectName();
|
||||||
for (Snapshot snapshot : snapshots) {
|
for (Snapshot snapshot : snapshots) {
|
||||||
Map<String, RawFile> fileTable = repo.getFiles();
|
Map<String, RawFile> fileTable = repo.getFiles();
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
|
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
|
||||||
import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents;
|
import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents;
|
||||||
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
|
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
|
||||||
|
import uk.ac.ic.wlgitbridge.git.exception.GitUserException;
|
||||||
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
|
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
|
||||||
import uk.ac.ic.wlgitbridge.git.util.RepositoryObjectTreeWalker;
|
import uk.ac.ic.wlgitbridge.git.util.RepositoryObjectTreeWalker;
|
||||||
import uk.ac.ic.wlgitbridge.util.Log;
|
import uk.ac.ic.wlgitbridge.util.Log;
|
||||||
|
@ -62,7 +63,7 @@ public class GitProjectRepo implements ProjectRepo {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, RawFile> getFiles()
|
public Map<String, RawFile> getFiles()
|
||||||
throws IOException, SizeLimitExceededException {
|
throws IOException, GitUserException {
|
||||||
Preconditions.checkState(repository.isPresent());
|
Preconditions.checkState(repository.isPresent());
|
||||||
return new RepositoryObjectTreeWalker(
|
return new RepositoryObjectTreeWalker(
|
||||||
repository.get()
|
repository.get()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package uk.ac.ic.wlgitbridge.bridge.repo;
|
||||||
|
|
||||||
import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents;
|
import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents;
|
||||||
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
|
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
|
||||||
|
import uk.ac.ic.wlgitbridge.git.exception.GitUserException;
|
||||||
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
|
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -24,7 +25,7 @@ public interface ProjectRepo {
|
||||||
) throws IOException;
|
) throws IOException;
|
||||||
|
|
||||||
Map<String, RawFile> getFiles(
|
Map<String, RawFile> getFiles(
|
||||||
) throws IOException, SizeLimitExceededException;
|
) throws IOException, GitUserException;
|
||||||
|
|
||||||
Collection<String> commitAndGetMissing(
|
Collection<String> commitAndGetMissing(
|
||||||
GitDirectoryContents gitDirectoryContents
|
GitDirectoryContents gitDirectoryContents
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package uk.ac.ic.wlgitbridge.git.exception;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class InvalidGitRepository extends GitUserException {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return "invalid git repo";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getDescriptionLines() {
|
||||||
|
return Arrays.asList(
|
||||||
|
"Your Git repository is invalid.",
|
||||||
|
"If your project contains a Git submodule,",
|
||||||
|
"please remove it and try again."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -74,7 +74,7 @@ public class WriteLatexPutHook implements PreReceiveHook {
|
||||||
);
|
);
|
||||||
} catch (OutOfDateException e) {
|
} catch (OutOfDateException e) {
|
||||||
receiveCommand.setResult(Result.REJECTED_NONFASTFORWARD);
|
receiveCommand.setResult(Result.REJECTED_NONFASTFORWARD);
|
||||||
} catch (SnapshotPostException e) {
|
} catch (GitUserException e) {
|
||||||
handleSnapshotPostException(receivePack, receiveCommand, e);
|
handleSnapshotPostException(receivePack, receiveCommand, e);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Log.warn("Throwable on pre receive: ", t);
|
Log.warn("Throwable on pre receive: ", t);
|
||||||
|
@ -90,7 +90,7 @@ public class WriteLatexPutHook implements PreReceiveHook {
|
||||||
private void handleSnapshotPostException(
|
private void handleSnapshotPostException(
|
||||||
ReceivePack receivePack,
|
ReceivePack receivePack,
|
||||||
ReceiveCommand receiveCommand,
|
ReceiveCommand receiveCommand,
|
||||||
SnapshotPostException e
|
GitUserException e
|
||||||
) {
|
) {
|
||||||
String message = e.getMessage();
|
String message = e.getMessage();
|
||||||
receivePack.sendError(message);
|
receivePack.sendError(message);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory;
|
import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory;
|
||||||
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
|
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
|
||||||
import uk.ac.ic.wlgitbridge.data.filestore.RepositoryFile;
|
import uk.ac.ic.wlgitbridge.data.filestore.RepositoryFile;
|
||||||
|
import uk.ac.ic.wlgitbridge.git.exception.InvalidGitRepository;
|
||||||
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
|
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -44,7 +45,7 @@ public class RepositoryObjectTreeWalker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RawDirectory getDirectoryContents(
|
public RawDirectory getDirectoryContents(
|
||||||
) throws IOException, SizeLimitExceededException {
|
) throws IOException, SizeLimitExceededException, InvalidGitRepository {
|
||||||
return new RawDirectory(walkGitObjectTree());
|
return new RawDirectory(walkGitObjectTree());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ public class RepositoryObjectTreeWalker {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, RawFile> walkGitObjectTree(
|
private Map<String, RawFile> walkGitObjectTree(
|
||||||
) throws IOException, SizeLimitExceededException {
|
) throws IOException, SizeLimitExceededException, InvalidGitRepository {
|
||||||
Map<String, RawFile> fileContentsTable = new HashMap<>();
|
Map<String, RawFile> fileContentsTable = new HashMap<>();
|
||||||
if (treeWalk == null) {
|
if (treeWalk == null) {
|
||||||
return fileContentsTable;
|
return fileContentsTable;
|
||||||
|
@ -71,9 +72,13 @@ public class RepositoryObjectTreeWalker {
|
||||||
while (treeWalk.next()) {
|
while (treeWalk.next()) {
|
||||||
String path = treeWalk.getPathString();
|
String path = treeWalk.getPathString();
|
||||||
|
|
||||||
|
ObjectId objectId = treeWalk.getObjectId(0);
|
||||||
|
if (!repository.hasObject(objectId)) {
|
||||||
|
throw new InvalidGitRepository();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
byte[] content = repository.open(
|
byte[] content = repository.open(
|
||||||
treeWalk.getObjectId(0)
|
objectId
|
||||||
).getBytes();
|
).getBytes();
|
||||||
fileContentsTable.put(path, new RepositoryFile(path, content));
|
fileContentsTable.put(path, new RepositoryFile(path, content));
|
||||||
} catch (LargeObjectException e) {
|
} catch (LargeObjectException e) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import uk.ac.ic.wlgitbridge.application.config.Oauth2;
|
||||||
import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException;
|
import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException;
|
||||||
import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocRequest;
|
import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocRequest;
|
||||||
import uk.ac.ic.wlgitbridge.util.Instance;
|
import uk.ac.ic.wlgitbridge.util.Instance;
|
||||||
|
import uk.ac.ic.wlgitbridge.util.Log;
|
||||||
import uk.ac.ic.wlgitbridge.util.Util;
|
import uk.ac.ic.wlgitbridge.util.Util;
|
||||||
|
|
||||||
import javax.servlet.*;
|
import javax.servlet.*;
|
||||||
|
@ -34,6 +35,17 @@ public class Oauth2Filter implements Filter {
|
||||||
@Override
|
@Override
|
||||||
public void init(FilterConfig filterConfig) {}
|
public void init(FilterConfig filterConfig) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The original request from git will not contain the Authorization header.
|
||||||
|
*
|
||||||
|
* So, for projects that need auth, we return 401. Git will swallow this
|
||||||
|
* and prompt the user for user/pass, and then make a brand new request.
|
||||||
|
* @param servletRequest
|
||||||
|
* @param servletResponse
|
||||||
|
* @param filterChain
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ServletException
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void doFilter(
|
public void doFilter(
|
||||||
ServletRequest servletRequest,
|
ServletRequest servletRequest,
|
||||||
|
@ -44,24 +56,29 @@ public class Oauth2Filter implements Filter {
|
||||||
((Request) servletRequest).getRequestURI().split("/")[1],
|
((Request) servletRequest).getRequestURI().split("/")[1],
|
||||||
".git"
|
".git"
|
||||||
);
|
);
|
||||||
|
Log.info("[{}] Checking if auth needed", project);
|
||||||
GetDocRequest doc = new GetDocRequest(project);
|
GetDocRequest doc = new GetDocRequest(project);
|
||||||
doc.request();
|
doc.request();
|
||||||
try {
|
try {
|
||||||
doc.getResult();
|
doc.getResult();
|
||||||
} catch (ForbiddenException e) {
|
} catch (ForbiddenException e) {
|
||||||
|
Log.info("[{}] Auth needed", project);
|
||||||
getAndInjectCredentials(
|
getAndInjectCredentials(
|
||||||
|
project,
|
||||||
servletRequest,
|
servletRequest,
|
||||||
servletResponse,
|
servletResponse,
|
||||||
filterChain
|
filterChain
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Log.info("[{}] Auth not needed");
|
||||||
filterChain.doFilter(servletRequest, servletResponse);
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this is ridiculous. Check for error cases first, then return/throw
|
// TODO: this is ridiculous. Check for error cases first, then return/throw
|
||||||
// TODO: also, use an Optional credential, since we treat it as optional
|
// TODO: also, use an Optional credential, since we treat it as optional
|
||||||
private void getAndInjectCredentials(
|
private void getAndInjectCredentials(
|
||||||
|
String projectName,
|
||||||
ServletRequest servletRequest,
|
ServletRequest servletRequest,
|
||||||
ServletResponse servletResponse,
|
ServletResponse servletResponse,
|
||||||
FilterChain filterChain
|
FilterChain filterChain
|
||||||
|
@ -71,6 +88,7 @@ public class Oauth2Filter implements Filter {
|
||||||
|
|
||||||
String authHeader = request.getHeader("Authorization");
|
String authHeader = request.getHeader("Authorization");
|
||||||
if (authHeader != null) {
|
if (authHeader != null) {
|
||||||
|
Log.info("[{}] Authorization header present");
|
||||||
StringTokenizer st = new StringTokenizer(authHeader);
|
StringTokenizer st = new StringTokenizer(authHeader);
|
||||||
if (st.hasMoreTokens()) {
|
if (st.hasMoreTokens()) {
|
||||||
String basic = st.nextToken();
|
String basic = st.nextToken();
|
||||||
|
@ -100,10 +118,9 @@ public class Oauth2Filter implements Filter {
|
||||||
oauth2.getOauth2ClientID(),
|
oauth2.getOauth2ClientID(),
|
||||||
oauth2.getOauth2ClientSecret()
|
oauth2.getOauth2ClientSecret()
|
||||||
)
|
)
|
||||||
)
|
).execute().getAccessToken();
|
||||||
.execute().getAccessToken();
|
|
||||||
} catch (TokenResponseException e) {
|
} catch (TokenResponseException e) {
|
||||||
unauthorized(response);
|
unauthorized(projectName, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Credential cred = new Credential.Builder(
|
final Credential cred = new Credential.Builder(
|
||||||
|
@ -118,7 +135,7 @@ public class Oauth2Filter implements Filter {
|
||||||
servletResponse
|
servletResponse
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
unauthorized(response);
|
unauthorized(projectName, response);
|
||||||
}
|
}
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new Error("Couldn't retrieve authentication", e);
|
throw new Error("Couldn't retrieve authentication", e);
|
||||||
|
@ -126,7 +143,7 @@ public class Oauth2Filter implements Filter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unauthorized(response);
|
unauthorized(projectName, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,8 +151,10 @@ public class Oauth2Filter implements Filter {
|
||||||
public void destroy() {}
|
public void destroy() {}
|
||||||
|
|
||||||
private void unauthorized(
|
private void unauthorized(
|
||||||
|
String projectName,
|
||||||
ServletResponse servletResponse
|
ServletResponse servletResponse
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
Log.info("[{}] Unauthorized", projectName);
|
||||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||||
response.setContentType("text/plain");
|
response.setContentType("text/plain");
|
||||||
response.setHeader("WWW-Authenticate", "Basic realm=\"Git Bridge\"");
|
response.setHeader("WWW-Authenticate", "Basic realm=\"Git Bridge\"");
|
||||||
|
|
|
@ -39,6 +39,13 @@ public abstract class SnapshotAPIRequest<T extends Result> extends Request<T> {
|
||||||
).intercept(request1);
|
).intercept(request1);
|
||||||
oauth2.intercept(request1);
|
oauth2.intercept(request1);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
request.setInterceptor(request1 -> {
|
||||||
|
new BasicAuthentication(
|
||||||
|
USERNAME,
|
||||||
|
PASSWORD
|
||||||
|
).intercept(request1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,9 @@ public class WLGitBridgeIntegrationTest {
|
||||||
put("wlgbCanSwapProjects", new HashMap<String, SnapshotAPIState>() {{
|
put("wlgbCanSwapProjects", new HashMap<String, SnapshotAPIState>() {{
|
||||||
put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/wlgbCanSwapProjects/state/state.json")).build());
|
put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/wlgbCanSwapProjects/state/state.json")).build());
|
||||||
}});
|
}});
|
||||||
|
put("pushSubmoduleFailsWithInvalidGitRepo", new HashMap<String, SnapshotAPIState>() {{
|
||||||
|
put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/pushSubmoduleFailsWithInvalidGitRepo/state/state.json")).build());
|
||||||
|
}});
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
|
@ -624,6 +627,43 @@ public class WLGitBridgeIntegrationTest {
|
||||||
wlgb.stop();
|
wlgb.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final List<String> EXPECTED_OUT_PUSH_SUBMODULE = Arrays.asList(
|
||||||
|
"remote: hint: Your Git repository is invalid.",
|
||||||
|
"remote: hint: If your project contains a Git submodule,",
|
||||||
|
"remote: hint: please remove it and try again.",
|
||||||
|
"To http://127.0.0.1:33875/testproj.git",
|
||||||
|
"! [remote rejected] master -> master (invalid git repo)",
|
||||||
|
"error: failed to push some refs to 'http://127.0.0.1:33875/testproj.git'"
|
||||||
|
);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void pushSubmoduleFailsWithInvalidGitRepo() throws IOException, GitAPIException, InterruptedException {
|
||||||
|
MockSnapshotServer server = new MockSnapshotServer(3875, getResource("/pushSubmoduleFailsWithInvalidGitRepo").toFile());
|
||||||
|
server.start();
|
||||||
|
server.setState(states.get("pushSubmoduleFailsWithInvalidGitRepo").get("state"));
|
||||||
|
GitBridgeApp wlgb = new GitBridgeApp(new String[] {
|
||||||
|
makeConfigFile(33875, 3875)
|
||||||
|
});
|
||||||
|
wlgb.run();
|
||||||
|
File dir = folder.newFolder();
|
||||||
|
File testprojDir = cloneRepository("testproj", 33875, dir);
|
||||||
|
runtime.exec("mkdir sub", null, testprojDir).waitFor();
|
||||||
|
File sub = new File(testprojDir, "sub");
|
||||||
|
runtime.exec("touch sub.txt", null, sub).waitFor();
|
||||||
|
runtime.exec("git init", null, sub).waitFor();
|
||||||
|
runtime.exec("git add -A", null, sub).waitFor();
|
||||||
|
runtime.exec("git commit -m \"sub\"", null, sub).waitFor();
|
||||||
|
runtime.exec("git add -A", null, testprojDir).waitFor();
|
||||||
|
runtime.exec("git commit -m \"push\"", null, testprojDir).waitFor();
|
||||||
|
Process gitPush = runtime.exec("git push", null, testprojDir);
|
||||||
|
int pushExitCode = gitPush.waitFor();
|
||||||
|
wlgb.stop();
|
||||||
|
assertEquals(1, pushExitCode);
|
||||||
|
List<String> actual = Util.linesFromStream(gitPush.getErrorStream(), 2, "[K");
|
||||||
|
assertEquals(EXPECTED_OUT_PUSH_SUBMODULE, actual);
|
||||||
|
wlgb.stop();
|
||||||
|
}
|
||||||
|
|
||||||
private File cloneRepository(String repositoryName, int port, File dir) throws IOException, InterruptedException {
|
private File cloneRepository(String repositoryName, int port, File dir) throws IOException, InterruptedException {
|
||||||
String repo = "git clone http://127.0.0.1:" + port + "/" + repositoryName + ".git";
|
String repo = "git clone http://127.0.0.1:" + port + "/" + repositoryName + ".git";
|
||||||
Process gitProcess = runtime.exec(repo, null, dir);
|
Process gitProcess = runtime.exec(repo, null, dir);
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"project": "testproj",
|
||||||
|
"getDoc": {
|
||||||
|
"versionID": 1,
|
||||||
|
"createdAt": "2014-11-30T18:40:58.123Z",
|
||||||
|
"email": "jdleesmiller+1@gmail.com",
|
||||||
|
"name": "John+1"
|
||||||
|
},
|
||||||
|
"getSavedVers": [
|
||||||
|
{
|
||||||
|
"versionID": 1,
|
||||||
|
"comment": "added more info on doc GET and error details",
|
||||||
|
"email": "jdleesmiller+1@gmail.com",
|
||||||
|
"name": "John+1",
|
||||||
|
"createdAt": "2014-11-30T18:47:01.333Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"getForVers": [
|
||||||
|
{
|
||||||
|
"versionID": 1,
|
||||||
|
"srcs": [
|
||||||
|
{
|
||||||
|
"content": "content\n",
|
||||||
|
"path": "main.tex"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "This text is from another file.",
|
||||||
|
"path": "foo/bar/test.tex"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"atts": [
|
||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:3875/state/testproj/min_mean_wait_evm_7_eps_150dpi.png",
|
||||||
|
"path": "min_mean_wait_evm_7_eps_150dpi.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"push": "success",
|
||||||
|
"postback": {
|
||||||
|
"type": "success",
|
||||||
|
"versionID": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
This text is from another file.
|
|
@ -0,0 +1 @@
|
||||||
|
content
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
Loading…
Reference in a new issue