diff --git a/services/git-bridge/.dockerignore b/services/git-bridge/.dockerignore new file mode 100644 index 0000000000..acfeee7413 --- /dev/null +++ b/services/git-bridge/.dockerignore @@ -0,0 +1,9 @@ +* +!start.sh +!/conf +!/lib +!/src/main +!/pom.xml +!/Makefile +!/LICENSE +!/vendor diff --git a/services/git-bridge/.github/dependabot.yml b/services/git-bridge/.github/dependabot.yml new file mode 100644 index 0000000000..bbb9f78a8d --- /dev/null +++ b/services/git-bridge/.github/dependabot.yml @@ -0,0 +1,21 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" + + pull-request-branch-name: + # Separate sections of the branch name with a hyphen + # Docker images use the branch name and do not support slashes in tags + # https://github.com/overleaf/google-ops/issues/822 + # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#pull-request-branch-nameseparator + separator: "-" + + # Block informal upgrades -- security upgrades use a separate queue. + # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#open-pull-requests-limit + open-pull-requests-limit: 0 + + labels: + - "dependencies" + - "type:maintenance" diff --git a/services/git-bridge/.gitignore b/services/git-bridge/.gitignore new file mode 100644 index 0000000000..74a7f43d6e --- /dev/null +++ b/services/git-bridge/.gitignore @@ -0,0 +1,53 @@ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# Let's not share anything because we're using Maven. + +.idea +*.iml + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ +target/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Local configuration files +conf/runtime.json diff --git a/services/git-bridge/Dockerfile b/services/git-bridge/Dockerfile new file mode 100644 index 0000000000..cf96b5ddf8 --- /dev/null +++ b/services/git-bridge/Dockerfile @@ -0,0 +1,58 @@ +# Dockerfile for git-bridge + +FROM maven:3-jdk-11 as base + +RUN apt-get update && apt-get install -y make git sqlite3 \ + && rm -rf /var/lib/apt/lists + +COPY vendor/envsubst /opt/envsubst +RUN chmod +x /opt/envsubst + +RUN useradd --create-home node + +FROM base as builder + +COPY . /app + +WORKDIR /app + +RUN make package \ +# The name of the created jar contains the current version tag. +# Rename it to a static path that can be used for copying. +&& find /app/target \ + -name 'writelatex-git-bridge*jar-with-dependencies.jar' \ + -exec mv {} /git-bridge.jar \; + +FROM openjdk:11-jre + +RUN apt-get update && apt-get install -y git sqlite3 procps htop net-tools sockstat libjemalloc2 \ + && rm -rf /var/lib/apt/lists + +ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 + +# Install Google Cloud Profiler agent +RUN mkdir -p /opt/cprof && \ + wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent.tar.gz \ + | tar xzv -C /opt/cprof + +# Install Google Cloud Debugger agent +RUN mkdir /opt/cdbg && \ + wget -qO- https://storage.googleapis.com/cloud-debugger/compute-java/debian-wheezy/cdbg_java_agent_gce.tar.gz | \ + tar xvz -C /opt/cdbg + +RUN useradd --create-home node + +COPY --from=builder /git-bridge.jar / + +COPY vendor/envsubst /opt/envsubst +RUN chmod +x /opt/envsubst + +COPY conf/envsubst_template.json envsubst_template.json +COPY start.sh start.sh + +RUN mkdir conf +RUN chown node:node conf + +USER node + +CMD ["/start.sh"] diff --git a/services/git-bridge/LICENSE b/services/git-bridge/LICENSE new file mode 100644 index 0000000000..dc6bb13af4 --- /dev/null +++ b/services/git-bridge/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Winston Li + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/services/git-bridge/Makefile b/services/git-bridge/Makefile new file mode 100644 index 0000000000..b3b2c06fef --- /dev/null +++ b/services/git-bridge/Makefile @@ -0,0 +1,31 @@ +# git-bridge makefile + +MVN_OPTS := "--no-transfer-progress" + +runtime-conf: + /opt/envsubst < conf/envsubst_template.json > conf/runtime.json + + +run: package runtime-conf + java $(GIT_BRIDGE_JVM_ARGS) -jar \ + target/writelatex-git-bridge-1.0-SNAPSHOT-jar-with-dependencies.jar \ + conf/runtime.json + + +build: + mvn $(MVN_OPTS) package -DskipTests + + +test: + mvn $(MVN_OPTS) test + + +clean: + mvn $(MVN_OPTS) clean + + +package: clean + mvn $(MVN_OPTS) package -DskipTests + + +.PHONY: run package build clean test runtime-conf diff --git a/services/git-bridge/README.md b/services/git-bridge/README.md new file mode 100644 index 0000000000..744a97aa45 --- /dev/null +++ b/services/git-bridge/README.md @@ -0,0 +1,138 @@ +# writelatex-git-bridge + +## Docker + +The `Dockerfile` contains all the requirements for building and running the + writelatex-git-bridge. + +```bash +# build the image +docker build -t writelatex-git-bridge . + +# run it with the demo config +docker run -v `pwd`/conf/local.json:/conf/runtime.json writelatex-git-bridge +``` + +## Native install + +### Required packages + + * `maven` (for building, running tests and packaging) + * `jdk-8` (for compiling and running) + +### Commands + +To be run from the base directory: + +**Build jar**: +`mvn package` + +**Run tests**: +`mvn test` + +**Clean**: +`mvn clean` + +To be run from the dev-environment: + +**Build jar**: +`bin/run git-bridge make package` + +**Run tests**: +`bin/run git-bridge make test` + +**Clean**: +`bin/run git-bridge make clean` + +### Installation + +Install dependencies: + +``` +sudo apt-get update +sudo apt-get install -y maven +sudo apt-get install -y openjdk-8-jdk +sudo update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java +sudo update-alternatives --set javac /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/javac +``` + +Create a config file according to the format below. + +Run `mvn package` to build, test, and package it into a jar at `target/writelatex-git-bridge-1.0-SNAPSHOT-jar-with-dependencies.jar`. + +Use `java -jar ` to run the server. + +## Runtime Configuration + +The configuration file is in `.json` format. + + { + "port" (int): the port number, + "rootGitDirectory" (string): the directory in which to store + git repos and the db/atts, + "apiBaseUrl" (string): base url for the snapshot api, + "username" (string, optional): username for http basic auth, + "password" (string, optional): password for http basic auth, + "postbackBaseUrl" (string): the postback url, + "serviceName" (string): current name of writeLaTeX + in case it ever changes, + "oauth2" (object): { null or missing if oauth2 shouldn't be used + "oauth2ClientID" (string): oauth2 client ID, + "oauth2ClientSecret" (string): oauth2 client secret, + "oauth2Server" (string): oauth2 server, + with protocol and + without trailing slash + }, + "repoStore" (object, optional): { configure the repo store + "maxFileSize" (long, optional): maximum size of a file, inclusive + }, + "swapStore" (object, optional): { the place to swap projects to. + if null, type defaults to + "noop" + "type" (string): "s3", "memory", "noop" (not recommended), + "awsAccessKey" (string, optional): only for s3, + "awsSecret" (string, optional): only for s3, + "s3BucketName" (string, optional): only for s3 + }, + "swapJob" (object, optional): { configure the project + swapping job. + if null, defaults to no-op + "minProjects" (int64): lower bound on number of projects + present. The swap job will never go + below this, regardless of what the + watermark shows. Regardless, if + minProjects prevents an eviction, + the swap job will WARN, + "lowGiB" (int32): the low watermark for swapping, + i.e. swap until disk usage is below this, + "highGiB" (int32): the high watermark for swapping, + i.e. start swapping when + disk usage becomes this, + "intervalMillis" (int64): amount of time in between running + swap job and checking watermarks. + 3600000 is 1 hour + } + } + +You have to restart the server for configuration changes to take effect. + + +## Creating OAuth app + +In dev-env, run the following command in mongo to create the oauth application +for git-bridge. + +``` +db.oauthApplications.insert({ + "clientSecret" : "e6b2e9eee7ae2bb653823250bb69594a91db0547fe3790a7135acb497108e62d", + "grants" : [ + "password" + ], + "id" : "264c723c925c13590880751f861f13084934030c13b4452901e73bdfab226edc", + "name" : "Overleaf Git Bridge", + "redirectUris" : [], + "scopes" : [ + "git_bridge" + ] +}) +``` diff --git a/services/git-bridge/conf/envsubst_template.json b/services/git-bridge/conf/envsubst_template.json new file mode 100644 index 0000000000..e9c08ef5c9 --- /dev/null +++ b/services/git-bridge/conf/envsubst_template.json @@ -0,0 +1,33 @@ +{ + "port": ${GIT_BRIDGE_PORT:-8000}, + "bindIp": "${GIT_BRIDGE_BIND_IP:-0.0.0.0}", + "idleTimeout": ${GIT_BRIDGE_IDLE_TIMEOUT:-30000}, + "rootGitDirectory": "${GIT_BRIDGE_ROOT_DIR:-/tmp/wlgb}", + "apiBaseUrl": "${GIT_BRIDGE_API_BASE_URL:-https://localhost/api/v0}", + "postbackBaseUrl": "${GIT_BRIDGE_POSTBACK_BASE_URL:-https://localhost}", + "serviceName": "${GIT_BRIDGE_SERVICE_NAME:-Overleaf}", + "oauth2": { + "oauth2ClientID": "${GIT_BRIDGE_OAUTH2_CLIENT_ID}", + "oauth2ClientSecret": "${GIT_BRIDGE_OAUTH2_CLIENT_SECRET}", + "oauth2Server": "${GIT_BRIDGE_OAUTH2_SERVER:-https://localhost}" + }, + "repoStore": { + "maxFileNum": ${GIT_BRIDGE_REPOSTORE_MAX_FILE_NUM:-2000}, + "maxFileSize": ${GIT_BRIDGE_REPOSTORE_MAX_FILE_SIZE:-52428800} + }, + "swapStore": { + "type": "${GIT_BRIDGE_SWAPSTORE_TYPE:-noop}", + "awsAccessKey": "${GIT_BRIDGE_SWAPSTORE_AWS_ACCESS_KEY}", + "awsSecret": "${GIT_BRIDGE_SWAPSTORE_AWS_SECRET}", + "s3BucketName": "${GIT_BRIDGE_SWAPSTORE_S3_BUCKET_NAME}", + "awsRegion": "${GIT_BRIDGE_SWAPSTORE_AWS_REGION:-us-east-1}" + }, + "swapJob": { + "minProjects": ${GIT_BRIDGE_SWAPJOB_MIN_PROJECTS:-50}, + "lowGiB": ${GIT_BRIDGE_SWAPJOB_LOW_GIB:-128}, + "highGiB": ${GIT_BRIDGE_SWAPJOB_HIGH_GIB:-256}, + "intervalMillis": ${GIT_BRIDGE_SWAPJOB_INTERVAL_MILLIS:-3600000}, + "compressionMethod": "${GIT_BRIDGE_SWAPJOB_COMPRESSION_METHOD:-gzip}" + }, + "sqliteHeapLimitBytes": ${GIT_BRIDGE_SQLITE_HEAP_LIMIT_BYTES:-0} +} diff --git a/services/git-bridge/conf/example_config.json b/services/git-bridge/conf/example_config.json new file mode 100644 index 0000000000..bfad73f461 --- /dev/null +++ b/services/git-bridge/conf/example_config.json @@ -0,0 +1,33 @@ +{ + "port": 8080, + "bindIp": "127.0.0.1", + "idleTimeout": 30000, + "rootGitDirectory": "/tmp/wlgb", + "apiBaseUrl": "https://localhost/api/v0", + "postbackBaseUrl": "https://localhost", + "serviceName": "Overleaf", + "oauth2": { + "oauth2ClientID": "asdf", + "oauth2ClientSecret": "asdf", + "oauth2Server": "https://localhost" + }, + "repoStore": { + "maxFileNum": 2000, + "maxFileSize": 52428800 + }, + "swapStore": { + "type": "s3", + "awsAccessKey": "asdf", + "awsSecret": "asdf", + "s3BucketName": "com.overleaf.testbucket", + "awsRegion": "us-east-1" + }, + "swapJob": { + "minProjects": 50, + "lowGiB": 128, + "highGiB": 256, + "intervalMillis": 3600000, + "compressionMethod": "gzip" + }, + "sqliteHeapLimitBytes": 512000000 +} diff --git a/services/git-bridge/conf/local.json b/services/git-bridge/conf/local.json new file mode 100644 index 0000000000..9ced5cb053 --- /dev/null +++ b/services/git-bridge/conf/local.json @@ -0,0 +1,28 @@ +{ + "port": 8000, + "bindIp": "0.0.0.0", + "idleTimeout": 30000, + "rootGitDirectory": "/tmp/wlgb", + "apiBaseUrl": "http://v2.overleaf.test:4000/api/v0", + "postbackBaseUrl": "http://git-bridge:8000", + "serviceName": "Overleaf", + "oauth2": { + "oauth2ClientID": "264c723c925c13590880751f861f13084934030c13b4452901e73bdfab226edc", + "oauth2ClientSecret": "e6b2e9eee7ae2bb653823250bb69594a91db0547fe3790a7135acb497108e62d", + "oauth2Server": "http://v2.overleaf.test:4000" + }, + "repoStore": { + "maxFileNum": 2000, + "maxFileSize": 52428800 + }, + "swapStore": { + "type": "noop" + }, + "swapJob": { + "minProjects": 50, + "lowGiB": 128, + "highGiB": 256, + "intervalMillis": 3600000, + "compressionMethod": "gzip" + } +} diff --git a/services/git-bridge/lib/newrelic.jar b/services/git-bridge/lib/newrelic.jar new file mode 100644 index 0000000000..f02c7055e4 Binary files /dev/null and b/services/git-bridge/lib/newrelic.jar differ diff --git a/services/git-bridge/newrelic.yml b/services/git-bridge/newrelic.yml new file mode 100644 index 0000000000..ceac8b2e2f --- /dev/null +++ b/services/git-bridge/newrelic.yml @@ -0,0 +1,300 @@ +# This file configures the New Relic Agent. New Relic monitors +# Java applications with deep visibility and low overhead. For more details and additional +# configuration options visit https://docs.newrelic.com/docs/java/java-agent-configuration. +# +# This configuration file is custom generated for Winston +# +# This section is for settings common to all environments. +# Do not add anything above this next line. +common: &default_settings + + # ============================== LICENSE KEY =============================== + # You must specify the license key associated with your New Relic + # account. For example, if your license key is 12345 use this: + # license_key: '12345' + # The key binds your Agent's data to your account in the New Relic service. + license_key: '' + + # Agent Enabled + # Use this setting to disable the agent instead of removing it from the startup command. + # Default is true. + agent_enabled: true + + # Set the name of your application as you'd like it show up in New Relic. + # If enable_auto_app_naming is false, the agent reports all data to this application. + # Otherwise, the agent reports only background tasks (transactions for non-web applications) + # to this application. To report data to more than one application + # (useful for rollup reporting), separate the application names with ";". + # For example, to report data to "My Application" and "My Application 2" use this: + # app_name: My Application;My Application 2 + # This setting is required. Up to 3 different application names can be specified. + # The first application name must be unique. + app_name: Git Bridge + + # To enable high security, set this property to true. When in high + # security mode, the agent will use SSL and obfuscated SQL. Additionally, + # request parameters and message parameters will not be sent to New Relic. + high_security: false + + # Set to true to enable support for auto app naming. + # The name of each web app is detected automatically + # and the agent reports data separately for each one. + # This provides a finer-grained performance breakdown for + # web apps in New Relic. + # Default is false. + enable_auto_app_naming: false + + # Set to true to enable component-based transaction naming. + # Set to false to use the URI of a web request as the name of the transaction. + # Default is true. + enable_auto_transaction_naming: true + + # The agent uses its own log file to keep its logging + # separate from that of your application. Specify the log level here. + # This setting is dynamic, so changes do not require restarting your application. + # The levels in increasing order of verboseness are: + # off, severe, warning, info, fine, finer, finest + # Default is info. + log_level: info + + # Log all data sent to and from New Relic in plain text. + # This setting is dynamic, so changes do not require restarting your application. + # Default is false. + audit_mode: false + + # The number of backup log files to save. + # Default is 1. + log_file_count: 1 + + # The maximum number of kbytes to write to any one log file. + # The log_file_count must be set greater than 1. + # Default is 0 (no limit). + log_limit_in_kbytes: 0 + + # Override other log rolling configuration and roll the logs daily. + # Default is false. + log_daily: false + + # The name of the log file. + # Default is newrelic_agent.log. + log_file_name: newrelic_agent.log + + # The log file directory. + # Default is the logs directory in the newrelic.jar parent directory. + #log_file_path: + + # The agent communicates with New Relic via https by + # default. If you want to communicate with newrelic via http, + # then turn off SSL by setting this value to false. + # This work is done asynchronously to the threads that process your + # application code, so response times will not be directly affected + # by this change. + # Default is true. + ssl: true + + # Proxy settings for connecting to the New Relic server: + # If a proxy is used, the host setting is required. Other settings + # are optional. Default port is 8080. The username and password + # settings will be used to authenticate to Basic Auth challenges + # from a proxy server. + #proxy_host: hostname + #proxy_port: 8080 + #proxy_user: username + #proxy_password: password + + # Limits the number of lines to capture for each stack trace. + # Default is 30 + max_stack_trace_lines: 30 + + # Provides the ability to configure the attributes sent to New Relic. These + # attributes can be found in transaction traces, traced errors, Insight's + # transaction events, and Insight's page views. + attributes: + + # When true, attributes will be sent to New Relic. The default is true. + enabled: true + + #A comma separated list of attribute keys whose values should + # be sent to New Relic. + #include: + + # A comma separated list of attribute keys whose values should + # not be sent to New Relic. + #exclude: + + + # Transaction tracer captures deep information about slow + # transactions and sends this to the New Relic service once a + # minute. Included in the transaction is the exact call sequence of + # the transactions including any SQL statements issued. + transaction_tracer: + + # Transaction tracer is enabled by default. Set this to false to turn it off. + # This feature is not available to Lite accounts and is automatically disabled. + # Default is true. + enabled: true + + # Threshold in seconds for when to collect a transaction + # trace. When the response time of a controller action exceeds + # this threshold, a transaction trace will be recorded and sent to + # New Relic. Valid values are any float value, or (default) "apdex_f", + # which will use the threshold for the "Frustrated" Apdex level + # (greater than four times the apdex_t value). + # Default is apdex_f. + transaction_threshold: apdex_f + + # When transaction tracer is on, SQL statements can optionally be + # recorded. The recorder has three modes, "off" which sends no + # SQL, "raw" which sends the SQL statement in its original form, + # and "obfuscated", which strips out numeric and string literals. + # Default is obfuscated. + record_sql: obfuscated + + # Set this to true to log SQL statements instead of recording them. + # SQL is logged using the record_sql mode. + # Default is false. + log_sql: false + + # Threshold in seconds for when to collect stack trace for a SQL + # call. In other words, when SQL statements exceed this threshold, + # then capture and send to New Relic the current stack trace. This is + # helpful for pinpointing where long SQL calls originate from. + # Default is 0.5 seconds. + stack_trace_threshold: 0.5 + + # Determines whether the agent will capture query plans for slow + # SQL queries. Only supported for MySQL and PostgreSQL. + # Default is true. + explain_enabled: true + + # Threshold for query execution time below which query plans will not + # not be captured. Relevant only when `explain_enabled` is true. + # Default is 0.5 seconds. + explain_threshold: 0.5 + + # Use this setting to control the variety of transaction traces. + # The higher the setting, the greater the variety. + # Set this to 0 to always report the slowest transaction trace. + # Default is 20. + top_n: 20 + + # Error collector captures information about uncaught exceptions and + # sends them to New Relic for viewing. + error_collector: + + # This property enables the collection of errors. If the property is not + # set or the property is set to false, then errors will not be collected. + # Default is true. + enabled: true + + # Use this property to exclude specific exceptions from being reported as errors + # by providing a comma separated list of full class names. + # The default is to exclude akka.actor.ActorKilledException. If you want to override + # this, you must provide any new value as an empty list is ignored. + ignore_errors: akka.actor.ActorKilledException + + # Use this property to exclude specific http status codes from being reported as errors + # by providing a comma separated list of status codes. + # The default is to exclude 404s. If you want to override + # this, you must provide any new value as an empty list is ignored. + ignore_status_codes: 404 + + # Transaction Events are used for Histograms and Percentiles. Unaggregated data is collected + # for each web transaction and sent to the server on harvest. + transaction_events: + + # Set to false to disable transaction events. + # Default is true. + enabled: true + + # Events are collected up to the configured amount. Afterwards, events are sampled to + # maintain an even distribution across the harvest cycle. + # Default is 2000. Setting to 0 will disable. + max_samples_stored: 2000 + + # Cross Application Tracing adds request and response headers to + # external calls using supported HTTP libraries to provide better + # performance data when calling applications monitored by other New Relic Agents. + cross_application_tracer: + + # Set to false to disable cross application tracing. + # Default is true. + enabled: true + + # Thread profiler measures wall clock time, CPU time, and method call counts + # in your application's threads as they run. + # This feature is not available to Lite accounts and is automatically disabled. + thread_profiler: + + # Set to false to disable the thread profiler. + # Default is true. + enabled: true + + # New Relic Real User Monitoring gives you insight into the performance real users are + # experiencing with your website. This is accomplished by measuring the time it takes for + # your users' browsers to download and render your web pages by injecting a small amount + # of JavaScript code into the header and footer of each page. + browser_monitoring: + + # By default the agent automatically inserts API calls in compiled JSPs to + # inject the monitoring JavaScript into web pages. Not all rendering engines are supported. + # See https://docs.newrelic.com/docs/java/real-user-monitoring-in-java#manual_instrumentation + # for instructions to add these manually to your pages. + # Set this attribute to false to turn off this behavior. + auto_instrument: true + + class_transformer: + # This instrumentation reports the name of the user principal returned from + # HttpServletRequest.getUserPrincipal() when servlets and filters are invoked. + com.newrelic.instrumentation.servlet-user: + enabled: false + + com.newrelic.instrumentation.spring-aop-2: + enabled: false + + # Classes loaded by classloaders in this list will not be instrumented. + # This is a useful optimization for runtimes which use classloaders to + # load dynamic classes which the agent would not instrument. + classloader_excludes: + groovy.lang.GroovyClassLoader$InnerLoader, + org.codehaus.groovy.runtime.callsite.CallSiteClassLoader, + com.collaxa.cube.engine.deployment.BPELClassLoader, + org.springframework.data.convert.ClassGeneratingEntityInstantiator$ObjectInstantiatorClassGenerator, + org.mvel2.optimizers.impl.asm.ASMAccessorOptimizer$ContextClassLoader, + gw.internal.gosu.compiler.SingleServingGosuClassLoader, + + # User-configurable custom labels for this agent. Labels are name-value pairs. + # There is a maximum of 64 labels per agent. Names and values are limited to 255 characters. + # Names and values may not contain colons (:) or semicolons (;). + labels: + + # An example label + #label_name: label_value + + +# Application Environments +# ------------------------------------------ +# Environment specific settings are in this section. +# You can use the environment to override the default settings. +# For example, to change the app_name setting. +# Use -Dnewrelic.environment= on the Java startup command line +# to set the environment. +# The default environment is production. + +# NOTE if your application has other named environments, you should +# provide configuration settings for these environments here. + +development: + <<: *default_settings + app_name: Git Bridge (Development) + +test: + <<: *default_settings + app_name: Git Bridge (Test) + +production: + <<: *default_settings + +staging: + <<: *default_settings + app_name: Git Bridge (Staging) diff --git a/services/git-bridge/pom.xml b/services/git-bridge/pom.xml new file mode 100644 index 0000000000..15c8595089 --- /dev/null +++ b/services/git-bridge/pom.xml @@ -0,0 +1,229 @@ + + + 4.0.0 + + uk.ac.ic.wlgitbridge + writelatex-git-bridge + 1.0-SNAPSHOT + + UTF-8 + + + + + + + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Djdk.net.URLClassPath.disableClassPathURLCheck=true + + + + + maven-assembly-plugin + 3.1.0 + + + package + + single + + + + + + + uk.ac.ic.wlgitbridge.Main + + + + jar-with-dependencies + + + + + + + + + junit + junit + 4.13.2 + test + + + + org.jmock + jmock-junit4 + 2.8.4 + test + + + + org.eclipse.jetty + jetty-servlet + 9.4.38.v20210224 + + + + org.eclipse.jetty + jetty-server + 9.4.38.v20210224 + + + + com.google.code.gson + gson + 2.8.2 + + + + org.asynchttpclient + async-http-client + 2.3.0 + + + + org.eclipse.jgit + org.eclipse.jgit + 5.12.0.202106070339-r + + + + org.eclipse.jgit + org.eclipse.jgit.http.server + 5.12.0.202106070339-r + + + + org.xerial + sqlite-jdbc + 3.36.0.1 + + + + joda-time + joda-time + 2.9.9 + + + + com.google.oauth-client + google-oauth-client + 1.23.0 + + + + com.google.http-client + google-http-client + 1.23.0 + + + + com.google.http-client + google-http-client-gson + 1.23.0 + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + ch.qos.logback + logback-classic + 1.2.3 + + + + com.google.guava + guava + 30.1.1-jre + + + + org.mock-server + mockserver-netty + 5.3.0 + test + + + + org.mockito + mockito-core + 3.11.1 + test + + + + com.amazonaws + aws-java-sdk + 1.11.274 + + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.2 + + + + + org.glassfish.jaxb + jaxb-runtime + 2.3.2 + + + + org.apache.httpcomponents + httpclient + 4.5.5 + + + + commons-io + commons-io + 2.10.0 + + + + org.apache.commons + commons-compress + 1.20 + + + + io.prometheus + simpleclient + 0.10.0 + + + + io.prometheus + simpleclient_hotspot + 0.10.0 + + + + io.prometheus + simpleclient_servlet + 0.10.0 + + + diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/Main.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/Main.java new file mode 100644 index 0000000000..f56a3a80e3 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/Main.java @@ -0,0 +1,49 @@ +package uk.ac.ic.wlgitbridge; + +import uk.ac.ic.wlgitbridge.application.GitBridgeApp; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.server.GitBridgeServer; +import uk.ac.ic.wlgitbridge.util.Log; + +import java.util.Arrays; + +/** + * Created by Winston on 01/11/14. + */ + +/** + * This is the entry point into the Git Bridge. + * + * It is responsible for creating the {@link GitBridgeApp} and then running it. + * + * The {@link GitBridgeApp} parses args and creates the {@link GitBridgeServer}. + * + * The {@link GitBridgeServer} creates the {@link Bridge}, among other things. + * + * The {@link Bridge} is the heart of the Git Bridge. Start there, and follow + * the links outwards (which lead back to the Git users and the postback from + * the snapshot API) and inwards (which lead into the components of the Git + * Bridge: the configurable repo store, db store, and swap store, along with + * the project lock, the swap job, the snapshot API, the resource cache + * and the postback manager). + */ +public class Main { + + public static void main(String[] args) { + Log.info( + "Git Bridge started with args: " + + Arrays.toString(args) + ); + try { + new GitBridgeApp(args).run(); + } catch (Throwable t) { + /* So that we get a timestamp */ + Log.error( + "Fatal exception thrown to top level, exiting: ", + t + ); + System.exit(1); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/GitBridgeApp.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/GitBridgeApp.java new file mode 100644 index 0000000000..a184effde4 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/GitBridgeApp.java @@ -0,0 +1,102 @@ +package uk.ac.ic.wlgitbridge.application; + +import uk.ac.ic.wlgitbridge.application.config.Config; +import uk.ac.ic.wlgitbridge.application.exception.ArgsException; +import uk.ac.ic.wlgitbridge.application.exception.ConfigFileException; +import uk.ac.ic.wlgitbridge.server.GitBridgeServer; +import uk.ac.ic.wlgitbridge.util.Log; + +import javax.servlet.ServletException; +import java.io.IOException; + +/** + * Created by Winston on 02/11/14. + */ + +/** + * Class that represents the application. Parses arguments and gives them to the + * server, or dies with a usage message. + */ +public class GitBridgeApp implements Runnable { + + public static final int EXIT_CODE_FAILED = 1; + private static final String USAGE_MESSAGE = + "usage: writelatex-git-bridge [config_file]"; + + private String configFilePath; + Config config; + private GitBridgeServer server; + + /** + * Constructs an instance of the WriteLatex-Git Bridge application. + * @param args args from main, which should be in the format [config_file] + */ + public GitBridgeApp(String[] args) { + try { + parseArguments(args); + loadConfigFile(); + Log.info("Config loaded: {}", config.getSanitisedString()); + } catch (ArgsException e) { + printUsage(); + System.exit(EXIT_CODE_FAILED); + } catch (ConfigFileException e) { + Log.error( + "The property for " + + e.getMissingMember() + + " is invalid. Check your config file." + ); + System.exit(EXIT_CODE_FAILED); + } catch (IOException e) { + Log.error("Invalid config file. Check the file path."); + System.exit(EXIT_CODE_FAILED); + } + try { + server = new GitBridgeServer(config); + } catch (ServletException e) { + Log.error( + "Servlet exception when instantiating GitBridgeServer", + e + ); + } + } + + /** + * Starts the server with the port number and root directory path given in + * the command-line arguments. + */ + @Override + public void run() { + server.start(); + } + + public void stop() { + server.stop(); + } + + /* Helper methods */ + + private void parseArguments(String[] args) throws ArgsException { + checkArgumentsLength(args); + parseConfigFilePath(args); + } + + private void checkArgumentsLength(String[] args) throws ArgsException { + if (args.length < 1) { + throw new ArgsException(); + } + } + + private void parseConfigFilePath(String[] args) throws ArgsException { + configFilePath = args[0]; + } + + private void loadConfigFile() throws ConfigFileException, IOException { + Log.info("Loading config file at path: " + configFilePath); + config = new Config(configFilePath); + } + + private void printUsage() { + System.err.println(USAGE_MESSAGE); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/config/Config.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/config/Config.java new file mode 100644 index 0000000000..8dbef10b23 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/config/Config.java @@ -0,0 +1,211 @@ +package uk.ac.ic.wlgitbridge.application.config; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.application.exception.ConfigFileException; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStoreConfig; +import uk.ac.ic.wlgitbridge.bridge.swap.job.SwapJobConfig; +import uk.ac.ic.wlgitbridge.bridge.swap.store.SwapStoreConfig; +import uk.ac.ic.wlgitbridge.snapshot.base.JSONSource; +import uk.ac.ic.wlgitbridge.util.Instance; + +import javax.annotation.Nullable; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.util.Optional; + +/** + * Created by Winston on 05/12/14. + */ +public class Config implements JSONSource { + + static Config asSanitised(Config config) { + return new Config( + config.port, + config.bindIp, + config.idleTimeout, + config.rootGitDirectory, + config.apiBaseURL, + config.postbackURL, + config.serviceName, + Oauth2.asSanitised(config.oauth2), + config.repoStore, + SwapStoreConfig.sanitisedCopy(config.swapStore), + config.swapJob, + config.sqliteHeapLimitBytes + ); + } + + private int port; + private String bindIp; + private int idleTimeout; + private String rootGitDirectory; + private String apiBaseURL; + private String postbackURL; + private String serviceName; + @Nullable + private Oauth2 oauth2; + @Nullable + private RepoStoreConfig repoStore; + @Nullable + private SwapStoreConfig swapStore; + @Nullable + private SwapJobConfig swapJob; + private int sqliteHeapLimitBytes = 0; + + public Config( + String configFilePath + ) throws ConfigFileException, + IOException { + this(new FileReader(configFilePath)); + } + + Config(Reader reader) { + fromJSON(new Gson().fromJson(reader, JsonElement.class)); + } + + public Config( + int port, + String bindIp, + int idleTimeout, + String rootGitDirectory, + String apiBaseURL, + String postbackURL, + String serviceName, + Oauth2 oauth2, + RepoStoreConfig repoStore, + SwapStoreConfig swapStore, + SwapJobConfig swapJob, + int sqliteHeapLimitBytes + ) { + this.port = port; + this.bindIp = bindIp; + this.idleTimeout = idleTimeout; + this.rootGitDirectory = rootGitDirectory; + this.apiBaseURL = apiBaseURL; + this.postbackURL = postbackURL; + this.serviceName = serviceName; + this.oauth2 = oauth2; + this.repoStore = repoStore; + this.swapStore = swapStore; + this.swapJob = swapJob; + this.sqliteHeapLimitBytes = sqliteHeapLimitBytes; + } + + @Override + public void fromJSON(JsonElement json) { + JsonObject configObject = json.getAsJsonObject(); + port = getElement(configObject, "port").getAsInt(); + bindIp = getElement(configObject, "bindIp").getAsString(); + idleTimeout = getElement(configObject, "idleTimeout").getAsInt(); + rootGitDirectory = getElement( + configObject, + "rootGitDirectory" + ).getAsString(); + String apiBaseURL = getElement( + configObject, + "apiBaseUrl" + ).getAsString(); + if (!apiBaseURL.endsWith("/")) { + apiBaseURL += "/"; + } + this.apiBaseURL = apiBaseURL; + serviceName = getElement(configObject, "serviceName").getAsString(); + postbackURL = getElement(configObject, "postbackBaseUrl").getAsString(); + if (!postbackURL.endsWith("/")) { + postbackURL += "/"; + } + oauth2 = new Gson().fromJson(configObject.get("oauth2"), Oauth2.class); + repoStore = new Gson().fromJson( + configObject.get("repoStore"), RepoStoreConfig.class); + swapStore = new Gson().fromJson( + configObject.get("swapStore"), + SwapStoreConfig.class + ); + swapJob = new Gson().fromJson( + configObject.get("swapJob"), + SwapJobConfig.class + ); + if (configObject.has("sqliteHeapLimitBytes")) { + sqliteHeapLimitBytes = getElement(configObject, "sqliteHeapLimitBytes").getAsInt(); + } + } + + public String getSanitisedString() { + return Instance.prettyGson.toJson(Config.asSanitised(this)); + } + + public int getPort() { + return port; + } + + public String getBindIp() { + return bindIp; + } + + public int getIdleTimeout() { + return idleTimeout; + } + + public String getRootGitDirectory() { + return rootGitDirectory; + } + + public int getSqliteHeapLimitBytes() { + return this.sqliteHeapLimitBytes; + } + + public String getAPIBaseURL() { + return apiBaseURL; + } + + public String getServiceName() { + return serviceName; + } + + public String getPostbackURL() { + return postbackURL; + } + + public boolean isUsingOauth2() { + return oauth2 != null; + } + + public Oauth2 getOauth2() { + if (!isUsingOauth2()) { + throw new AssertionError("Getting oauth2 when not using it"); + } + return oauth2; + } + + public Optional getRepoStore() { + return Optional.ofNullable(repoStore); + } + + public Optional getSwapStore() { + return Optional.ofNullable(swapStore); + } + + public Optional getSwapJob() { + return Optional.ofNullable(swapJob); + } + + private JsonElement getElement(JsonObject configObject, String name) { + JsonElement element = configObject.get(name); + if (element == null) { + throw new RuntimeException(new ConfigFileException(name)); + } + return element; + } + + private String getOptionalString(JsonObject configObject, String name) { + JsonElement element = configObject.get(name); + if (element == null || !element.isJsonPrimitive()) { + return ""; + } + return element.getAsString(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/config/Oauth2.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/config/Oauth2.java new file mode 100644 index 0000000000..daf1170c82 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/config/Oauth2.java @@ -0,0 +1,42 @@ +package uk.ac.ic.wlgitbridge.application.config; + +/** + * Created by winston on 25/10/15. + */ +public class Oauth2 { + + private final String oauth2ClientID; + private final String oauth2ClientSecret; + private final String oauth2Server; + + public Oauth2( + String oauth2ClientID, + String oauth2ClientSecret, + String oauth2Server + ) { + this.oauth2ClientID = oauth2ClientID; + this.oauth2ClientSecret = oauth2ClientSecret; + this.oauth2Server = oauth2Server; + } + + public String getOauth2ClientID() { + return oauth2ClientID; + } + + public String getOauth2ClientSecret() { + return oauth2ClientSecret; + } + + public String getOauth2Server() { + return oauth2Server; + } + + public static Oauth2 asSanitised(Oauth2 oauth2) { + return new Oauth2( + "", + "", + oauth2.oauth2Server + ); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/exception/ArgsException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/exception/ArgsException.java new file mode 100644 index 0000000000..9557c35f29 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/exception/ArgsException.java @@ -0,0 +1,6 @@ +package uk.ac.ic.wlgitbridge.application.exception; + +/** + * Created by Winston on 03/11/14. + */ +public class ArgsException extends Exception {} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/exception/ConfigFileException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/exception/ConfigFileException.java new file mode 100644 index 0000000000..bbe9671dc2 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/exception/ConfigFileException.java @@ -0,0 +1,18 @@ +package uk.ac.ic.wlgitbridge.application.exception; + +/** + * Created by Winston on 05/12/14. + */ +public class ConfigFileException extends Exception { + + private final String missingMember; + + public ConfigFileException(String missingMember) { + this.missingMember = missingMember; + } + + public String getMissingMember() { + return missingMember; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/jetty/NullLogger.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/jetty/NullLogger.java new file mode 100644 index 0000000000..e996bd50c3 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/application/jetty/NullLogger.java @@ -0,0 +1,85 @@ +package uk.ac.ic.wlgitbridge.application.jetty; + +import org.eclipse.jetty.util.log.Logger; + +/** + * Created by Winston on 03/11/14. + */ +public class NullLogger implements Logger { + + @Override + public String getName() { + return "null_logger"; + } + + @Override + public void warn(String s, Object... objects) { + + } + + @Override + public void warn(Throwable throwable) { + + } + + @Override + public void warn(String s, Throwable throwable) { + + } + + @Override + public void info(String s, Object... objects) { + + } + + @Override + public void info(Throwable throwable) { + + } + + @Override + public void info(String s, Throwable throwable) { + + } + + @Override + public boolean isDebugEnabled() { + return false; + } + + @Override + public void setDebugEnabled(boolean b) { + + } + + @Override + public void debug(String s, Object... objects) { + + } + + @Override + public void debug(String s, long l) { + + } + + @Override + public void debug(Throwable throwable) { + + } + + @Override + public void debug(String s, Throwable throwable) { + + } + + @Override + public Logger getLogger(String s) { + return this; + } + + @Override + public void ignore(Throwable throwable) { + + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/Bridge.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/Bridge.java new file mode 100644 index 0000000000..f07e622d80 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/Bridge.java @@ -0,0 +1,839 @@ +package uk.ac.ic.wlgitbridge.bridge; + +import com.google.api.client.auth.oauth2.Credential; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import uk.ac.ic.wlgitbridge.application.config.Config; +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.db.ProjectState; +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SqliteDBStore; +import uk.ac.ic.wlgitbridge.bridge.gc.GcJob; +import uk.ac.ic.wlgitbridge.bridge.gc.GcJobImpl; +import uk.ac.ic.wlgitbridge.bridge.lock.LockGuard; +import uk.ac.ic.wlgitbridge.bridge.lock.ProjectLock; +import uk.ac.ic.wlgitbridge.bridge.repo.*; +import uk.ac.ic.wlgitbridge.bridge.resource.ResourceCache; +import uk.ac.ic.wlgitbridge.bridge.resource.UrlResourceCache; +import uk.ac.ic.wlgitbridge.bridge.snapshot.NetSnapshotApi; +import uk.ac.ic.wlgitbridge.bridge.snapshot.SnapshotApi; +import uk.ac.ic.wlgitbridge.bridge.snapshot.SnapshotApiFacade; +import uk.ac.ic.wlgitbridge.bridge.swap.job.SwapJob; +import uk.ac.ic.wlgitbridge.bridge.swap.job.SwapJobImpl; +import uk.ac.ic.wlgitbridge.bridge.swap.store.S3SwapStore; +import uk.ac.ic.wlgitbridge.bridge.swap.store.SwapStore; +import uk.ac.ic.wlgitbridge.data.CandidateSnapshot; +import uk.ac.ic.wlgitbridge.data.ProjectLockImpl; +import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents; +import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory; +import uk.ac.ic.wlgitbridge.data.filestore.RawFile; +import uk.ac.ic.wlgitbridge.data.model.Snapshot; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; +import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException; +import uk.ac.ic.wlgitbridge.git.exception.FileLimitExceededException; +import uk.ac.ic.wlgitbridge.git.handler.WLReceivePackFactory; +import uk.ac.ic.wlgitbridge.git.handler.WLRepositoryResolver; +import uk.ac.ic.wlgitbridge.git.handler.WLUploadPackFactory; +import uk.ac.ic.wlgitbridge.git.handler.hook.WriteLatexPutHook; +import uk.ac.ic.wlgitbridge.server.FileHandler; +import uk.ac.ic.wlgitbridge.server.PostbackContents; +import uk.ac.ic.wlgitbridge.server.PostbackHandler; +import uk.ac.ic.wlgitbridge.snapshot.base.MissingRepositoryException; +import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotAttachment; +import uk.ac.ic.wlgitbridge.snapshot.push.PostbackManager; +import uk.ac.ic.wlgitbridge.snapshot.push.PostbackPromise; +import uk.ac.ic.wlgitbridge.snapshot.push.PushResult; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.*; +import uk.ac.ic.wlgitbridge.util.Log; + +import java.io.File; +import java.io.IOException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.*; + +/** + * This is the heart of the Git Bridge. You plug in all the parts (project + * lock, repo store, db store, swap store, snapshot api, resource cache and + * postback manager) is called by Git user requests and Overleaf postback + * requests. + * + * Follow these links to go "outward" (to input from Git users and Overleaf): + * + * 1. JGit hooks, which handle user Git requests: + * + * @see WLRepositoryResolver - used on all requests associate a repo with a + * project name, or fail + * +* @see WLUploadPackFactory - used to handle clones and fetches + * + * @see WLReceivePackFactory - used to handle pushes by setting a hook + * @see WriteLatexPutHook - the hook used to handle pushes + * + * 2. The Postback Servlet, which handles postbacks from the Overleaf app + * to confirm that a project is pushed. If a postback is lost, it's fine, we + * just update ourselves on the next access. + * + * @see PostbackHandler - the entry point for postbacks + * + * Follow these links to go "inward" (to the Git Bridge components): + * + * 1. The Project Lock, used to synchronise accesses to projects and shutdown + * the Git Bridge gracefully by preventing further lock acquiring. + * + * @see ProjectLock - the interface used for the Project Lock + * @see ProjectLockImpl - the default concrete implementation + * + * 2. The Repo Store, used to provide repository objects. + * + * The default implementation uses Git on the file system. + * + * @see RepoStore - the interface for the Repo Store + * @see FSGitRepoStore - the default concrete implementation + * @see ProjectRepo - an interface for an actual repo instance + * @see GitProjectRepo - the default concrete implementation + * + * 3. The DB Store, used to store persistent data such as the latest version + * of each project that we have (used for querying the Snapshot API), along + * with caching remote blobs. + * + * The default implementation is SQLite based. + * + * @see DBStore - the interface for the DB store + * @see SqliteDBStore - the default concrete implementation + * + * 4. The Swap Store, used to swap projects to when the disk goes over a + * certain data usage. + * + * The default implementation tarbzips projects to/from Amazon S3. + * + * @see SwapStore - the interface for the Swap Store + * @see S3SwapStore - the default concrete implementation + * + * 5. The Swap Job, which performs the actual swapping on the swap store based + * on various configuration options. + * + * @see SwapJob - the interface for the Swap Job + * @see SwapJobImpl - the default concrete implementation + * + * 6. The Snapshot API, which provides data from the Overleaf app. + * + * @see SnapshotApiFacade - wraps a concrete instance of the Snapshot API. + * @see SnapshotApi - the interface for the Snapshot API. + * @see NetSnapshotApi - the default concrete implementation + * + * 7. The Resource Cache, which provides the data for attachment resources from + * URLs. It will generally fetch from the source on a cache miss. + * + * The default implementation uses the DB Store to maintain a mapping from + * URLs to files in an actual repo. + * + * @see ResourceCache - the interface for the Resource Cache + * @see UrlResourceCache - the default concrete implementation + * + * 8. The Postback Manager, which keeps track of pending postbacks. It stores a + * mapping from project names to postback promises. + * + * @see PostbackManager - the class + * @see PostbackPromise - the object waited on for a postback. + * + */ +public class Bridge { + + private final Config config; + + private final ProjectLock lock; + + private final RepoStore repoStore; + private final DBStore dbStore; + private final SwapStore swapStore; + private final SwapJob swapJob; + private final GcJob gcJob; + + private final SnapshotApiFacade snapshotAPI; + private final ResourceCache resourceCache; + + private final PostbackManager postbackManager; + + /** + * Creates a Bridge from its configurable parts, which are the repo, db and + * swap store, and the swap job config. + * + * This should be the method used to create a Bridge. + * @param config The config to use + * @param repoStore The repo store to use + * @param dbStore The db store to use + * @param swapStore The swap store to use + * @param snapshotApi The snapshot api to use + * @return The constructed Bridge. + */ + public static Bridge make( + Config config, + RepoStore repoStore, + DBStore dbStore, + SwapStore swapStore, + SnapshotApi snapshotApi + ) { + ProjectLock lock = new ProjectLockImpl((int threads) -> + Log.info("Waiting for " + threads + " projects...") + ); + return new Bridge( + config, + lock, + repoStore, + dbStore, + swapStore, + SwapJob.fromConfig( + config.getSwapJob(), + lock, + repoStore, + dbStore, + swapStore + ), + new GcJobImpl(repoStore, lock), + new SnapshotApiFacade(snapshotApi), + new UrlResourceCache(dbStore) + ); + } + + /** + * Creates a bridge from all of its components, not just its configurable + * parts. This is for substituting mock/stub components for testing. + * It's also used by Bridge.make to actually construct the bridge. + * @param lock the {@link ProjectLock} to use + * @param repoStore the {@link RepoStore} to use + * @param dbStore the {@link DBStore} to use + * @param swapStore the {@link SwapStore} to use + * @param swapJob the {@link SwapJob} to use + * @param gcJob + * @param snapshotAPI the {@link SnapshotApi} to use + * @param resourceCache the {@link ResourceCache} to use + */ + Bridge( + Config config, + ProjectLock lock, + RepoStore repoStore, + DBStore dbStore, + SwapStore swapStore, + SwapJob swapJob, + GcJob gcJob, + SnapshotApiFacade snapshotAPI, + ResourceCache resourceCache + ) { + this.config = config; + this.lock = lock; + this.repoStore = repoStore; + this.dbStore = dbStore; + this.swapStore = swapStore; + this.snapshotAPI = snapshotAPI; + this.resourceCache = resourceCache; + this.swapJob = swapJob; + this.gcJob = gcJob; + postbackManager = new PostbackManager(); + Runtime.getRuntime().addShutdownHook(new Thread(this::doShutdown)); + repoStore.purgeNonexistentProjects(dbStore.getProjectNames()); + } + + /** + * This performs the graceful shutdown of the Bridge, which is called by the + * shutdown hook. It acquires the project write lock, which prevents + * work being done for new projects (which acquire the read lock). + * Once it has the write lock, there are no readers left, so the git bridge + * can shut down gracefully. + * + * It is also used by the tests. + */ + void doShutdown() { + Log.info("Shutdown received."); + Log.info("Stopping SwapJob"); + swapJob.stop(); + Log.info("Stopping GcJob"); + gcJob.stop(); + Log.info("Waiting for projects"); + lock.lockAll(); + Log.info("Bye"); + } + + /** + * Starts the swap job, which will begin checking whether projects should be + * swapped with a configurable frequency. + */ + public void startBackgroundJobs() { + swapJob.start(); + gcJob.start(); + } + + public boolean healthCheck() { + try { + dbStore.getNumProjects(); + File rootDirectory = new File("/"); + if (!rootDirectory.exists()) { + throw new Exception("bad filesystem state, root directory does not exist"); + } + Log.info("[HealthCheck] passed"); + return true; + } catch (Exception e) { + Log.error("[HealthCheck] FAILED!", e); + return false; + } + } + + /** + * Performs a check of inconsistencies in the DB. This was used to upgrade + * the schema. + */ + public void checkDB() { + Log.info("Checking DB"); + File rootDir = repoStore.getRootDirectory(); + for (File f : rootDir.listFiles()) { + if (f.getName().equals(".wlgb")) { + continue; + } + String projName = f.getName(); + try (LockGuard __ = lock.lockGuard(projName)) { + File dotGit = new File(f, ".git"); + if (!dotGit.exists()) { + Log.warn("Project: {} has no .git", projName); + continue; + } + ProjectState state = dbStore.getProjectState(projName); + if (state != ProjectState.NOT_PRESENT) { + continue; + } + Log.warn( + "Project: {} not in swap_store, adding", + projName + ); + dbStore.setLastAccessedTime( + projName, + new Timestamp(dotGit.lastModified()) + ); + } + } + } + + /** + * Synchronises the given repository with Overleaf. + * + * It acquires the project lock and calls + * {@link #getUpdatedRepoCritical(Optional, String, GetDocResult)}. + * @param oauth2 The oauth2 to use + * @param projectName The name of the project + * @throws IOException + * @throws GitUserException + */ + public ProjectRepo getUpdatedRepo( + Optional oauth2, + String projectName + ) throws IOException, GitUserException { + try (LockGuard __ = lock.lockGuard(projectName)) { + Optional maybeDoc = snapshotAPI.getDoc(oauth2, projectName); + if (!maybeDoc.isPresent()) { + throw new RepositoryNotFoundException(projectName); + } + GetDocResult doc = maybeDoc.get(); + Log.info("[{}] Updating repository", projectName); + return getUpdatedRepoCritical(oauth2, projectName, doc); + } + } + + /** + * Synchronises the given repository with Overleaf. + * + * Pre: the project lock must be acquired for the given repo. + * + * 1. Queries the project state for the given project name. + * a. NOT_PRESENT = We've never seen it before, and the row for the + * project doesn't even exist. The project definitely + * exists because we would have aborted otherwise. + * b. PRESENT = The project is on disk. + * c. SWAPPED = The project is in the {@link SwapStore} + * + * If the project has never been cloned, it is git init'd. If the project + * is in swap, it is restored to disk. Otherwise, the project was already + * present. + * + * With the project present, snapshots are downloaded from the snapshot + * API with {@link #updateProject(Optional, ProjectRepo)}. + * + * Then, the last accessed time of the project is set to the current time. + * This is to support the LRU of the swap store. + * @param oauth2 + * @param projectName The name of the project + * @throws IOException + * @throws GitUserException + */ + private ProjectRepo getUpdatedRepoCritical( + Optional oauth2, + String projectName, + GetDocResult doc + ) throws IOException, GitUserException { + ProjectRepo repo; + ProjectState state = dbStore.getProjectState(projectName); + switch (state) { + case NOT_PRESENT: + Log.info("[{}] Repo not present", projectName); + String migratedFromID = doc.getMigratedFromID(); + if (migratedFromID != null) { + Log.info("[{}] Has a migratedFromId: {}", projectName, migratedFromID); + try (LockGuard __ = lock.lockGuard(migratedFromID)) { + ProjectState sourceState = dbStore.getProjectState(migratedFromID); + switch (sourceState) { + case NOT_PRESENT: + // Normal init-repo + Log.info("[{}] migrated-from project not present, proceed as normal", + projectName + ); + repo = repoStore.initRepo(projectName); + break; + case SWAPPED: + // Swap back and then copy + swapJob.restore(migratedFromID); + /* Fallthrough */ + default: + // Copy data, and set version to zero + Log.info("[{}] Init from other project: {}", + projectName, + migratedFromID + ); + repo = repoStore.initRepoFromExisting(projectName, migratedFromID); + dbStore.setLatestVersionForProject(migratedFromID, 0); + dbStore.setLastAccessedTime( + migratedFromID, + Timestamp.valueOf(LocalDateTime.now()) + + ); + } + } + break; + } else { + repo = repoStore.initRepo(projectName); + break; + } + case SWAPPED: + swapJob.restore(projectName); + /* Fallthrough */ + default: + repo = repoStore.getExistingRepo(projectName); + } + updateProject(oauth2, repo); + dbStore.setLastAccessedTime( + projectName, + Timestamp.valueOf(LocalDateTime.now()) + ); + return repo; + } + + /** + * The public call to push a project. + * + * It acquires the lock and calls {@link #pushCritical( + * Optional, + * String, + * RawDirectory, + * RawDirectory + * )}, catching exceptions, logging, and rethrowing them. + * @param oauth2 The oauth2 to use for the snapshot API + * @param projectName The name of the project to push to + * @param directoryContents The new contents of the project + * @param oldDirectoryContents The old contents of the project + * @param hostname + * @throws SnapshotPostException + * @throws IOException + * @throws MissingRepositoryException + * @throws ForbiddenException + * @throws GitUserException + */ + public void push( + Optional oauth2, + String projectName, + RawDirectory directoryContents, + RawDirectory oldDirectoryContents, + String hostname + ) throws SnapshotPostException, IOException, MissingRepositoryException, ForbiddenException, GitUserException { + Log.debug("[{}] pushing to Overleaf", projectName); + try (LockGuard __ = lock.lockGuard(projectName)) { + pushCritical( + oauth2, + projectName, + directoryContents, + oldDirectoryContents + ); + } catch (SevereSnapshotPostException e) { + Log.warn( + "[" + projectName + "] Failed to put to Overleaf", + e + ); + throw e; + } catch (SnapshotPostException e) { + /* Stack trace should be printed further up */ + Log.warn( + "[{}] Exception when waiting for postback: {}", + projectName, + e.getClass().getSimpleName() + ); + throw e; + } catch (IOException e) { + Log.warn("[{}] IOException on put: {}", projectName, e); + throw e; + } + + gcJob.queueForGc(projectName); + } + + /** + * Does the work of pushing to a project, assuming the project lock is held. + * The {@link WriteLatexPutHook} is the original caller, and when we return + * without throwing, the commit is committed. + * + * We start off by creating a postback key, which is given in the url when + * the Overleaf app tries to access the atts. + * + * Then creates a {@link CandidateSnapshot} from the old and new project + * contents. The + * {@link CandidateSnapshot} is created using + * {@link #createCandidateSnapshot(String, RawDirectory, RawDirectory)}, + * which creates the snapshot object and writes the push files to the + * atts directory, which is served by the {@link PostbackHandler}. + * The files are deleted at the end of a try-with-resources block. + * + * Then 3 things are used to make the push request to the snapshot API: + * 1. The oauth2 + * 2. The candidate snapshot + * 3. The postback key + * + * If the snapshot API reports this as not successful, we immediately throw + * an {@link OutOfDateException}, which goes back to the user. + * + * Otherwise, we wait (with a timeout) on a promise from the postback + * manager, which can throw back to the user. + * + * If this is successful, we approve the snapshot with + * {@link #approveSnapshot(int, CandidateSnapshot)}, which updates our side + * of the push: the latest version and the URL index store. + * + * Then, we set the last accessed time for the swap store. + * + * Finally, after we return, the push to the repo from the hook is + * successful and the repo gets updated. + * + * @param oauth2 + * @param projectName + * @param directoryContents + * @param oldDirectoryContents + * @throws IOException + * @throws MissingRepositoryException + * @throws ForbiddenException + * @throws SnapshotPostException + * @throws GitUserException + */ + private void pushCritical( + Optional oauth2, + String projectName, + RawDirectory directoryContents, + RawDirectory oldDirectoryContents + ) throws IOException, MissingRepositoryException, ForbiddenException, SnapshotPostException, GitUserException { + Optional maxFileNum = config + .getRepoStore() + .flatMap(RepoStoreConfig::getMaxFileNum); + if (maxFileNum.isPresent()) { + long maxFileNum_ = maxFileNum.get(); + if (directoryContents.getFileTable().size() > maxFileNum_) { + Log.debug("[{}] Too many files: {}/{}", projectName, directoryContents.getFileTable().size(), maxFileNum_); + throw new FileLimitExceededException(directoryContents.getFileTable().size(), maxFileNum_); + } + } + Log.info("[{}] Pushing files ({} new, {} old)", projectName, directoryContents.getFileTable().size(), oldDirectoryContents.getFileTable().size()); + String postbackKey = postbackManager.makeKeyForProject(projectName); + Log.info( + "[{}] Created postback key: {}", + projectName, + postbackKey + ); + try ( + CandidateSnapshot candidate = createCandidateSnapshot( + projectName, + directoryContents, + oldDirectoryContents + ); + ) { + Log.info( + "[{}] Candidate snapshot created: {}", + projectName, + candidate + ); + PushResult result + = snapshotAPI.push(oauth2, candidate, postbackKey); + if (result.wasSuccessful()) { + Log.info( + "[{}] Push to Overleaf successful", + projectName + ); + Log.info("[{}] Waiting for postback...", projectName); + int versionID = + postbackManager.waitForVersionIdOrThrow(projectName); + Log.info( + "[{}] Got version ID for push: {}", + projectName, + versionID + ); + approveSnapshot(versionID, candidate); + Log.info( + "[{}] Approved version ID: {}", + projectName, + versionID + ); + dbStore.setLastAccessedTime( + projectName, + Timestamp.valueOf(LocalDateTime.now()) + ); + } else { + Log.warn( + "[{}] Went out of date while waiting for push", + projectName + ); + throw new OutOfDateException(); + } + } + } + + /** + * A public call that should originate from the {@link FileHandler}. + * + * The {@link FileHandler} serves atts to the Overleaf app during a push. + * The Overleaf app includes the postback key in the request, which was + * originally given on a push request. + * + * This method checks that the postback key matches, and throws if not. + * + * The FileHandler should not serve the file if this throws. + * @param projectName The project name that this key belongs to + * @param postbackKey The key + * @throws InvalidPostbackKeyException If the key doesn't match + */ + public void checkPostbackKey(String projectName, String postbackKey) + throws InvalidPostbackKeyException { + postbackManager.checkPostbackKey(projectName, postbackKey); + } + + /** + * A public call that originates from the postback thread + * {@link PostbackContents#processPostback()}, i.e. once the Overleaf app + * has fetched all the atts and has committed the push and is happy, it + * calls back here, fulfilling the promise that the push + * {@link #push(Optional, String, RawDirectory, RawDirectory, String)} + * is waiting on. + * + * The Overleaf app will have invented a new version for the push, which is + * passed to the promise for the original push request to update the app. + * @param projectName The name of the project being pushed to + * @param postbackKey The postback key being used + * @param versionID the new version id to use + * @throws UnexpectedPostbackException if the postback key is invalid + */ + public void postbackReceivedSuccessfully( + String projectName, + String postbackKey, + int versionID + ) throws UnexpectedPostbackException { + Log.info( + "[{}]" + + " Postback received by postback thread, version: {}", + projectName, + versionID); + postbackManager.postVersionIDForProject( + projectName, + versionID, + postbackKey + ); + } + + /** + * As with {@link #postbackReceivedSuccessfully(String, String, int)}, + * but with an exception instead. + * + * This is based on the JSON body of the postback from the Overleaf app. + * + * The most likely problem is an {@link OutOfDateException}. + * @param projectName The name of the project + * @param postbackKey The postback key being used + * @param exception The exception encountered + * @throws UnexpectedPostbackException If the postback key is invalid + */ + public void postbackReceivedWithException( + String projectName, + String postbackKey, + SnapshotPostException exception + ) throws UnexpectedPostbackException { + Log.warn("[{}] Postback received with exception", projectName); + postbackManager.postExceptionForProject( + projectName, + exception, + postbackKey + ); + } + + /* PRIVATE */ + + /** + * Called by {@link #getUpdatedRepoCritical(Optional, String)} + * + * Does the actual work of getting the snapshots for a project from the + * snapshot API and committing them to a repo. + * + * If any snapshots were found, sets the latest version for the project. + * + * @param oauth2 + * @param repo + * @throws IOException + * @throws GitUserException + */ + private void updateProject( + Optional oauth2, + ProjectRepo repo + ) throws IOException, GitUserException { + String projectName = repo.getProjectName(); + int latestVersionId = dbStore.getLatestVersionForProject(projectName); + Deque snapshots = snapshotAPI.getSnapshots( + oauth2, projectName, latestVersionId); + + makeCommitsFromSnapshots(repo, snapshots); + + // TODO: in case crashes around here, add an + // "updating_from_commit" column to the DB as a way to rollback the + // any failed partial updates before re-trying + // Also need to consider the empty state (a new git init'd repo being + // the rollback target) + if (!snapshots.isEmpty()) { + dbStore.setLatestVersionForProject( + projectName, + snapshots.getLast().getVersionID() + ); + } + } + + /** + * Called by {@link #updateProject(Optional, ProjectRepo)}. + * + * Performs the actual Git commits on the disk. + * + * Each commit adds files to the db store + * ({@link ResourceCache#get(String, String, String, Map, Map, Optional)}, + * and then removes any files that were deleted. + * @param repo The repository to commit to + * @param snapshots The snapshots to commit + * @throws IOException If an IOException occurred + * @throws SizeLimitExceededException If one of the files was too big. + */ + private void makeCommitsFromSnapshots( + ProjectRepo repo, + Collection snapshots + ) throws IOException, GitUserException { + String name = repo.getProjectName(); + Optional maxSize = config + .getRepoStore() + .flatMap(RepoStoreConfig::getMaxFileSize); + for (Snapshot snapshot : snapshots) { + RawDirectory directory = repo.getDirectory(); + Map fileTable = directory.getFileTable(); + List files = new ArrayList<>(); + files.addAll(snapshot.getSrcs()); + for (RawFile file : files) { + long size = file.size(); + /* Can't throw in ifPresent... */ + if (maxSize.isPresent()) { + long maxSize_ = maxSize.get(); + if (size >= maxSize_) { + throw new SizeLimitExceededException( + Optional.of(file.getPath()), size, maxSize_); + } + } + } + Map fetchedUrls = new HashMap<>(); + for (SnapshotAttachment snapshotAttachment : snapshot.getAtts()) { + files.add( + resourceCache.get( + name, + snapshotAttachment.getUrl(), + snapshotAttachment.getPath(), + fileTable, + fetchedUrls, + maxSize + ) + ); + } + Log.info( + "[{}] Committing version ID: {}", + name, + snapshot.getVersionID() + ); + Collection missingFiles = repo.commitAndGetMissing( + new GitDirectoryContents( + files, + repoStore.getRootDirectory(), + name, + snapshot + ) + ); + dbStore.deleteFilesForProject( + name, + missingFiles.toArray(new String[missingFiles.size()]) + ); + } + } + + /** + * Called by + * {@link #pushCritical(Optional, String, RawDirectory, RawDirectory)}. + * + * This call consists of 2 things: Creating the candidate snapshot, + * and writing the atts to the atts directory. + * + * The candidate snapshot RAIIs away those atts (use try-with-resources). + * @param projectName The name of the project + * @param directoryContents The new directory contents + * @param oldDirectoryContents The old directory contents + * @return The {@link CandidateSnapshot} created + * @throws IOException If an I/O exception occurred on writing + */ + private CandidateSnapshot createCandidateSnapshot( + String projectName, + RawDirectory directoryContents, + RawDirectory oldDirectoryContents + ) throws IOException { + CandidateSnapshot candidateSnapshot = new CandidateSnapshot( + projectName, + dbStore.getLatestVersionForProject(projectName), + directoryContents, + oldDirectoryContents + ); + candidateSnapshot.writeServletFiles(repoStore.getRootDirectory()); + return candidateSnapshot; + } + + /** + * Called by + * {@link #pushCritical(Optional, String, RawDirectory, RawDirectory)}. + * + * This method approves a push by setting the latest version and removing + * any deleted files from the db store (files were already added by the + * resources cache). + * @param versionID + * @param candidateSnapshot + */ + private void approveSnapshot( + int versionID, + CandidateSnapshot candidateSnapshot + ) { + List deleted = candidateSnapshot.getDeleted(); + dbStore.setLatestVersionForProject( + candidateSnapshot.getProjectName(), + versionID + ); + dbStore.deleteFilesForProject( + candidateSnapshot.getProjectName(), + deleted.toArray(new String[deleted.size()]) + ); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/DBInitException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/DBInitException.java new file mode 100644 index 0000000000..0c8a6d7b04 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/DBInitException.java @@ -0,0 +1,20 @@ +package uk.ac.ic.wlgitbridge.bridge.db; + +/** + * Created by winston on 23/08/2016. + */ +public class DBInitException extends RuntimeException { + + public DBInitException(String message) { + super(message); + } + + public DBInitException(String message, Throwable cause) { + super(message, cause); + } + + public DBInitException(Throwable cause) { + super(cause); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/DBStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/DBStore.java new file mode 100644 index 0000000000..ff1e05ee0c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/DBStore.java @@ -0,0 +1,44 @@ +package uk.ac.ic.wlgitbridge.bridge.db; + +import java.sql.Timestamp; +import java.util.List; + +/** + * Created by winston on 20/08/2016. + */ +public interface DBStore { + + int getNumProjects(); + + List getProjectNames(); + + void setLatestVersionForProject(String project, int versionID); + + int getLatestVersionForProject(String project); + + void addURLIndexForProject(String projectName, String url, String path); + + void deleteFilesForProject(String project, String... files); + + String getPathForURLInProject(String projectName, String url); + + String getOldestUnswappedProject(); + + void swap(String projectName, String compressionMethod); + + void restore(String projectName); + + String getSwapCompression(String projectName); + + int getNumUnswappedProjects(); + + ProjectState getProjectState(String projectName); + + /** + * Sets the last accessed time for the given project name. + * @param projectName the project's name + * @param time the time, or null if the project is to be swapped + */ + void setLastAccessedTime(String projectName, Timestamp time); + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/ProjectState.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/ProjectState.java new file mode 100644 index 0000000000..ad389f5ac1 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/ProjectState.java @@ -0,0 +1,12 @@ +package uk.ac.ic.wlgitbridge.bridge.db; + +/** + * Created by winston on 24/08/2016. + */ +public enum ProjectState { + + NOT_PRESENT, + PRESENT, + SWAPPED + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/noop/NoopDbStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/noop/NoopDbStore.java new file mode 100644 index 0000000000..0f0f9abb7c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/noop/NoopDbStore.java @@ -0,0 +1,75 @@ +package uk.ac.ic.wlgitbridge.bridge.db.noop; + +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.db.ProjectState; + +import java.sql.Timestamp; +import java.util.List; + +public class NoopDbStore implements DBStore { + + @Override + public int getNumProjects() { + return 0; + } + + @Override + public List getProjectNames() { + return null; + } + + @Override + public void setLatestVersionForProject(String project, int versionID) { + + } + + @Override + public int getLatestVersionForProject(String project) { + return 0; + } + + @Override + public void addURLIndexForProject(String projectName, String url, String path) { + + } + + @Override + public void deleteFilesForProject(String project, String... files) { + + } + + @Override + public String getPathForURLInProject(String projectName, String url) { + return null; + } + + @Override + public String getOldestUnswappedProject() { + return null; + } + + @Override + public int getNumUnswappedProjects() { + return 0; + } + + @Override + public ProjectState getProjectState(String projectName) { + return null; + } + + @Override + public void setLastAccessedTime(String projectName, Timestamp time) { + } + + @Override + public void swap(String projectName, String compressionMethod) {} + + @Override + public void restore(String projectName) {} + + @Override + public String getSwapCompression(String projectName) { + return null; + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SQLQuery.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SQLQuery.java new file mode 100644 index 0000000000..0873d133b8 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SQLQuery.java @@ -0,0 +1,13 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Winston on 20/11/14. + */ +public interface SQLQuery extends SQLUpdate { + + public T processResultSet(ResultSet resultSet) throws SQLException; + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SQLUpdate.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SQLUpdate.java new file mode 100644 index 0000000000..167be212c0 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SQLUpdate.java @@ -0,0 +1,18 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Created by Winston on 20/11/14. + */ +public interface SQLUpdate { + + String getSQL(); + default void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SqliteDBStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SqliteDBStore.java new file mode 100644 index 0000000000..ec59313bae --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SqliteDBStore.java @@ -0,0 +1,228 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite; + +import com.google.common.base.Preconditions; +import uk.ac.ic.wlgitbridge.bridge.db.DBInitException; +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.db.ProjectState; +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.query.*; +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.alter.*; +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create.*; +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.delete.*; +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert.*; + +import java.io.File; +import java.sql.*; +import java.util.List; +import java.util.stream.Stream; + +/** + * Created by Winston on 17/11/14. + */ +public class SqliteDBStore implements DBStore { + + private final Connection connection; + private int heapLimitBytes = 0; + + public SqliteDBStore(File dbFile) { + this(dbFile, 0); + } + + public SqliteDBStore(File dbFile, int heapLimitBytes) { + this.heapLimitBytes = heapLimitBytes; + try { + connection = openConnectionTo(dbFile); + createTables(); + } catch (Throwable t) { + throw new DBInitException(t); + } + } + + @Override + public int getNumProjects() { + return query(new GetNumProjects()); + } + + @Override + public List getProjectNames() { + return query(new GetProjectNamesSQLQuery()); + } + + @Override + public void setLatestVersionForProject( + String projectName, + int versionID + ) { + update(new SetProjectSQLUpdate(projectName, versionID)); + } + + @Override + public int getLatestVersionForProject( + String projectName + ) { + return query(new GetLatestVersionForProjectSQLQuery(projectName)); + } + + @Override + public void addURLIndexForProject( + String projectName, + String url, + String path + ) { + update(new AddURLIndexSQLUpdate(projectName, url, path)); + } + + @Override + public void deleteFilesForProject( + String projectName, + String... paths + ) { + update(new DeleteFilesForProjectSQLUpdate(projectName, paths)); + } + + @Override + public String getPathForURLInProject( + String projectName, + String url + ) { + return query(new GetPathForURLInProjectSQLQuery(projectName, url)); + } + + @Override + public String getOldestUnswappedProject() { + return query(new GetOldestProjectName()); + } + + @Override + public int getNumUnswappedProjects() { + return query(new GetNumUnswappedProjects()); + } + + @Override + public ProjectState getProjectState(String projectName) { + return query(new GetProjectState(projectName)); + } + + @Override + public void setLastAccessedTime( + String projectName, + Timestamp lastAccessed + ) { + update(new SetProjectLastAccessedTime(projectName, lastAccessed)); + } + + @Override + public void swap(String projectName, String compressionMethod) { + update(new UpdateSwap(projectName, compressionMethod)); + } + + @Override + public void restore(String projectName) { + update(new UpdateRestore(projectName)); + } + + @Override + public String getSwapCompression(String projectName) { + return query(new GetSwapCompression(projectName)); + } + + private Connection openConnectionTo(File dbFile) { + File parentDir = dbFile.getParentFile(); + if (!parentDir.exists() && !parentDir.mkdirs()) { + throw new DBInitException( + parentDir.getAbsolutePath() + " directory didn't exist, " + + "and unable to create. Check your permissions." + ); + } + try { + Class.forName("org.sqlite.JDBC"); + } catch (ClassNotFoundException e) { + throw new DBInitException(e); + } + try { + return DriverManager.getConnection( + "jdbc:sqlite:" + dbFile.getAbsolutePath() + ); + } catch (SQLException e) { + throw new DBInitException("Unable to connect to DB", e); + } + } + + private void createTables() { + /* Migrations */ + /* We need to eat exceptions from here */ + try { doUpdate(new SetSoftHeapLimitPragma(this.heapLimitBytes)); } catch (SQLException ignore) {} + try { doUpdate(new ProjectsAddLastAccessed()); } catch (SQLException ignore) {} + try { doUpdate(new ProjectsAddSwapTime()); } catch (SQLException ignore) {} + try { doUpdate(new ProjectsAddRestoreTime()); } catch (SQLException ignore) {} + try { doUpdate(new ProjectsAddSwapCompression()); } catch (SQLException ignore) {} + + /* Create tables (if they don't exist) */ + Stream.of( + new CreateProjectsTableSQLUpdate(), + new CreateProjectsIndexLastAccessed(), + new CreateURLIndexStoreSQLUpdate(), + new CreateIndexURLIndexStore() + ).forEach(this::update); + + /* In the case of needing to change the schema, we need to check that + migrations didn't just fail */ + Preconditions.checkState(query(new LastAccessedColumnExists())); + Preconditions.checkState(query(new SwapTimeColumnExists())); + Preconditions.checkState(query(new RestoreTimeColumnExists())); + Preconditions.checkState(query(new SwapCompressionColumnExists())); + } + + private void update(SQLUpdate update) { + try { + doUpdate(update); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private T query(SQLQuery query) { + try { + return doQuery(query); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private void doUpdate(SQLUpdate update) throws SQLException { + PreparedStatement statement = null; + try { + statement = connection.prepareStatement(update.getSQL()); + update.addParametersToStatement(statement); + statement.executeUpdate(); + } catch (SQLException e) { + throw e; + } finally { + try { + statement.close(); + } catch (Throwable t) { + throw new SQLException(t); + } + } + } + + private T doQuery(SQLQuery query) throws SQLException { + PreparedStatement statement = null; + ResultSet results = null; + try { + statement = connection.prepareStatement(query.getSQL()); + query.addParametersToStatement(statement); + results = statement.executeQuery(); + return query.processResultSet(results); + } catch (SQLException e) { + throw e; + } finally { + if (statement != null) { + statement.close(); + } + if (results != null) { + results.close(); + } + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetLatestVersionForProjectSQLQuery.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetLatestVersionForProjectSQLQuery.java new file mode 100644 index 0000000000..4633fcf7cc --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetLatestVersionForProjectSQLQuery.java @@ -0,0 +1,43 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Winston on 20/11/14. + */ +public class GetLatestVersionForProjectSQLQuery implements SQLQuery { + + private static final String GET_VERSION_IDS_FOR_PROJECT_NAME = + "SELECT `version_id` FROM `projects` WHERE `name` = ?"; + + private final String projectName; + + public GetLatestVersionForProjectSQLQuery(String projectName) { + this.projectName = projectName; + } + + @Override + public Integer processResultSet(ResultSet resultSet) throws SQLException { + int versionID = 0; + while (resultSet.next()) { + versionID = resultSet.getInt("version_id"); + } + return versionID; + } + + @Override + public String getSQL() { + return GET_VERSION_IDS_FOR_PROJECT_NAME; + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setString(1, projectName); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetNumProjects.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetNumProjects.java new file mode 100644 index 0000000000..e5123c2ea1 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetNumProjects.java @@ -0,0 +1,30 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by winston on 24/08/2016. + */ +public class GetNumProjects implements SQLQuery { + + private static final String GET_NUM_PROJECTS = + "SELECT COUNT(*)\n" + + " FROM `projects`"; + + @Override + public String getSQL() { + return GET_NUM_PROJECTS; + } + + @Override + public Integer processResultSet(ResultSet resultSet) throws SQLException { + while (resultSet.next()) { + return resultSet.getInt("COUNT(*)"); + } + throw new IllegalStateException("Count always returns results"); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetNumUnswappedProjects.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetNumUnswappedProjects.java new file mode 100644 index 0000000000..ec7a89b980 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetNumUnswappedProjects.java @@ -0,0 +1,31 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by winston on 24/08/2016. + */ +public class GetNumUnswappedProjects implements SQLQuery { + + private static final String GET_NUM_UNSWAPPED_PROJECTS = + "SELECT COUNT(*)\n" + + " FROM `projects`\n" + + " WHERE `last_accessed` IS NOT NULL"; + + @Override + public String getSQL() { + return GET_NUM_UNSWAPPED_PROJECTS; + } + + @Override + public Integer processResultSet(ResultSet resultSet) throws SQLException { + while (resultSet.next()) { + return resultSet.getInt("COUNT(*)"); + } + throw new IllegalStateException("Count always returns results"); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetOldestProjectName.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetOldestProjectName.java new file mode 100644 index 0000000000..1128a53c01 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetOldestProjectName.java @@ -0,0 +1,31 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by winston on 23/08/2016. + */ +public class GetOldestProjectName implements SQLQuery { + + private static final String GET_OLDEST_PROJECT_NAME = + "SELECT `name`, MIN(`last_accessed`)\n" + + " FROM `projects` \n" + + " WHERE `last_accessed` IS NOT NULL;"; + + @Override + public String getSQL() { + return GET_OLDEST_PROJECT_NAME; + } + + @Override + public String processResultSet(ResultSet resultSet) throws SQLException { + while (resultSet.next()) { + return resultSet.getString("name"); + } + return null; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetPathForURLInProjectSQLQuery.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetPathForURLInProjectSQLQuery.java new file mode 100644 index 0000000000..b4905c7a8e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetPathForURLInProjectSQLQuery.java @@ -0,0 +1,50 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Winston on 20/11/14. + */ +public class GetPathForURLInProjectSQLQuery implements SQLQuery { + + private static final String GET_URL_INDEXES_FOR_PROJECT_NAME = + "SELECT `path` " + + "FROM `url_index_store` " + + "WHERE `project_name` = ? " + + "AND `url` = ?"; + + private final String projectName; + private final String url; + + public GetPathForURLInProjectSQLQuery(String projectName, String url) { + this.projectName = projectName; + this.url = url; + } + + @Override + public String processResultSet(ResultSet resultSet) throws SQLException { + String path = null; + while (resultSet.next()) { + path = resultSet.getString("path"); + } + return path; + } + + @Override + public String getSQL() { + return GET_URL_INDEXES_FOR_PROJECT_NAME; + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setString(1, projectName); + statement.setString(2, url); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetProjectNamesSQLQuery.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetProjectNamesSQLQuery.java new file mode 100644 index 0000000000..fcf0f761b9 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetProjectNamesSQLQuery.java @@ -0,0 +1,34 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Winston on 21/02/15. + */ +public class GetProjectNamesSQLQuery implements SQLQuery> { + + private static final String GET_URL_INDEXES_FOR_PROJECT_NAME = + "SELECT `name` FROM `projects`"; + + @Override + public List processResultSet( + ResultSet resultSet + ) throws SQLException { + List projectNames = new ArrayList<>(); + while (resultSet.next()) { + projectNames.add(resultSet.getString("name")); + } + return projectNames; + } + + @Override + public String getSQL() { + return GET_URL_INDEXES_FOR_PROJECT_NAME; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetProjectState.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetProjectState.java new file mode 100644 index 0000000000..0120289e89 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetProjectState.java @@ -0,0 +1,51 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.ProjectState; +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by winston on 24/08/2016. + */ +public class GetProjectState implements SQLQuery { + + private static final String GET_PROJECT_STATE = + "SELECT `last_accessed`\n" + + " FROM `projects`\n" + + " WHERE `name` = ?"; + + private final String projectName; + + public GetProjectState(String projectName) { + this.projectName = projectName; + } + + @Override + public String getSQL() { + return GET_PROJECT_STATE; + } + + @Override + public ProjectState processResultSet( + ResultSet resultSet + ) throws SQLException { + while (resultSet.next()) { + if (resultSet.getTimestamp("last_accessed") == null) { + return ProjectState.SWAPPED; + } + return ProjectState.PRESENT; + } + return ProjectState.NOT_PRESENT; + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setString(1, projectName); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetSwapCompression.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetSwapCompression.java new file mode 100644 index 0000000000..c7f4d5b510 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/GetSwapCompression.java @@ -0,0 +1,39 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class GetSwapCompression implements SQLQuery { + private static final String GET_SWAP_COMPRESSION = + "SELECT `swap_compression` FROM `projects` WHERE `name` = ?"; + + private final String projectName; + + public GetSwapCompression(String projectName) { + this.projectName = projectName; + } + + @Override + public String processResultSet(ResultSet resultSet) throws SQLException { + String compression = null; + while (resultSet.next()) { + compression = resultSet.getString("swap_compression"); + } + return compression; + } + + @Override + public String getSQL() { + return GET_SWAP_COMPRESSION; + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setString(1, projectName); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/LastAccessedColumnExists.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/LastAccessedColumnExists.java new file mode 100644 index 0000000000..da3b525ff3 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/LastAccessedColumnExists.java @@ -0,0 +1,31 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by winston on 04/09/2016. + */ +public class LastAccessedColumnExists implements SQLQuery { + + private static final String LAST_ACCESSED_COLUMN_EXISTS = + "PRAGMA table_info(`projects`)"; + + @Override + public String getSQL() { + return LAST_ACCESSED_COLUMN_EXISTS; + } + + @Override + public Boolean processResultSet(ResultSet resultSet) throws SQLException { + while (resultSet.next()) { + if (resultSet.getString(2).equals("last_accessed")) { + return true; + } + } + return false; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/RestoreTimeColumnExists.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/RestoreTimeColumnExists.java new file mode 100644 index 0000000000..ed4d0567ae --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/RestoreTimeColumnExists.java @@ -0,0 +1,26 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class RestoreTimeColumnExists implements SQLQuery { + private static final String RESTORE_TIME_COLUMN_EXISTS = + "PRAGMA table_info(`projects`)"; + + @Override + public String getSQL() { + return RESTORE_TIME_COLUMN_EXISTS; + } + + @Override + public Boolean processResultSet(ResultSet resultSet) throws SQLException { + while (resultSet.next()) { + if (resultSet.getString(2).equals("restore_time")) { + return true; + } + } + return false; + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/SwapCompressionColumnExists.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/SwapCompressionColumnExists.java new file mode 100644 index 0000000000..1b1754f26e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/SwapCompressionColumnExists.java @@ -0,0 +1,27 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SwapCompressionColumnExists implements SQLQuery { + private static final String SWAP_COMPRESSION_COLUMN_EXISTS = + "PRAGMA table_info(`projects`)"; + + @Override + public String getSQL() { + return SWAP_COMPRESSION_COLUMN_EXISTS; + } + + @Override + public Boolean processResultSet(ResultSet resultSet) throws SQLException { + while (resultSet.next()) { + if (resultSet.getString(2).equals("swap_compression")) { + return true; + } + } + return false; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/SwapTimeColumnExists.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/SwapTimeColumnExists.java new file mode 100644 index 0000000000..9426dedc5f --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/query/SwapTimeColumnExists.java @@ -0,0 +1,27 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.query; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SwapTimeColumnExists implements SQLQuery { + private static final String SWAP_TIME_COLUMN_EXISTS = + "PRAGMA table_info(`projects`)"; + + @Override + public String getSQL() { + return SWAP_TIME_COLUMN_EXISTS; + } + + @Override + public Boolean processResultSet(ResultSet resultSet) throws SQLException { + while (resultSet.next()) { + if (resultSet.getString(2).equals("swap_time")) { + return true; + } + } + return false; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddLastAccessed.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddLastAccessed.java new file mode 100644 index 0000000000..549d4403b2 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddLastAccessed.java @@ -0,0 +1,19 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.alter; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +/** + * Created by winston on 03/09/2016. + */ +public class ProjectsAddLastAccessed implements SQLUpdate { + + private static final String PROJECTS_ADD_LAST_ACCESSED = + "ALTER TABLE `projects`\n" + + "ADD COLUMN `last_accessed` DATETIME NULL DEFAULT 0"; + + @Override + public String getSQL() { + return PROJECTS_ADD_LAST_ACCESSED; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddRestoreTime.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddRestoreTime.java new file mode 100644 index 0000000000..dd92efcab5 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddRestoreTime.java @@ -0,0 +1,14 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.alter; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +public class ProjectsAddRestoreTime implements SQLUpdate { + private static final String PROJECTS_ADD_RESTORE_TIME = + "ALTER TABLE `projects`\n" + + "ADD COLUMN `restore_time` DATETIME NULL;\n"; + + @Override + public String getSQL() { + return PROJECTS_ADD_RESTORE_TIME; + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddSwapCompression.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddSwapCompression.java new file mode 100644 index 0000000000..c5599d9116 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddSwapCompression.java @@ -0,0 +1,14 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.alter; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +public class ProjectsAddSwapCompression implements SQLUpdate { + private static final String PROJECTS_ADD_SWAP_COMPRESSION = + "ALTER TABLE `projects`\n" + + "ADD COLUMN `swap_compression` VARCHAR NULL;\n"; + + @Override + public String getSQL() { + return PROJECTS_ADD_SWAP_COMPRESSION; + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddSwapTime.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddSwapTime.java new file mode 100644 index 0000000000..da89be8c25 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/ProjectsAddSwapTime.java @@ -0,0 +1,15 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.alter; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +public class ProjectsAddSwapTime implements SQLUpdate { + private static final String PROJECTS_ADD_SWAP_TIME = + "ALTER TABLE `projects`\n" + + "ADD COLUMN `swap_time` DATETIME NULL;\n"; + + @Override + public String getSQL() { + return PROJECTS_ADD_SWAP_TIME; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/SetSoftHeapLimitPragma.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/SetSoftHeapLimitPragma.java new file mode 100644 index 0000000000..da1a98cfa5 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/alter/SetSoftHeapLimitPragma.java @@ -0,0 +1,17 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.alter; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +public class SetSoftHeapLimitPragma implements SQLUpdate { + private int heapLimitBytes = 0; + + public SetSoftHeapLimitPragma(int heapLimitBytes) { + this.heapLimitBytes = heapLimitBytes; + } + + @Override + public String getSQL() { + return "PRAGMA soft_heap_limit="+this.heapLimitBytes+";"; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateIndexURLIndexStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateIndexURLIndexStore.java new file mode 100644 index 0000000000..9b89199b65 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateIndexURLIndexStore.java @@ -0,0 +1,19 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +/** + * Created by Winston on 21/02/15. + */ +public class CreateIndexURLIndexStore implements SQLUpdate { + + public static final String CREATE_INDEX_URL_INDEX_STORE = + "CREATE UNIQUE INDEX IF NOT EXISTS `project_path_index` " + + "ON `url_index_store`(`project_name`, `path`);\n"; + + @Override + public String getSQL() { + return CREATE_INDEX_URL_INDEX_STORE; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateProjectsIndexLastAccessed.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateProjectsIndexLastAccessed.java new file mode 100644 index 0000000000..6e129d8fee --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateProjectsIndexLastAccessed.java @@ -0,0 +1,19 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +/** + * Created by winston on 23/08/2016. + */ +public class CreateProjectsIndexLastAccessed implements SQLUpdate { + + private static final String CREATE_PROJECTS_INDEX_LAST_ACCESSED = + "CREATE INDEX IF NOT EXISTS `projects_index_last_accessed`\n" + + " ON `projects`(`last_accessed`)"; + + @Override + public String getSQL() { + return CREATE_PROJECTS_INDEX_LAST_ACCESSED; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateProjectsTableSQLUpdate.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateProjectsTableSQLUpdate.java new file mode 100644 index 0000000000..15042445ad --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateProjectsTableSQLUpdate.java @@ -0,0 +1,26 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +/** + * Created by Winston on 20/11/14. + */ +public class CreateProjectsTableSQLUpdate implements SQLUpdate { + + private static final String CREATE_PROJECTS_TABLE = + "CREATE TABLE IF NOT EXISTS `projects` (\n" + + " `name` VARCHAR NOT NULL DEFAULT '',\n" + + " `version_id` INT NOT NULL DEFAULT 0,\n" + + " `last_accessed` DATETIME NULL DEFAULT 0,\n" + + " `swap_time` DATETIME NULL,\n" + + " `restore_time` DATETIME NULL,\n" + + " `swap_compression` VARCHAR NULL,\n" + + " PRIMARY KEY (`name`)\n" + + ")"; + + @Override + public String getSQL() { + return CREATE_PROJECTS_TABLE; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateURLIndexStoreSQLUpdate.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateURLIndexStoreSQLUpdate.java new file mode 100644 index 0000000000..3ca0e9cf4c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/create/CreateURLIndexStoreSQLUpdate.java @@ -0,0 +1,28 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +/** + * Created by Winston on 20/11/14. + */ +public class CreateURLIndexStoreSQLUpdate implements SQLUpdate { + + private static final String CREATE_URL_INDEX_STORE = + "CREATE TABLE IF NOT EXISTS `url_index_store` (\n"+ + " `project_name` varchar(10) NOT NULL DEFAULT '',\n"+ + " `url` text NOT NULL,\n"+ + " `path` text NOT NULL,\n"+ + " PRIMARY KEY (`project_name`,`url`),\n"+ + " CONSTRAINT `url_index_store_ibfk_1` " + + "FOREIGN KEY (`project_name`) " + + "REFERENCES `projects` (`name`) " + + "ON DELETE CASCADE " + + "ON UPDATE CASCADE\n"+ + ");\n"; + + @Override + public String getSQL() { + return CREATE_URL_INDEX_STORE; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/delete/DeleteFilesForProjectSQLUpdate.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/delete/DeleteFilesForProjectSQLUpdate.java new file mode 100644 index 0000000000..4390ebe9a0 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/delete/DeleteFilesForProjectSQLUpdate.java @@ -0,0 +1,53 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.delete; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Created by Winston on 20/11/14. + */ +public class DeleteFilesForProjectSQLUpdate implements SQLUpdate { + + private static final String DELETE_URL_INDEXES_FOR_PROJECT_NAME = + "DELETE FROM `url_index_store` " + + "WHERE `project_name` = ? AND path IN ("; + + private final String projectName; + private final String[] paths; + + public DeleteFilesForProjectSQLUpdate( + String projectName, + String... paths + ) { + this.projectName = projectName; + this.paths = paths; + } + + @Override + public String getSQL() { + StringBuilder sb = new StringBuilder( + DELETE_URL_INDEXES_FOR_PROJECT_NAME + ); + for (int i = 0; i < paths.length; i++) { + sb.append("?"); + if (i < paths.length - 1) { + sb.append(", "); + } + } + sb.append(");\n"); + return sb.toString(); + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setString(1, projectName); + for (int i = 0; i < paths.length; i++) { + statement.setString(i + 2, paths[i]); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/AddURLIndexSQLUpdate.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/AddURLIndexSQLUpdate.java new file mode 100644 index 0000000000..5c337bed7a --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/AddURLIndexSQLUpdate.java @@ -0,0 +1,45 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Created by Winston on 20/11/14. + */ +public class AddURLIndexSQLUpdate implements SQLUpdate { + + private static final String ADD_URL_INDEX = + "INSERT OR REPLACE INTO `url_index_store`(" + + "`project_name`, " + + "`url`, " + + "`path`" + + ") VALUES " + + "(?, ?, ?)\n"; + + private final String projectName; + private final String url; + private final String path; + + public AddURLIndexSQLUpdate(String projectName, String url, String path) { + this.projectName = projectName; + this.url = url; + this.path = path; + } + + @Override + public String getSQL() { + return ADD_URL_INDEX; + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setString(1, projectName); + statement.setString(2, url); + statement.setString(3, path); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/SetProjectLastAccessedTime.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/SetProjectLastAccessedTime.java new file mode 100644 index 0000000000..7870822a50 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/SetProjectLastAccessedTime.java @@ -0,0 +1,43 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; + +/** + * Created by winston on 23/08/2016. + */ +public class SetProjectLastAccessedTime implements SQLUpdate { + + private static final String SET_PROJECT_LAST_ACCESSED_TIME = + "UPDATE `projects`\n" + + "SET `last_accessed` = ?\n" + + "WHERE `name` = ?"; + + private final String projectName; + private final Timestamp lastAccessed; + + public SetProjectLastAccessedTime( + String projectName, + Timestamp lastAccessed + ) { + this.projectName = projectName; + this.lastAccessed = lastAccessed; + } + + @Override + public String getSQL() { + return SET_PROJECT_LAST_ACCESSED_TIME; + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setTimestamp(1, lastAccessed); + statement.setString(2, projectName); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/SetProjectSQLUpdate.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/SetProjectSQLUpdate.java new file mode 100644 index 0000000000..717beaec05 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/SetProjectSQLUpdate.java @@ -0,0 +1,39 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Created by Winston on 20/11/14. + */ +public class SetProjectSQLUpdate implements SQLUpdate { + + private static final String SET_PROJECT = + "INSERT OR REPLACE " + + "INTO `projects`(`name`, `version_id`, `last_accessed`) " + + "VALUES (?, ?, DATETIME('now'));\n"; + + private final String projectName; + private final int versionID; + + public SetProjectSQLUpdate(String projectName, int versionID) { + this.projectName = projectName; + this.versionID = versionID; + } + + @Override + public String getSQL() { + return SET_PROJECT; + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setString(1, projectName); + statement.setInt(2, versionID); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/UpdateRestore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/UpdateRestore.java new file mode 100644 index 0000000000..91abc5f473 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/UpdateRestore.java @@ -0,0 +1,40 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; + +public class UpdateRestore implements SQLUpdate { + private static final String UPDATE_RESTORE = + "UPDATE `projects`\n" + + "SET `last_accessed` = ?,\n" + + " `swap_time` = NULL,\n" + + " `restore_time` = ?,\n" + + " `swap_compression` = NULL\n" + + "WHERE `name` = ?;\n"; + + private final String projectName; + private final Timestamp now; + + public UpdateRestore(String projectName) { + this.projectName = projectName; + this.now = Timestamp.valueOf(LocalDateTime.now()); + } + + @Override + public String getSQL() { + return UPDATE_RESTORE; + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setTimestamp(1, now); + statement.setTimestamp(2, now); + statement.setString(3, projectName); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/UpdateSwap.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/UpdateSwap.java new file mode 100644 index 0000000000..24d7815279 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/insert/UpdateSwap.java @@ -0,0 +1,42 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert; + +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLUpdate; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; + +public class UpdateSwap implements SQLUpdate { + private static final String UPDATE_SWAP = + "UPDATE `projects`\n" + + "SET `last_accessed` = NULL,\n" + + " `swap_time` = ?,\n" + + " `restore_time` = NULL,\n" + + " `swap_compression` = ?\n" + + "WHERE `name` = ?;\n"; + + private final String projectName; + private final String compression; + private final Timestamp now; + + public UpdateSwap(String projectName, String compression) { + this.projectName = projectName; + this.compression = compression; + this.now = Timestamp.valueOf(LocalDateTime.now()); + } + + @Override + public String getSQL() { + return UPDATE_SWAP; + } + + @Override + public void addParametersToStatement( + PreparedStatement statement + ) throws SQLException { + statement.setTimestamp(1, now); + statement.setString(2, compression); + statement.setString(3, projectName); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/gc/GcJob.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/gc/GcJob.java new file mode 100644 index 0000000000..74f965d7a4 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/gc/GcJob.java @@ -0,0 +1,34 @@ +package uk.ac.ic.wlgitbridge.bridge.gc; + +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.bridge.repo.ProjectRepo; +import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Is started by the bridge. Every time a project is updated, we queue it for + * GC which executes every hour or so. + * + * We don't queue it into a more immediate Executor because there is no way to + * know if a call to {@link Bridge#updateProject(Optional, ProjectRepo)}, + * which releases the lock, is going to call + * {@link Bridge#push(Optional, String, RawDirectory, RawDirectory, String)}. + * + * We don't want the GC to run in between an update and a push. + */ +public interface GcJob { + + void start(); + + void stop(); + + void onPreGc(Runnable preGc); + + void onPostGc(Runnable postGc); + + void queueForGc(String projectName); + + CompletableFuture waitForRun(); +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/gc/GcJobImpl.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/gc/GcJobImpl.java new file mode 100644 index 0000000000..31c655242b --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/gc/GcJobImpl.java @@ -0,0 +1,141 @@ +package uk.ac.ic.wlgitbridge.bridge.gc; + +import uk.ac.ic.wlgitbridge.bridge.lock.LockGuard; +import uk.ac.ic.wlgitbridge.bridge.lock.ProjectLock; +import uk.ac.ic.wlgitbridge.bridge.repo.ProjectRepo; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.TimerUtils; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Implementation of {@link GcJob} using its own Timer and a synchronized + * queue. + */ +public class GcJobImpl implements GcJob { + + private final RepoStore repoStore; + private final ProjectLock locks; + + private final long intervalMs; + private final Timer timer; + + private final Set gcQueue; + + /** + * Hooks in case they are needed, e.g. for testing. + */ + private AtomicReference preGc; + private AtomicReference postGc; + + /* We need to iterate over and empty it after every run */ + private final Lock jobWaitersLock; + private final List> jobWaiters; + + public GcJobImpl(RepoStore repoStore, ProjectLock locks, long intervalMs) { + this.repoStore = repoStore; + this.locks = locks; + this.intervalMs = intervalMs; + timer = new Timer(); + gcQueue = Collections.newSetFromMap(new ConcurrentHashMap<>()); + preGc = new AtomicReference<>(() -> {}); + postGc = new AtomicReference<>(() -> {}); + jobWaitersLock = new ReentrantLock(); + jobWaiters = new ArrayList<>(); + } + + public GcJobImpl(RepoStore repoStore, ProjectLock locks) { + this( + repoStore, + locks, + TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS) + ); + } + + @Override + public void start() { + Log.info("Starting GC job to run every [{}] ms", intervalMs); + timer.scheduleAtFixedRate( + TimerUtils.makeTimerTask(this::doGC), + intervalMs, + intervalMs + ); + } + + @Override + public void stop() { + Log.info("Stopping GC job"); + timer.cancel(); + } + + @Override + public void onPreGc(Runnable preGc) { + this.preGc.set(preGc); + } + + @Override + public void onPostGc(Runnable postGc) { + this.postGc.set(postGc); + } + + /** + * Needs to be callable from any thread. + * @param projectName + */ + @Override + public void queueForGc(String projectName) { + gcQueue.add(projectName); + } + + @Override + public CompletableFuture waitForRun() { + CompletableFuture ret = new CompletableFuture<>(); + jobWaitersLock.lock(); + try { + jobWaiters.add(ret); + } finally { + jobWaitersLock.unlock(); + } + return ret; + } + + private void doGC() { + Log.info("GC job running"); + int numGcs = 0; + preGc.get().run(); + for ( + Iterator it = gcQueue.iterator(); + it.hasNext(); + it.remove(), ++numGcs + ) { + String proj = it.next(); + Log.info("[{}] Running GC job on project", proj); + try (LockGuard __ = locks.lockGuard(proj)) { + try { + ProjectRepo repo = repoStore.getExistingRepo(proj); + repo.runGC(); + repo.deleteIncomingPacks(); + } catch (IOException e) { + Log.info("[{}] Failed to GC project", proj); + } + } + } + Log.info("GC job finished, num gcs: {}", numGcs); + jobWaitersLock.lock(); + try { + jobWaiters.forEach(w -> w.complete(null)); + } finally { + jobWaitersLock.unlock(); + } + postGc.get().run(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/lock/LockGuard.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/lock/LockGuard.java new file mode 100644 index 0000000000..73c8d253c1 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/lock/LockGuard.java @@ -0,0 +1,10 @@ +package uk.ac.ic.wlgitbridge.bridge.lock; + +/** + * Created by winston on 24/08/2016. + */ +public interface LockGuard extends AutoCloseable { + + void close(); + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/lock/ProjectLock.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/lock/ProjectLock.java new file mode 100644 index 0000000000..98f6ae96c2 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/lock/ProjectLock.java @@ -0,0 +1,23 @@ +package uk.ac.ic.wlgitbridge.bridge.lock; + +/** + * Project Lock class. + * + * The locks should be re-entrant. For example, we are usually holding the lock + * when a project must be restored, which tries to acquire the lock again. + */ +public interface ProjectLock { + + void lockAll(); + + void lockForProject(String projectName); + + void unlockForProject(String projectName); + + /* RAII hahaha */ + default LockGuard lockGuard(String projectName) { + lockForProject(projectName); + return () -> unlockForProject(projectName); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStore.java new file mode 100644 index 0000000000..c323930157 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStore.java @@ -0,0 +1,238 @@ +package uk.ac.ic.wlgitbridge.bridge.repo; + +import com.google.api.client.repackaged.com.google.common.base.Preconditions; +import org.apache.commons.io.FileUtils; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.Project; +import uk.ac.ic.wlgitbridge.util.Tar; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import static uk.ac.ic.wlgitbridge.util.Util.deleteInDirectoryApartFrom; + +/** + * Created by winston on 20/08/2016. + */ +public class FSGitRepoStore implements RepoStore { + + private static final long DEFAULT_MAX_FILE_SIZE = 50 * 1024 * 1024; + + private final String repoStorePath; + + private final File rootDirectory; + + private final long maxFileSize; + + private final Function fsSizer; + + public FSGitRepoStore( + String repoStorePath, + Optional maxFileSize + ) { + this( + repoStorePath, + maxFileSize.orElse(DEFAULT_MAX_FILE_SIZE), + d -> d.getTotalSpace() - d.getFreeSpace() + ); + } + + public FSGitRepoStore( + String repoStorePath, + long maxFileSize, + Function fsSizer + ) { + this.repoStorePath = repoStorePath; + rootDirectory = initRootGitDirectory(repoStorePath); + this.maxFileSize = maxFileSize; + this.fsSizer = fsSizer; + } + + @Override + public String getRepoStorePath() { + return repoStorePath; + } + + @Override + public File getRootDirectory() { + return rootDirectory; + } + + @Override + public ProjectRepo initRepo(String project) throws IOException { + GitProjectRepo ret = GitProjectRepo.fromName(project); + ret.initRepo(this); + return new WalkOverrideGitRepo( + ret, Optional.of(maxFileSize), Optional.empty()); + } + + @Override + public ProjectRepo initRepoFromExisting( + String project, String fromProject + ) throws IOException { + String repoRoot = getRepoStorePath(); + String sourcePath = repoRoot + "/" + fromProject; + String destinationPath = repoRoot + "/" + project; + Log.info("[{}] Init repo by copying data from: {}, to: {}", + project, + sourcePath, + destinationPath + ); + File source = new File(sourcePath); + File destination = new File(destinationPath); + FileUtils.copyDirectory(source, destination); + GitProjectRepo ret = GitProjectRepo.fromName(project); + ret.useExistingRepository(this); + return new WalkOverrideGitRepo( + ret, Optional.of(maxFileSize), Optional.empty()); + } + + @Override + public ProjectRepo getExistingRepo(String project) throws IOException { + GitProjectRepo ret = GitProjectRepo.fromName(project); + ret.useExistingRepository(this); + return new WalkOverrideGitRepo( + ret, Optional.of(maxFileSize), Optional.empty()); + } + + @Override + public ProjectRepo useJGitRepo(Repository repo, ObjectId commitId) { + GitProjectRepo ret = GitProjectRepo.fromJGitRepo(repo); + return new WalkOverrideGitRepo( + ret, Optional.of(maxFileSize), Optional.of(commitId)); + } + + /* TODO: Perhaps we should just delete bad directories on the fly. */ + @Override + public void purgeNonexistentProjects( + Collection existingProjectNames + ) { + List excludedFromDeletion = + new ArrayList<>(existingProjectNames); + excludedFromDeletion.add(".wlgb"); + deleteInDirectoryApartFrom( + rootDirectory, + excludedFromDeletion.toArray(new String[] {}) + ); + } + + @Override + public long totalSize() { + return fsSizer.apply(rootDirectory); + } + + @Override + public InputStream bzip2Project( + String projectName, + long[] sizePtr + ) throws IOException { + Project.checkValidProjectName(projectName); + Log.info("[{}] bzip2 project", projectName); + return Tar.bz2.zip(getDotGitForProject(projectName), sizePtr); + } + + @Override + public InputStream gzipProject( + String projectName, + long[] sizePtr + ) throws IOException { + Project.checkValidProjectName(projectName); + Log.info("[{}] gzip project", projectName); + return Tar.gzip.zip(getDotGitForProject(projectName), sizePtr); + } + + @Override + public void gcProject(String projectName) throws IOException { + Project.checkValidProjectName(projectName); + ProjectRepo repo = getExistingRepo(projectName); + repo.runGC(); + } + + @Override + public void remove(String projectName) throws IOException { + Project.checkValidProjectName(projectName); + FileUtils.deleteDirectory(new File(rootDirectory, projectName)); + } + + @Override + public void unbzip2Project( + String projectName, + InputStream dataStream + ) throws IOException { + Preconditions.checkArgument( + Project.isValidProjectName(projectName), + "[%s] invalid project name: ", + projectName + ); + Preconditions.checkState( + getDirForProject(projectName).mkdirs(), + "[%s] directories for " + + "evicted project already exist", + projectName + ); + Log.info("[{}] un-bzip2 project", projectName); + Tar.bz2.unzip(dataStream, getDirForProject(projectName)); + } + + @Override + public void ungzipProject( + String projectName, + InputStream dataStream + ) throws IOException { + Preconditions.checkArgument( + Project.isValidProjectName(projectName), + "[%s] invalid project name: ", + projectName + ); + Preconditions.checkState( + getDirForProject(projectName).mkdirs(), + "[%s] directories for " + + "evicted project already exist", + projectName + ); + Log.info("[{}] un-gzip project", projectName); + Tar.gzip.unzip(dataStream, getDirForProject(projectName)); + } + + private File getDirForProject(String projectName) { + Project.checkValidProjectName(projectName); + return Paths.get( + rootDirectory.getAbsolutePath() + ).resolve( + projectName + ).toFile(); + } + + private File getDotGitForProject(String projectName) { + Project.checkValidProjectName(projectName); + return Paths.get( + rootDirectory.getAbsolutePath() + ).resolve( + projectName + ).resolve( + ".git" + ).toFile(); + } + + private File initRootGitDirectory(String rootGitDirectoryPath) { + File rootGitDirectory = new File(rootGitDirectoryPath); + rootGitDirectory.mkdirs(); + Preconditions.checkArgument( + rootGitDirectory.isDirectory(), + "given root git directory " + + "is not a directory: %s", + rootGitDirectory.getAbsolutePath() + ); + return rootGitDirectory; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepo.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepo.java new file mode 100644 index 0000000000..5c7ac2cb07 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepo.java @@ -0,0 +1,280 @@ +package uk.ac.ic.wlgitbridge.bridge.repo; + +import com.google.common.base.Preconditions; +import org.apache.commons.io.IOUtils; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ResetCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents; +import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; +import uk.ac.ic.wlgitbridge.git.util.RepositoryObjectTreeWalker; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.Project; +import uk.ac.ic.wlgitbridge.util.Util; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; + +/** + * Class representing a Git repository. + * + * It stores the projectName and repo separately because the hooks need to be + * able to construct one of these without knowing whether the repo exists yet. + * + * It can then be passed to the Bridge, which will either + * {@link #initRepo(RepoStore)} for a never-seen-before repo, or + * {@link #useExistingRepository(RepoStore)} for an existing repo. + * + * Make sure to acquire the project lock before calling methods here. + */ +public class GitProjectRepo implements ProjectRepo { + + private final String projectName; + private Optional repository; + + public static GitProjectRepo fromJGitRepo(Repository repo) { + return new GitProjectRepo( + repo.getWorkTree().getName(), Optional.of(repo)); + } + + public static GitProjectRepo fromName(String projectName) { + return new GitProjectRepo(projectName, Optional.empty()); + } + + GitProjectRepo(String projectName, Optional repository) { + Preconditions.checkArgument(Project.isValidProjectName(projectName)); + this.projectName = projectName; + this.repository = repository; + } + + @Override + public String getProjectName() { + return projectName; + } + + @Override + public void initRepo( + RepoStore repoStore + ) throws IOException { + initRepositoryField(repoStore); + Preconditions.checkState(repository.isPresent()); + Repository repo = this.repository.get(); + // TODO: assert that this is a fresh repo. At the moment, we can't be + // sure whether the repo to be init'd doesn't exist or is just fresh + // and we crashed / aborted while committing + if (repo.getObjectDatabase().exists()) return; + repo.create(); + } + + @Override + public void useExistingRepository( + RepoStore repoStore + ) throws IOException { + initRepositoryField(repoStore); + Preconditions.checkState(repository.isPresent()); + Preconditions.checkState( + repository.get().getObjectDatabase().exists() + ); + } + + @Override + public RawDirectory getDirectory() + throws IOException, GitUserException { + Preconditions.checkState(repository.isPresent()); + return new RepositoryObjectTreeWalker( + repository.get() + ).getDirectoryContents(Optional.empty()); + } + + @Override + public Collection commitAndGetMissing( + GitDirectoryContents contents + ) throws IOException { + try { + return doCommitAndGetMissing(contents); + } catch (GitAPIException e) { + throw new IOException(e); + } + } + + @Override + public void runGC() throws IOException { + Preconditions.checkState( + repository.isPresent(), + "Repo is not present" + ); + File dir = getProjectDir(); + Preconditions.checkState(dir.isDirectory()); + Log.info("[{}] Running git gc", projectName); + Process proc = new ProcessBuilder( + "git", "gc" + ).directory(dir).start(); + int exitCode; + try { + exitCode = proc.waitFor(); + Log.info("Exit: {}", exitCode); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (exitCode != 0) { + Log.warn("[{}] Git gc failed", dir.getAbsolutePath()); + Log.warn(IOUtils.toString( + proc.getInputStream(), + StandardCharsets.UTF_8 + )); + Log.warn(IOUtils.toString( + proc.getErrorStream(), + StandardCharsets.UTF_8 + )); + throw new IOException("git gc error"); + } + Log.info("[{}] git gc successful", projectName); + } + + @Override + public void deleteIncomingPacks() throws IOException { + Log.info( + "[{}] Checking for garbage `incoming` files", + projectName + ); + Files.walkFileTree(getDotGitDir().toPath(), new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory( + Path dir, + BasicFileAttributes attrs + ) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile( + Path file, + BasicFileAttributes attrs + ) throws IOException { + File file_ = file.toFile(); + String name = file_.getName(); + if (name.startsWith("incoming_") && name.endsWith(".pack")) { + Log.info("Deleting garbage `incoming` file: {}", file_); + Preconditions.checkState(file_.delete()); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed( + Path file, + IOException exc + ) throws IOException { + Preconditions.checkNotNull(file); + Preconditions.checkNotNull(exc); + Log.warn("Failed to visit file: " + file, exc); + return FileVisitResult.TERMINATE; + } + + @Override + public FileVisitResult postVisitDirectory( + Path dir, + IOException exc + ) throws IOException { + Preconditions.checkNotNull(dir); + if (exc != null) { + return FileVisitResult.TERMINATE; + } + return FileVisitResult.CONTINUE; + } + + }); + } + + @Override + public File getProjectDir() { + return getJGitRepository().getDirectory().getParentFile(); + } + + public void resetHard() throws IOException { + Git git = new Git(getJGitRepository()); + try { + git.reset().setMode(ResetCommand.ResetType.HARD).call(); + } catch (GitAPIException e) { + throw new IOException(e); + } + } + + @Override + public Repository getJGitRepository() { + return repository.get(); + } + + public File getDotGitDir() { + return getJGitRepository().getWorkTree(); + } + + private void initRepositoryField(RepoStore repoStore) throws IOException { + Preconditions.checkNotNull(repoStore); + Preconditions.checkArgument(Project.isValidProjectName(projectName)); + Preconditions.checkState(!repository.isPresent()); + repository = Optional.of(createJGitRepository(repoStore, projectName)); + } + + private Repository createJGitRepository( + RepoStore repoStore, + String projName + ) throws IOException { + File repoDir = new File(repoStore.getRootDirectory(), projName); + return new FileRepositoryBuilder().setWorkTree(repoDir).build(); + } + + private Collection doCommitAndGetMissing( + GitDirectoryContents contents + ) throws IOException, GitAPIException { + Preconditions.checkState(repository.isPresent()); + Repository repo = getJGitRepository(); + resetHard(); + String name = getProjectName(); + Log.info("[{}] Writing commit", name); + contents.write(); + Git git = new Git(getJGitRepository()); + Log.info("[{}] Getting missing files", name); + Set missingFiles = git.status().call().getMissing(); + for (String missing : missingFiles) { + Log.info("[{}] Git rm {}", name, missing); + git.rm().setCached(true).addFilepattern(missing).call(); + } + Log.info("[{}] Calling Git add", name); + git.add( + ).setWorkingTreeIterator( + new NoGitignoreIterator(repo) + ).addFilepattern(".").call(); + Log.info("[{}] Calling Git commit", name); + git.commit( + ).setAuthor( + new PersonIdent( + contents.getUserName(), + contents.getUserEmail(), + contents.getWhen(), + TimeZone.getDefault() + ) + ).setMessage( + contents.getCommitMessage() + ).call(); + Log.info( + "[{}] Deleting files in directory: {}", + name, + contents.getDirectory().getAbsolutePath() + ); + Util.deleteInDirectoryApartFrom(contents.getDirectory(), ".git"); + return missingFiles; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/NoGitignoreIterator.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/NoGitignoreIterator.java new file mode 100644 index 0000000000..64103c075e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/NoGitignoreIterator.java @@ -0,0 +1,87 @@ +package uk.ac.ic.wlgitbridge.bridge.repo; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.WorkingTreeIterator; +import org.eclipse.jgit.treewalk.WorkingTreeOptions; +import org.eclipse.jgit.util.FS; + +import java.io.File; +import java.lang.reflect.Field; + +/** + * Created by winston on 08/10/2016. + */ +public class NoGitignoreIterator extends FileTreeIterator { + + private static final Field ignoreNodeField; + + static { + try { + ignoreNodeField = WorkingTreeIterator.class.getDeclaredField( + "ignoreNode" + ); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + ignoreNodeField.setAccessible(true); + } + + public NoGitignoreIterator(Repository repo) { + super(repo); + } + + public NoGitignoreIterator( + Repository repo, + FileModeStrategy fileModeStrategy + ) { + super(repo, fileModeStrategy); + } + + public NoGitignoreIterator(File root, FS fs, WorkingTreeOptions options) { + super(root, fs, options); + } + + public NoGitignoreIterator( + File root, + FS fs, + WorkingTreeOptions options, + FileModeStrategy fileModeStrategy + ) { + super(root, fs, options, fileModeStrategy); + } + + protected NoGitignoreIterator(FileTreeIterator p, File root, FS fs) { + super(p, root, fs); + } + + protected NoGitignoreIterator( + WorkingTreeIterator p, + File root, + FS fs, + FileModeStrategy fileModeStrategy + ) { + super(p, root, fs, fileModeStrategy); + } + + // Note: the `list` is a list of top-level entities in this directory, + // not a full list of files in the tree. + @Override + protected void init(Entry[] list) { + super.init(list); + try { + ignoreNodeField.set(this, null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + // When entering a sub-directory, create a new instance of this class, + // so we can also ignore gitignore specifications in sub-directories + @Override + protected AbstractTreeIterator enterSubtree() { + String fullPath = getDirectory().getAbsolutePath() + "/" + current().getName(); + return new NoGitignoreIterator(this, new File(fullPath), fs, fileModeStrategy); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/ProjectRepo.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/ProjectRepo.java new file mode 100644 index 0000000000..1e77d45010 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/ProjectRepo.java @@ -0,0 +1,41 @@ +package uk.ac.ic.wlgitbridge.bridge.repo; + +import org.eclipse.jgit.lib.Repository; +import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents; +import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +/** + * Created by winston on 20/08/2016. + */ +public interface ProjectRepo { + + String getProjectName(); + + void initRepo( + RepoStore repoStore + ) throws IOException; + + void useExistingRepository( + RepoStore repoStore + ) throws IOException; + + RawDirectory getDirectory( + ) throws IOException, GitUserException; + + Collection commitAndGetMissing( + GitDirectoryContents gitDirectoryContents + ) throws IOException, GitUserException; + + void runGC() throws IOException; + + void deleteIncomingPacks() throws IOException; + + File getProjectDir(); + + Repository getJGitRepository(); +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/RepoStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/RepoStore.java new file mode 100644 index 0000000000..9aba00f477 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/RepoStore.java @@ -0,0 +1,104 @@ +package uk.ac.ic.wlgitbridge.bridge.repo; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +/** + * Created by winston on 20/08/2016. + */ +public interface RepoStore { + + /* Still need to get rid of these two methods. + Main dependency: GitRepoStore needs a Repository which needs a directory. + Instead, use a visitor or something. */ + String getRepoStorePath(); + + File getRootDirectory(); + + ProjectRepo initRepo(String project) throws IOException; + + ProjectRepo initRepoFromExisting(String project, String fromProject) throws IOException; + + ProjectRepo getExistingRepo(String project) throws IOException; + + ProjectRepo useJGitRepo(Repository repo, ObjectId commitId); + + void purgeNonexistentProjects( + Collection existingProjectNames + ); + + long totalSize(); + + /** + * Tars and bzip2s the .git directory of the given project. Throws an + * IOException if the project doesn't exist. The returned stream is a copy + * of the original .git directory, which must be deleted using remove(). + */ + InputStream bzip2Project( + String projectName, + long[] sizePtr + ) throws IOException; + + default InputStream bzip2Project( + String projectName + ) throws IOException { + return bzip2Project(projectName, null); + } + + /** + * Tars and gzips the .git directory of the given project. Throws an + * IOException if the project doesn't exist. The returned stream is a copy + * of the original .git directory, which must be deleted using remove(). + */ + InputStream gzipProject( + String projectName, + long[] sizePtr + ) throws IOException; + + default InputStream gzipProject( + String projectName + ) throws IOException { + return gzipProject(projectName, null); + } + + void gcProject(String projectName) throws IOException; + + /** + * Called after {@link #bzip2Project(String, long[])}'s has been safely + * uploaded to the swap store. Removes all traces of the project from disk, + * i.e. not just its .git, but the whole project's git directory. + * @param projectName + * @throws IOException + */ + void remove(String projectName) throws IOException; + + /** + * Unbzip2s the given data stream into a .git directory for projectName. + * Creates the project's git directory. + * If projectName already exists, throws an IOException. + * @param projectName the name of the project, e.g. abc123 + * @param dataStream the data stream containing the bzipped contents. + */ + void unbzip2Project( + String projectName, + InputStream dataStream + ) throws IOException; + + /** + * Ungzips the given data stream into a .git directory for projectName. + * Creates the project's git directory. + * If projectName already exists, throws an IOException. + * @param projectName the name of the project, e.g. abc123 + * @param dataStream the data stream containing the gzip contents. + */ + void ungzipProject( + String projectName, + InputStream dataStream + ) throws IOException; + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/RepoStoreConfig.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/RepoStoreConfig.java new file mode 100644 index 0000000000..52d41104a1 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/RepoStoreConfig.java @@ -0,0 +1,29 @@ +package uk.ac.ic.wlgitbridge.bridge.repo; + +import javax.annotation.Nullable; +import java.util.Optional; + +/** + * Created by winston on 02/07/2017. + */ +public class RepoStoreConfig { + + @Nullable + private final Long maxFileSize; + + @Nullable + private final Long maxFileNum; + + public RepoStoreConfig(Long maxFileSize, Long maxFileNum) { + this.maxFileSize = maxFileSize; + this.maxFileNum = maxFileNum; + } + + public Optional getMaxFileSize() { + return Optional.ofNullable(maxFileSize); + } + + public Optional getMaxFileNum() { + return Optional.ofNullable(maxFileNum); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/WalkOverrideGitRepo.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/WalkOverrideGitRepo.java new file mode 100644 index 0000000000..c240001c7c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/repo/WalkOverrideGitRepo.java @@ -0,0 +1,95 @@ +package uk.ac.ic.wlgitbridge.bridge.repo; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents; +import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; +import uk.ac.ic.wlgitbridge.git.util.RepositoryObjectTreeWalker; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Optional; + +/** + * This class takes a GitProjectRepo and delegates all calls to it. + * + * The purpose is to insert a file size check in {@link #getDirectory()}. + * + * We delegate instead of subclass because we can't override the static + * constructors in {@link GitProjectRepo}. + */ +public class WalkOverrideGitRepo implements ProjectRepo { + + private final GitProjectRepo gitRepo; + + private final Optional maxFileSize; + + private final Optional commitId; + + public WalkOverrideGitRepo( + GitProjectRepo gitRepo, + Optional maxFileSize, + Optional commitId + ) { + this.gitRepo = gitRepo; + this.maxFileSize = maxFileSize; + this.commitId = commitId; + } + + @Override + public String getProjectName() { + return gitRepo.getProjectName(); + } + + @Override + public void initRepo(RepoStore repoStore) throws IOException { + gitRepo.initRepo(repoStore); + } + + @Override + public void useExistingRepository(RepoStore repoStore) throws IOException { + gitRepo.useExistingRepository(repoStore); + } + + @Override + public RawDirectory getDirectory() throws IOException, GitUserException { + Repository repo = gitRepo.getJGitRepository(); + RepositoryObjectTreeWalker walker; + if (commitId.isPresent()) { + walker = new RepositoryObjectTreeWalker(repo, commitId.get()); + } else { + walker = new RepositoryObjectTreeWalker(repo); + } + return walker.getDirectoryContents(maxFileSize); + } + + @Override + public Collection commitAndGetMissing( + GitDirectoryContents gitDirectoryContents + ) throws GitUserException, IOException { + return gitRepo.commitAndGetMissing(gitDirectoryContents); + } + + @Override + public void runGC() throws IOException { + gitRepo.runGC(); + } + + @Override + public void deleteIncomingPacks() throws IOException { + gitRepo.deleteIncomingPacks(); + } + + @Override + public File getProjectDir() { + return gitRepo.getProjectDir(); + } + + @Override + public Repository getJGitRepository() { + return gitRepo.getJGitRepository(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/resource/ResourceCache.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/resource/ResourceCache.java new file mode 100644 index 0000000000..7a520bb3f8 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/resource/ResourceCache.java @@ -0,0 +1,24 @@ +package uk.ac.ic.wlgitbridge.bridge.resource; + +import uk.ac.ic.wlgitbridge.data.filestore.RawFile; +import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; + +/** + * Created by winston on 20/08/2016. + */ +public interface ResourceCache { + + RawFile get( + String projectName, + String url, + String newPath, + Map fileTable, + Map fetchedUrls, + Optional maxFileSize + ) throws IOException, SizeLimitExceededException; + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/resource/UrlResourceCache.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/resource/UrlResourceCache.java new file mode 100644 index 0000000000..29a0cbea57 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/resource/UrlResourceCache.java @@ -0,0 +1,142 @@ +package uk.ac.ic.wlgitbridge.bridge.resource; + +import static org.asynchttpclient.Dsl.*; +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.data.filestore.RawFile; +import uk.ac.ic.wlgitbridge.data.filestore.RepositoryFile; +import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException; +import uk.ac.ic.wlgitbridge.io.http.ning.NingHttpClient; +import uk.ac.ic.wlgitbridge.io.http.ning.NingHttpClientFacade; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.util.Log; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +/** + * Created by winston on 20/08/2016. + */ +public class UrlResourceCache implements ResourceCache { + + private final DBStore dbStore; + + private final NingHttpClientFacade http; + + UrlResourceCache(DBStore dbStore, NingHttpClientFacade http) { + this.dbStore = dbStore; + this.http = http; + } + + public UrlResourceCache(DBStore dbStore) { + this(dbStore, new NingHttpClient(asyncHttpClient())); + } + + @Override + public RawFile get( + String projectName, + String url, + String newPath, + Map fileTable, + Map fetchedUrls, + Optional maxFileSize + ) throws IOException, SizeLimitExceededException { + String path = dbStore.getPathForURLInProject(projectName, getCacheKeyFromUrl(url)); + byte[] contents; + if (path == null) { + path = newPath; + contents = fetch(projectName, url, path, maxFileSize); + fetchedUrls.put(url, contents); + } else { + Log.info("Found (" + projectName + "): " + url); + Log.info("At (" + projectName + "): " + path); + contents = fetchedUrls.get(url); + if (contents == null) { + RawFile rawFile = fileTable.get(path); + if (rawFile == null) { + Log.warn( + "File " + path + + " was not in the current commit, " + + "or the git tree, yet path was not null. " + + "File url is: " + + url + ); + contents = fetch(projectName, url, path, maxFileSize); + } else { + contents = rawFile.getContents(); + } + } + } + return new RepositoryFile(newPath, contents); + } + + private byte[] fetch( + String projectName, + final String url, + String path, + Optional maxFileSize + ) throws FailedConnectionException, SizeLimitExceededException { + byte[] contents; + Log.info("GET -> " + url); + try { + contents = http.get(url, hs -> { + List contentLengths = hs.getAll("Content-Length"); + if (!maxFileSize.isPresent()) { + return true; + } + if (contentLengths.isEmpty()) { + return true; + } + long contentLength = Long.parseLong(contentLengths.get(0)); + long maxFileSize_ = maxFileSize.get(); + if (contentLength <= maxFileSize_) { + return true; + } + throw new SizeLimitExceededException( + Optional.of(path), contentLength, maxFileSize_ + ); + }); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof SizeLimitExceededException) { + throw (SizeLimitExceededException) cause; + } + Log.warn( + "ExecutionException when fetching project: " + + projectName + + ", url: " + + url + + ", path: " + + path, + e + ); + throw new FailedConnectionException(); + } + if (maxFileSize.isPresent() && contents.length > maxFileSize.get()) { + throw new SizeLimitExceededException( + Optional.of(path), contents.length, maxFileSize.get()); + } + dbStore.addURLIndexForProject(projectName, getCacheKeyFromUrl(url), path); + return contents; + } + + /** + * Construct a suitable cache key from the given file URL. + * + * The file URL returned by the web service may contain a token parameter + * used for authentication. This token changes for every request, so we + * need to strip it from the query string before using the URL as a cache + * key. + */ + private String getCacheKeyFromUrl(String url) { + // We're not doing proper URL parsing here, but it should be enough to + // remove the token without touching the important parts of the URL. + // + // The URL looks like: + // + // https://history.overleaf.com/api/projects/:project_id/blobs/:hash?token=:token&_path=:path + return url.replaceAll("token=[^&]*", "token=REMOVED"); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/snapshot/NetSnapshotApi.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/snapshot/NetSnapshotApi.java new file mode 100644 index 0000000000..c289e44c30 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/snapshot/NetSnapshotApi.java @@ -0,0 +1,55 @@ +package uk.ac.ic.wlgitbridge.bridge.snapshot; + +import com.google.api.client.auth.oauth2.Credential; +import uk.ac.ic.wlgitbridge.data.CandidateSnapshot; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocRequest; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.GetForVersionRequest; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.GetForVersionResult; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.GetSavedVersRequest; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.GetSavedVersResult; +import uk.ac.ic.wlgitbridge.snapshot.push.PushRequest; +import uk.ac.ic.wlgitbridge.snapshot.push.PushResult; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Created by winston on 20/08/2016. + */ +public class NetSnapshotApi implements SnapshotApi { + + @Override + public CompletableFuture getDoc( + Optional oauth2, String projectName) { + return new GetDocRequest(opt(oauth2), projectName).request(); + } + + @Override + public CompletableFuture getForVersion( + Optional oauth2, String projectName, int versionId) { + return new GetForVersionRequest( + opt(oauth2), projectName, versionId).request(); + } + + @Override + public CompletableFuture getSavedVers( + Optional oauth2, String projectName) { + return new GetSavedVersRequest(opt(oauth2), projectName).request(); + } + + @Override + public CompletableFuture push( + Optional oauth2, + CandidateSnapshot candidateSnapshot, + String postbackKey + ) { + return new PushRequest( + opt(oauth2), candidateSnapshot, postbackKey).request(); + } + + private static Credential opt(Optional oauth2) { + return oauth2.orElse(null); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/snapshot/SnapshotApi.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/snapshot/SnapshotApi.java new file mode 100644 index 0000000000..0358db7834 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/snapshot/SnapshotApi.java @@ -0,0 +1,54 @@ +package uk.ac.ic.wlgitbridge.bridge.snapshot; + +import com.google.api.client.auth.oauth2.Credential; +import uk.ac.ic.wlgitbridge.data.CandidateSnapshot; +import uk.ac.ic.wlgitbridge.snapshot.base.MissingRepositoryException; +import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.GetForVersionResult; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.GetSavedVersResult; +import uk.ac.ic.wlgitbridge.snapshot.push.PushResult; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * Created by winston on 20/08/2016. + */ +public interface SnapshotApi { + + CompletableFuture getDoc( + Optional oauth2, String projectName); + + CompletableFuture getForVersion( + Optional oauth2, String projectName, int versionId); + + CompletableFuture getSavedVers( + Optional oauth2, String projectName); + + CompletableFuture push( + Optional oauth2, + CandidateSnapshot candidateSnapshot, + String postbackKey); + + static T getResult(CompletableFuture result) + throws MissingRepositoryException, FailedConnectionException, ForbiddenException { + try { + return result.join(); + } catch (CompletionException e) { + try { + throw e.getCause(); + } catch (MissingRepositoryException + | FailedConnectionException + | ForbiddenException + | RuntimeException r) { + throw r; + } catch (Throwable __) { + throw e; + } + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/snapshot/SnapshotApiFacade.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/snapshot/SnapshotApiFacade.java new file mode 100644 index 0000000000..9aa3165e06 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/snapshot/SnapshotApiFacade.java @@ -0,0 +1,162 @@ +package uk.ac.ic.wlgitbridge.bridge.snapshot; + +import com.google.api.client.auth.oauth2.Credential; +import uk.ac.ic.wlgitbridge.data.CandidateSnapshot; +import uk.ac.ic.wlgitbridge.data.model.Snapshot; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; +import uk.ac.ic.wlgitbridge.snapshot.base.MissingRepositoryException; +import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.GetForVersionResult; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotData; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.GetSavedVersResult; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.SnapshotInfo; +import uk.ac.ic.wlgitbridge.snapshot.push.PushResult; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.InvalidProjectException; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +/** + * Created by winston on 02/07/2017. + */ +public class SnapshotApiFacade { + + private final SnapshotApi api; + + public SnapshotApiFacade(SnapshotApi api) { + this.api = api; + } + + public boolean projectExists( + Optional oauth2, + String projectName + ) throws FailedConnectionException, GitUserException { + try { + SnapshotApi + .getResult(api.getDoc(oauth2, projectName)) + .getVersionID(); + return true; + } catch (InvalidProjectException e) { + return false; + } + } + + public Optional getDoc( + Optional oauth2, + String projectName + ) throws FailedConnectionException, GitUserException { + try { + GetDocResult doc = SnapshotApi + .getResult(api.getDoc(oauth2, projectName)); + doc.getVersionID(); + return Optional.of(doc); + } catch (InvalidProjectException e) { + return Optional.empty(); + } + } + + public Deque getSnapshots( + Optional oauth2, + String projectName, + int afterVersionId + ) throws GitUserException, FailedConnectionException { + List snapshotInfos = getSnapshotInfosAfterVersion( + oauth2, + projectName, + afterVersionId + ); + List snapshotDatas = getMatchingSnapshotData( + oauth2, + projectName, + snapshotInfos + ); + return combine(snapshotInfos, snapshotDatas); + } + + public PushResult push( + Optional oauth2, + CandidateSnapshot candidateSnapshot, + String postbackKey + ) throws MissingRepositoryException, FailedConnectionException, ForbiddenException { + return SnapshotApi.getResult(api.push( + oauth2, candidateSnapshot, postbackKey)); + } + + private List getSnapshotInfosAfterVersion( + Optional oauth2, + String projectName, + int version + ) throws FailedConnectionException, GitUserException { + SortedSet versions = new TreeSet<>(); + CompletableFuture getDoc + = api.getDoc(oauth2, projectName); + CompletableFuture savedVers + = api.getSavedVers(oauth2, projectName); + GetDocResult latestDoc = SnapshotApi.getResult(getDoc); + int latest = latestDoc.getVersionID(); + // Handle edge-case for projects with no changes, that were imported + // to v2. In which case both `latest` and `version` will be zero. + // See: https://github.com/overleaf/writelatex-git-bridge/pull/50 + if (latest > version || (latest == 0 && version == 0)) { + for ( + SnapshotInfo snapshotInfo : + SnapshotApi.getResult(savedVers).getSavedVers() + ) { + if (snapshotInfo.getVersionId() > version) { + versions.add(snapshotInfo); + } + } + versions.add(new SnapshotInfo( + latest, + latestDoc.getCreatedAt(), + latestDoc.getName(), + latestDoc.getEmail() + )); + + } + return new ArrayList<>(versions); + } + + private List getMatchingSnapshotData( + Optional oauth2, + String projectName, + List snapshotInfos + ) throws FailedConnectionException, ForbiddenException { + List> firedRequests + = fireDataRequests(oauth2, projectName, snapshotInfos); + List snapshotDataList = new ArrayList<>(); + for (CompletableFuture fired : firedRequests) { + snapshotDataList.add(fired.join().getSnapshotData()); + } + return snapshotDataList; + } + + private List> fireDataRequests( + Optional oauth2, + String projectName, + List snapshotInfos + ) { + return snapshotInfos + .stream() + .map(snap -> api.getForVersion( + oauth2, projectName, snap.getVersionId())) + .collect(Collectors.toList()); + } + + private static Deque combine( + List snapshotInfos, + List snapshotDatas + ) { + Deque snapshots = new LinkedList<>(); + Iterator infos = snapshotInfos.iterator(); + Iterator datas = snapshotDatas.iterator(); + while (infos.hasNext()) { + snapshots.add(new Snapshot(infos.next(), datas.next())); + } + return snapshots; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/NoopSwapJob.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/NoopSwapJob.java new file mode 100644 index 0000000000..49ebf0c58b --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/NoopSwapJob.java @@ -0,0 +1,20 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.job; + +/** + * Created by winston on 24/08/2016. + */ +public class NoopSwapJob implements SwapJob { + + @Override + public void start() {} + + @Override + public void stop() {} + + @Override + public void evict(String projName) {} + + @Override + public void restore(String projName) {} + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJob.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJob.java new file mode 100644 index 0000000000..8ea19f7d0e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJob.java @@ -0,0 +1,124 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.job; + +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.lock.ProjectLock; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.bridge.swap.store.SwapStore; + +import java.io.IOException; +import java.util.Optional; + +/** + * Created by winston on 20/08/2016. + */ +public interface SwapJob { + + enum CompressionMethod { Bzip2, Gzip } + + static CompressionMethod stringToCompressionMethod(String compressionString) { + if (compressionString == null) { + return null; + } + CompressionMethod result; + switch (compressionString) { + case "gzip": + result = CompressionMethod.Gzip; + break; + case "bzip2": + result = CompressionMethod.Bzip2; + break; + default: + result = null; + break; + } + return result; + } + + static String compressionMethodAsString(CompressionMethod compressionMethod) { + if (compressionMethod == null) { + return null; + } + String result; + switch (compressionMethod) { + case Gzip: + result = "gzip"; + break; + case Bzip2: + result = "bzip2"; + break; + default: + result = null; + break; + } + return result; + } + + static SwapJob fromConfig( + Optional cfg, + ProjectLock lock, + RepoStore repoStore, + DBStore dbStore, + SwapStore swapStore + ) { + if (cfg.isPresent()) { + return new SwapJobImpl( + cfg.get(), + lock, + repoStore, + dbStore, + swapStore + ); + } + return new NoopSwapJob(); + } + + /** + * Starts the swap job, which should schedule an attempted swap at the given + * configured interval (config["swapJob"]["intervalMillis"] + */ + void start(); + + /** + * Stops the stop job. + */ + void stop(); + + /** + * Called by the swap job when a project should be evicted. + * + * Pre: + * 1. projName must be in repoStore + * 2. projName should not be in swapStore + * 3. projName should be PRESENT in dbStore (last_accessed is not null) + * + * Acquires the project lock and performs an eviction of projName. + * + * Post: + * 1. projName should not in repoStore + * 2. projName must be in swapStore + * 3. projName must be SWAPPED in dbStore (last_accessed is null) + * @param projName + * @throws IOException + */ + void evict(String projName) throws IOException; + + /** + * Called on a project when it must be restored. + * + * Pre: + * 1. projName should not be in repoStore + * 2. projName must be in swapStore + * 3. projName must be SWAPPED in dbStore (last_accessed is null) + * + * Acquires the project lock and restores projName. + * + * Post: + * 1. projName must be in repoStore + * 2. projName should not in swapStore + * 3. projName should be PRESENT in dbStore (last_accessed is not null) + * @param projName + * @throws IOException + */ + void restore(String projName) throws IOException; + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJobConfig.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJobConfig.java new file mode 100644 index 0000000000..312696473d --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJobConfig.java @@ -0,0 +1,55 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.job; + +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.bridge.swap.job.SwapJob.CompressionMethod; + +/** + * Created by winston on 23/08/2016. + */ +public class SwapJobConfig { + + private final int minProjects; + private final int lowGiB; + private final int highGiB; + private final long intervalMillis; + private final String compressionMethod; + + public SwapJobConfig( + int minProjects, + int lowGiB, + int highGiB, + long intervalMillis, + String compressionMethod + ) { + this.minProjects = minProjects; + this.lowGiB = lowGiB; + this.highGiB = highGiB; + this.intervalMillis = intervalMillis; + this.compressionMethod = compressionMethod; + } + + public int getMinProjects() { + return minProjects; + } + + public int getLowGiB() { + return lowGiB; + } + + public int getHighGiB() { + return highGiB; + } + + public long getIntervalMillis() { + return intervalMillis; + } + + public SwapJob.CompressionMethod getCompressionMethod() { + CompressionMethod result = SwapJob.stringToCompressionMethod(compressionMethod); + if (result == null) { + Log.info("SwapJobConfig: un-supported compressionMethod '{}', default to 'bzip2'", compressionMethod); + result = CompressionMethod.Bzip2; + } + return result; + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJobImpl.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJobImpl.java new file mode 100644 index 0000000000..e6524de010 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJobImpl.java @@ -0,0 +1,262 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.job; + +import com.google.api.client.repackaged.com.google.common.base.Preconditions; +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.lock.LockGuard; +import uk.ac.ic.wlgitbridge.bridge.lock.ProjectLock; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.bridge.swap.store.SwapStore; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.TimerUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Timer; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by winston on 20/08/2016. + */ +public class SwapJobImpl implements SwapJob { + + private static final long GiB = (1l << 30); + + int minProjects; + long lowWatermarkBytes; + long highWatermarkBytes; + Duration interval; + + private final ProjectLock lock; + private final RepoStore repoStore; + private final DBStore dbStore; + private final SwapStore swapStore; + private final CompressionMethod compressionMethod; + + private final Timer timer; + + final AtomicInteger swaps; + + public SwapJobImpl( + SwapJobConfig cfg, + ProjectLock lock, + RepoStore repoStore, + DBStore dbStore, + SwapStore swapStore + ) { + this( + cfg.getMinProjects(), + GiB * cfg.getLowGiB(), + GiB * cfg.getHighGiB(), + Duration.ofMillis(cfg.getIntervalMillis()), + cfg.getCompressionMethod(), + lock, + repoStore, + dbStore, + swapStore + ); + } + + SwapJobImpl( + int minProjects, + long lowWatermarkBytes, + long highWatermarkBytes, + Duration interval, + CompressionMethod method, + ProjectLock lock, + RepoStore repoStore, + DBStore dbStore, + SwapStore swapStore + ) { + this.minProjects = minProjects; + this.lowWatermarkBytes = lowWatermarkBytes; + this.highWatermarkBytes = highWatermarkBytes; + this.interval = interval; + this.compressionMethod = method; + this.lock = lock; + this.repoStore = repoStore; + this.dbStore = dbStore; + this.swapStore = swapStore; + timer = new Timer(); + swaps = new AtomicInteger(0); + } + + @Override + public void start() { + timer.schedule( + TimerUtils.makeTimerTask(this::doSwap), + 0 + ); + } + + @Override + public void stop() { + timer.cancel(); + } + + private void doSwap() { + try { + doSwap_(); + } catch (Throwable t) { + Log.warn("Exception thrown during swap job", t); + } + timer.schedule( + TimerUtils.makeTimerTask(this::doSwap), + interval.toMillis() + ); + } + + private void doSwap_() { + ArrayList exceptionProjectNames = new ArrayList(); + + Log.info("Running swap number {}", swaps.get() + 1); + long totalSize = repoStore.totalSize(); + Log.info("Size is {}/{} (high)", totalSize, highWatermarkBytes); + if (totalSize < highWatermarkBytes) { + Log.info("No need to swap."); + swaps.incrementAndGet(); + return; + } + int numProjects = dbStore.getNumProjects(); + // while we have too many projects on disk + while ( + (totalSize = repoStore.totalSize()) > lowWatermarkBytes && + (numProjects = dbStore.getNumUnswappedProjects()) > minProjects + ) { + // check if we've had too many exceptions so far + if (exceptionProjectNames.size() >= 20) { + StringBuilder sb = new StringBuilder(); + for (String s: exceptionProjectNames) { + sb.append(s); + sb.append(' '); + } + Log.error( + "Too many exceptions while running swap, giving up on this run: {}", + sb.toString() + ); + break; + } + // get the oldest project and try to swap it + String projectName = dbStore.getOldestUnswappedProject(); + try { + evict(projectName); + } catch (Exception e) { + Log.warn("[{}] Exception while swapping, mark project and move on", projectName, e); + // NOTE: this is something of a hack. If a project fails to swap we get stuck in a + // loop where `dbStore.getOldestUnswappedProject()` gives the same failing project over and over again, + // which fills up the disk with errors. By touching the access time we can mark the project as a + // non-candidate for swapping. Ideally we should be checking the logs for these log events and fixing + // whatever is wrong with the project + dbStore.setLastAccessedTime( + projectName, + Timestamp.valueOf(LocalDateTime.now()) + ); + exceptionProjectNames.add(projectName); + } + } + if (totalSize > lowWatermarkBytes) { + Log.warn( + "Finished swapping, but total size is still too high." + ); + } + Log.info( + "Size: {}/{} (low), " + + "{} (high), " + + "projects on disk: {}/{}, " + + "min projects on disk: {}", + totalSize, + lowWatermarkBytes, + highWatermarkBytes, + numProjects, + dbStore.getNumProjects(), + minProjects + ); + swaps.incrementAndGet(); + } + + /** + * @see SwapJob#evict(String) for high-level description. + * + * 1. Acquires the project lock. + * 2. Gets a bz2 stream and size of a project from the repo store, or throws + * 3. Uploads the bz2 stream and size to the projName in the swapStore. + * 4. Sets the last accessed time in the dbStore to null, which makes our + * state SWAPPED + * 5. Removes the project from the repo store. + * @param projName + * @throws IOException + */ + @Override + public void evict(String projName) throws IOException { + Preconditions.checkNotNull(projName, "projName was null"); + Log.info("Evicting project: {}", projName); + try (LockGuard __ = lock.lockGuard(projName)) { + try { + repoStore.gcProject(projName); + } catch (Exception e) { + Log.error("[{}] Exception while running gc on project: {}", projName, e); + } + long[] sizePtr = new long[1]; + try (InputStream blob = getBlobStream(projName, sizePtr)) { + swapStore.upload(projName, blob, sizePtr[0]); + String compression = SwapJob.compressionMethodAsString(compressionMethod); + if (compression == null) { + throw new RuntimeException("invalid compression method, should not happen"); + } + dbStore.swap(projName, compression); + repoStore.remove(projName); + } + } + Log.info("Evicted project: {}", projName); + } + + private InputStream getBlobStream(String projName, long[] sizePtr) throws IOException { + if (compressionMethod == CompressionMethod.Gzip) { + return repoStore.gzipProject(projName, sizePtr); + } else if (compressionMethod == CompressionMethod.Bzip2) { + return repoStore.bzip2Project(projName, sizePtr); + } else { + throw new RuntimeException("invalid compression method, should not happen"); + } + } + + /** + * @see SwapJob#restore(String) for high-level description. + * + * 1. Acquires the project lock. + * 2. Gets a bz2 stream for the project from the swapStore. + * 3. Fully downloads and places the bz2 stream back in the repo store. + * 4. Sets the last accessed time in the dbStore to now, which makes our + * state PRESENT and the last project to be evicted. + * @param projName + * @throws IOException + */ + @Override + public void restore(String projName) throws IOException { + try (LockGuard __ = lock.lockGuard(projName)) { + try (InputStream zipped = swapStore.openDownloadStream(projName)) { + String compression = dbStore.getSwapCompression(projName); + if (compression == null) { + throw new RuntimeException("Missing compression method during restore, should not happen"); + } + if ("gzip".equals(compression)) { + repoStore.ungzipProject( + projName, + zipped + ); + } else if ("bzip2".equals(compression)) { + repoStore.unbzip2Project( + projName, + zipped + ); + } + swapStore.remove(projName); + dbStore.restore(projName); + } + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/InMemorySwapStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/InMemorySwapStore.java new file mode 100644 index 0000000000..a507e7e829 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/InMemorySwapStore.java @@ -0,0 +1,54 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.store; + +import org.apache.commons.io.IOUtils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by winston on 23/08/2016. + */ +public class InMemorySwapStore implements SwapStore { + + private final Map store; + + public InMemorySwapStore() { + store = new HashMap<>(); + } + + public InMemorySwapStore(SwapStoreConfig __) { + this(); + } + + @Override + public void upload( + String projectName, + InputStream uploadStream, + long contentLength + ) throws IOException { + store.put( + projectName, + IOUtils.toByteArray(uploadStream, contentLength) + ); + } + + @Override + public InputStream openDownloadStream(String projectName) { + byte[] buf = store.get(projectName); + if (buf == null) { + throw new IllegalArgumentException( + "no such project in swap store: " + projectName + ); + } + return new ByteArrayInputStream(buf); + } + + @Override + public void remove(String projectName) { + store.remove(projectName); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/NoopSwapStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/NoopSwapStore.java new file mode 100644 index 0000000000..0fec8a8cec --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/NoopSwapStore.java @@ -0,0 +1,28 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.store; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * Created by winston on 24/08/2016. + */ +public class NoopSwapStore implements SwapStore { + + public NoopSwapStore(SwapStoreConfig __) {} + + @Override + public void upload( + String projectName, + InputStream uploadStream, + long contentLength + ) {} + + @Override + public InputStream openDownloadStream(String projectName) { + return new ByteArrayInputStream(new byte[0]); + } + + @Override + public void remove(String projectName) {} + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/S3SwapStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/S3SwapStore.java new file mode 100644 index 0000000000..b439fb21b4 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/S3SwapStore.java @@ -0,0 +1,86 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.store; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.*; + +import java.io.InputStream; + +/** + * Created by winston on 21/08/2016. + */ +public class S3SwapStore implements SwapStore { + + private final AmazonS3 s3; + + private final String bucketName; + + public S3SwapStore(SwapStoreConfig cfg) { + this( + cfg.getAwsAccessKey(), + cfg.getAwsSecret(), + cfg.getS3BucketName(), + cfg.getAwsRegion() + ); + } + + S3SwapStore( + String accessKey, + String secret, + String bucketName, + String region + ) { + String regionToUse = null; + if (region == null) { + regionToUse = "us-east-1"; + } else { + regionToUse = region; + } + s3 = AmazonS3ClientBuilder + .standard() + .withRegion(regionToUse) + .withCredentials( + new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secret)) + ).build(); + this.bucketName = bucketName; + } + + @Override + public void upload( + String projectName, + InputStream uploadStream, + long contentLength + ) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(contentLength); + PutObjectRequest put = new PutObjectRequest( + bucketName, + projectName, + uploadStream, + metadata + ); + PutObjectResult res = s3.putObject(put); + } + + @Override + public InputStream openDownloadStream(String projectName) { + GetObjectRequest get = new GetObjectRequest( + bucketName, + projectName + ); + S3Object res = s3.getObject(get); + return res.getObjectContent(); + } + + @Override + public void remove(String projectName) { + DeleteObjectRequest del = new DeleteObjectRequest( + bucketName, + projectName + ); + s3.deleteObject(del); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/SwapStore.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/SwapStore.java new file mode 100644 index 0000000000..168e959313 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/SwapStore.java @@ -0,0 +1,44 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.store; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +/** + * Created by winston on 20/08/2016. + */ +public interface SwapStore { + + Map> swapStores = + new HashMap>() { + + { + put("noop", NoopSwapStore::new); + put("memory", InMemorySwapStore::new); + put("s3", S3SwapStore::new); + } + + }; + + static SwapStore fromConfig( + Optional cfg + ) { + SwapStoreConfig cfg_ = cfg.orElse(SwapStoreConfig.NOOP); + String type = cfg_.getType(); + return swapStores.get(type).apply(cfg_); + } + + void upload( + String projectName, + InputStream uploadStream, + long contentLength + ) throws IOException; + + InputStream openDownloadStream(String projectName); + + void remove(String projectName); + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/SwapStoreConfig.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/SwapStoreConfig.java new file mode 100644 index 0000000000..7559b240ec --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/swap/store/SwapStoreConfig.java @@ -0,0 +1,85 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.store; + +/** + * Created by winston on 24/08/2016. + */ +public class SwapStoreConfig { + + public static final SwapStoreConfig NOOP = new SwapStoreConfig( + "noop", + null, + null, + null, + null + ); + + private String type; + private String awsAccessKey; + private String awsSecret; + private String s3BucketName; + private String awsRegion; + + public SwapStoreConfig() {} + + public SwapStoreConfig( + String awsAccessKey, + String awsSecret, + String s3BucketName, + String awsRegion + ) { + this( + "s3", + awsAccessKey, + awsSecret, + s3BucketName, + awsRegion + ); + } + + SwapStoreConfig( + String type, + String awsAccessKey, + String awsSecret, + String s3BucketName, + String awsRegion + ) { + this.type = type; + this.awsAccessKey = awsAccessKey; + this.awsSecret = awsSecret; + this.s3BucketName = s3BucketName; + this.awsRegion = awsRegion; + } + + public String getType() { + return type; + } + + public String getAwsAccessKey() { + return awsAccessKey; + } + + public String getAwsSecret() { + return awsSecret; + } + + public String getS3BucketName() { + return s3BucketName; + } + + public String getAwsRegion() { return awsRegion; } + + public SwapStoreConfig sanitisedCopy() { + return new SwapStoreConfig( + type, + awsAccessKey == null ? null : "", + awsSecret == null ? null : "", + s3BucketName, + awsRegion + ); + } + + public static SwapStoreConfig sanitisedCopy(SwapStoreConfig swapStore) { + return swapStore == null ? null : swapStore.sanitisedCopy(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/util/CastUtil.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/util/CastUtil.java new file mode 100644 index 0000000000..8dd4238551 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/bridge/util/CastUtil.java @@ -0,0 +1,18 @@ +package uk.ac.ic.wlgitbridge.bridge.util; + +import com.google.common.base.Preconditions; + +/** + * Created by winston on 01/07/2017. + */ +public class CastUtil { + + public static int assumeInt(long l) { + Preconditions.checkArgument( + l <= (long) Integer.MAX_VALUE + && l >= (long) Integer.MIN_VALUE, + l + " cannot fit inside an int"); + return (int) l; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/CandidateSnapshot.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/CandidateSnapshot.java new file mode 100644 index 0000000000..4c3aef49f8 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/CandidateSnapshot.java @@ -0,0 +1,150 @@ +package uk.ac.ic.wlgitbridge.data; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.data.filestore.RawFile; +import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory; +import uk.ac.ic.wlgitbridge.util.Util; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Created by Winston on 16/11/14. + */ +public class CandidateSnapshot implements AutoCloseable { + + private final String projectName; + private final int currentVersion; + private final List files; + private final List deleted; + private File attsDirectory; + + public CandidateSnapshot( + String projectName, + int currentVersion, + RawDirectory directoryContents, + RawDirectory oldDirectoryContents + ) { + this.projectName = projectName; + this.currentVersion = currentVersion; + files = diff(directoryContents, oldDirectoryContents); + deleted = deleted(directoryContents, oldDirectoryContents); + } + + private List diff( + RawDirectory directoryContents, + RawDirectory oldDirectoryContents + ) { + List files = new LinkedList(); + Map fileTable = directoryContents.getFileTable(); + Map oldFileTable = oldDirectoryContents.getFileTable(); + for (Entry entry : fileTable.entrySet()) { + RawFile file = entry.getValue(); + files.add(new ServletFile(file, oldFileTable.get(file.getPath()))); + } + return files; + } + + private List deleted( + RawDirectory directoryContents, + RawDirectory oldDirectoryContents + ) { + List deleted = new LinkedList(); + Map fileTable = directoryContents.getFileTable(); + for ( + Entry entry : + oldDirectoryContents.getFileTable().entrySet() + ) { + String path = entry.getKey(); + RawFile newFile = fileTable.get(path); + if (newFile == null) { + deleted.add(path); + } + } + return deleted; + } + + public void writeServletFiles(File rootGitDirectory) throws IOException { + attsDirectory = new File( + rootGitDirectory, + ".wlgb/atts/" + projectName + ); + for (ServletFile file : files) { + if (file.isChanged()) { + file.writeToDiskWithName(attsDirectory, file.getUniqueIdentifier()); + } + } + } + + public void deleteServletFiles() throws IOException { + if (attsDirectory != null) { + Util.deleteDirectory(attsDirectory); + } + } + + public JsonElement getJsonRepresentation(String postbackKey) { + String projectURL = Util.getPostbackURL() + "api/" + projectName; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("latestVerId", currentVersion); + jsonObject.add("files", getFilesAsJson(projectURL, postbackKey)); + jsonObject.addProperty( + "postbackUrl", projectURL + "/" + postbackKey + "/postback" + ); + return jsonObject; + } + + private JsonArray getFilesAsJson(String projectURL, String postbackKey) { + JsonArray filesArray = new JsonArray(); + for (ServletFile file : files) { + filesArray.add(getFileAsJson(file, projectURL, postbackKey)); + } + return filesArray; + } + + private JsonObject getFileAsJson( + ServletFile file, + String projectURL, + String postbackKey + ) { + JsonObject jsonFile = new JsonObject(); + jsonFile.addProperty("name", file.getPath()); + if (file.isChanged()) { + String identifier = file.getUniqueIdentifier(); + String url = projectURL + "/" + identifier + "?key=" + postbackKey; + jsonFile.addProperty("url", url); + } + return jsonFile; + } + + public String getProjectName() { + return projectName; + } + + public List getDeleted() { + return deleted; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("VersionId: "); + sb.append(currentVersion); + sb.append(", files: "); + sb.append(files); + sb.append(", deleted: "); + sb.append(deleted); + return sb.toString(); + } + + @Override + public void close() throws IOException { + deleteServletFiles(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/LockAllWaiter.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/LockAllWaiter.java new file mode 100644 index 0000000000..a148aea70e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/LockAllWaiter.java @@ -0,0 +1,10 @@ +package uk.ac.ic.wlgitbridge.data; + +/** + * Created by Winston on 21/02/15. + */ +public interface LockAllWaiter { + + void threadsRemaining(int threads); + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/ProjectLockImpl.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/ProjectLockImpl.java new file mode 100644 index 0000000000..ee6729e916 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/ProjectLockImpl.java @@ -0,0 +1,89 @@ +package uk.ac.ic.wlgitbridge.data; + +import uk.ac.ic.wlgitbridge.bridge.lock.ProjectLock; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import uk.ac.ic.wlgitbridge.util.Log; + +/** + * Created by Winston on 20/11/14. + */ +public class ProjectLockImpl implements ProjectLock { + + private final Map projectLocks; + private final ReentrantReadWriteLock rwlock; + private final Lock rlock; + private final ReentrantReadWriteLock.WriteLock wlock; + private LockAllWaiter waiter; + private boolean waiting; + + public ProjectLockImpl() { + projectLocks = new HashMap(); + rwlock = new ReentrantReadWriteLock(); + rlock = rwlock.readLock(); + wlock = rwlock.writeLock(); + waiting = false; + } + + public ProjectLockImpl(LockAllWaiter waiter) { + this(); + setWaiter(waiter); + } + + @Override + public void lockForProject(String projectName) { + Log.debug("[{}] taking project lock", projectName); + getLockForProjectName(projectName).lock(); + Log.debug("[{}] taking reentrant lock", projectName); + rlock.lock(); + Log.debug("[{}] taken locks", projectName); + } + + @Override + public void unlockForProject(String projectName) { + Log.debug("[{}] releasing project lock", projectName); + getLockForProjectName(projectName).unlock(); + Log.debug("[{}] releasing reentrant lock", projectName); + rlock.unlock(); + Log.debug("[{}] released locks", projectName); + if (waiting) { + Log.debug("[{}] waiting for remaining threads", projectName); + trySignal(); + } + } + + private void trySignal() { + int threads = rwlock.getReadLockCount(); + Log.debug("-> waiting for {} threads", threads); + if (waiter != null && threads > 0) { + waiter.threadsRemaining(threads); + } + Log.debug("-> finished waiting for threads"); + } + + public void lockAll() { + Log.debug("-> locking all threads"); + waiting = true; + trySignal(); + Log.debug("-> locking reentrant write lock"); + wlock.lock(); + } + + private synchronized Lock getLockForProjectName(String projectName) { + Lock lock = projectLocks.get(projectName); + if (lock == null) { + lock = new ReentrantLock(); + projectLocks.put(projectName, lock); + } + return lock; + } + + public void setWaiter(LockAllWaiter waiter) { + this.waiter = waiter; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/ServletFile.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/ServletFile.java new file mode 100644 index 0000000000..cabd761a98 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/ServletFile.java @@ -0,0 +1,46 @@ +package uk.ac.ic.wlgitbridge.data; + +import uk.ac.ic.wlgitbridge.data.filestore.RawFile; +import java.util.UUID; + +/** + * Created by Winston on 21/02/15. + */ +public class ServletFile extends RawFile { + + private final RawFile file; + private final boolean changed; + private String uuid; + + public ServletFile(RawFile file, RawFile oldFile) { + this.file = file; + this.uuid = UUID.randomUUID().toString(); + changed = !equals(oldFile); + } + + public String getUniqueIdentifier() { return uuid; } + + @Override + public String getPath() { + return file.getPath(); + } + + @Override + public byte[] getContents() { + return file.getContents(); + } + + @Override + public long size() { + return getContents().length; + } + + public boolean isChanged() { + return changed; + } + + @Override + public String toString() { + return getPath(); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/GitDirectoryContents.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/GitDirectoryContents.java new file mode 100644 index 0000000000..0186efedbc --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/GitDirectoryContents.java @@ -0,0 +1,84 @@ +package uk.ac.ic.wlgitbridge.data.filestore; + +import uk.ac.ic.wlgitbridge.data.model.Snapshot; +import uk.ac.ic.wlgitbridge.util.Util; + +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.List; + +/** + * Created by Winston on 14/11/14. + */ +public class GitDirectoryContents { + + private final List files; + private final File gitDirectory; + private final String userName; + private final String userEmail; + private final String commitMessage; + private final Date when; + + public GitDirectoryContents( + List files, + File rootGitDirectory, + String projectName, + String userName, + String userEmail, + String commitMessage, + Date when + ) { + this.files = files; + this.gitDirectory = new File(rootGitDirectory, projectName); + this.userName = userName; + this.userEmail = userEmail; + this.commitMessage = commitMessage; + this.when = when; + } + + public GitDirectoryContents( + List files, + File rootGitDirectory, + String projectName, + Snapshot snapshot + ) { + this( + files, + rootGitDirectory, + projectName, + snapshot.getUserName(), + snapshot.getUserEmail(), + snapshot.getComment(), + snapshot.getCreatedAt() + ); + } + + public void write() throws IOException { + Util.deleteInDirectoryApartFrom(gitDirectory, ".git"); + for (RawFile fileNode : files) { + fileNode.writeToDisk(gitDirectory); + } + } + + public File getDirectory() { + return gitDirectory; + } + + public String getUserName() { + return userName; + } + + public String getUserEmail() { + return userEmail; + } + + public String getCommitMessage() { + return commitMessage; + } + + public Date getWhen() { + return when; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/RawDirectory.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/RawDirectory.java new file mode 100644 index 0000000000..db8a554282 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/RawDirectory.java @@ -0,0 +1,23 @@ +package uk.ac.ic.wlgitbridge.data.filestore; + +import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException; + +import java.util.Map; +import java.util.Optional; + +/** + * Created by Winston on 16/11/14. + */ +public class RawDirectory { + + private final Map fileTable; + + public RawDirectory(Map fileTable) { + this.fileTable = fileTable; + } + + public Map getFileTable() { + return fileTable; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/RawFile.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/RawFile.java new file mode 100644 index 0000000000..8c74f4de20 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/RawFile.java @@ -0,0 +1,46 @@ +package uk.ac.ic.wlgitbridge.data.filestore; + +import uk.ac.ic.wlgitbridge.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * Created by Winston on 16/11/14. + */ +public abstract class RawFile { + + public abstract String getPath(); + + public abstract byte[] getContents(); + + public abstract long size(); + + public final void writeToDisk(File directory) throws IOException { + writeToDiskWithName(directory, getPath()); + } + + public final void writeToDiskWithName(File directory, String name) throws IOException { + File file = new File(directory, name); + file.getParentFile().mkdirs(); + file.createNewFile(); + OutputStream out = new FileOutputStream(file); + out.write(getContents()); + out.close(); + Log.info("Wrote file: {}", file.getAbsolutePath()); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof RawFile)) { + return false; + } + RawFile that = (RawFile) obj; + return getPath().equals(that.getPath()) + && Arrays.equals(getContents(), that.getContents()); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/RepositoryFile.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/RepositoryFile.java new file mode 100644 index 0000000000..e7cc331aec --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/filestore/RepositoryFile.java @@ -0,0 +1,31 @@ +package uk.ac.ic.wlgitbridge.data.filestore; + +/** + * Created by Winston on 16/11/14. + */ +public class RepositoryFile extends RawFile { + + private final String path; + private final byte[] contents; + + public RepositoryFile(String path, byte[] contents) { + this.path = path; + this.contents = contents; + } + + @Override + public String getPath() { + return path; + } + + @Override + public byte[] getContents() { + return contents; + } + + @Override + public long size() { + return contents.length; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/model/Snapshot.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/model/Snapshot.java new file mode 100644 index 0000000000..467691997c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/data/model/Snapshot.java @@ -0,0 +1,77 @@ +package uk.ac.ic.wlgitbridge.data.model; + +import org.joda.time.DateTime; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotAttachment; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotData; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotFile; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.SnapshotInfo; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.WLUser; + +import java.util.Date; +import java.util.List; + +/** + * Created by Winston on 03/11/14. + */ +public class Snapshot implements Comparable { + + private final int versionID; + private final String comment; + private final String userName; + private final String userEmail; + private final Date createdAt; + + private final List srcs; + private final List atts; + + public Snapshot(SnapshotInfo info, SnapshotData data) { + versionID = info.getVersionId(); + comment = info.getComment(); + WLUser user = info.getUser(); + userName = user.getName(); + userEmail = user.getEmail(); + createdAt = new DateTime(info.getCreatedAt()).toDate(); + + srcs = data.getSrcs(); + atts = data.getAtts(); + } + + public int getVersionID() { + return versionID; + } + + public String getComment() { + return comment; + } + + public String getUserName() { + return userName; + } + + public String getUserEmail() { + return userEmail; + } + + public List getSrcs() { + return srcs; + } + + public List getAtts() { + return atts; + } + + public Date getCreatedAt() { + return createdAt; + } + + @Override + public int compareTo(Snapshot snapshot) { + return Integer.compare(versionID, snapshot.versionID); + } + + @Override + public String toString() { + return String.valueOf(versionID); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/FileLimitExceededException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/FileLimitExceededException.java new file mode 100644 index 0000000000..f25767a4db --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/FileLimitExceededException.java @@ -0,0 +1,34 @@ +package uk.ac.ic.wlgitbridge.git.exception; + +import uk.ac.ic.wlgitbridge.util.Util; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class FileLimitExceededException extends GitUserException { + + private final long numFiles; + + private final long maxFiles; + + public FileLimitExceededException(long numFiles, long maxFiles) { + this.numFiles = numFiles; + this.maxFiles = maxFiles; + } + + @Override + public String getMessage() { + return "too many files"; + } + + @Override + public List getDescriptionLines() { + return Arrays.asList( + "repository contains " + + numFiles + " files, which exceeds the limit of " + + maxFiles + " files" + ); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/GitUserException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/GitUserException.java new file mode 100644 index 0000000000..91a7478c89 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/GitUserException.java @@ -0,0 +1,14 @@ +package uk.ac.ic.wlgitbridge.git.exception; + +import java.util.List; + +/** + * Created by winston on 20/08/2016. + */ +public abstract class GitUserException extends Exception { + + public abstract String getMessage(); + + public abstract List getDescriptionLines(); + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/InvalidGitRepository.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/InvalidGitRepository.java new file mode 100644 index 0000000000..de4129c44c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/InvalidGitRepository.java @@ -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 getDescriptionLines() { + return Arrays.asList( + "Your Git repository contains a reference we cannot resolve.", + "If your project contains a Git submodule,", + "please remove it and try again." + ); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/SizeLimitExceededException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/SizeLimitExceededException.java new file mode 100644 index 0000000000..69c6bd014a --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/SizeLimitExceededException.java @@ -0,0 +1,40 @@ +package uk.ac.ic.wlgitbridge.git.exception; + +import uk.ac.ic.wlgitbridge.util.Util; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class SizeLimitExceededException extends GitUserException { + + private final Optional path; + + private final long actualSize; + + private final long maxSize; + + public SizeLimitExceededException( + Optional path, long actualSize, long maxSize) { + this.path = path; + this.actualSize = actualSize; + this.maxSize = maxSize; + } + + @Override + public String getMessage() { + return "file too big"; + } + + @Override + public List getDescriptionLines() { + String filename = + path.isPresent() ? "File '" + path.get() + "' is" : "There's a file"; + return Arrays.asList( + filename + " too large to push to " + + Util.getServiceName() + " via git", + "the recommended maximum file size is 50 MiB" + ); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/SnapshotAPIException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/SnapshotAPIException.java new file mode 100644 index 0000000000..0a5e5a0529 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/exception/SnapshotAPIException.java @@ -0,0 +1,10 @@ +package uk.ac.ic.wlgitbridge.git.exception; + +import uk.ac.ic.wlgitbridge.snapshot.base.JSONSource; + +/** + * Created by winston on 20/08/2016. + */ +public abstract class SnapshotAPIException + extends GitUserException + implements JSONSource {} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/WLReceivePackFactory.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/WLReceivePackFactory.java new file mode 100644 index 0000000000..c6f0444fba --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/WLReceivePackFactory.java @@ -0,0 +1,81 @@ +package uk.ac.ic.wlgitbridge.git.handler; + +import com.google.api.client.auth.oauth2.Credential; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.ReceivePack; +import org.eclipse.jgit.transport.resolver.ReceivePackFactory; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.bridge.snapshot.SnapshotApi; +import uk.ac.ic.wlgitbridge.git.handler.hook.WriteLatexPutHook; +import uk.ac.ic.wlgitbridge.git.servlet.WLGitServlet; +import uk.ac.ic.wlgitbridge.server.Oauth2Filter; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.Util; + +import javax.servlet.http.HttpServletRequest; +import java.util.Optional; + +/** + * Created by Winston on 02/11/14. + */ +/** + * One of the "big three" interfaces created by {@link WLGitServlet} to handle + * user Git requests. + * + * This class just puts a {@link WriteLatexPutHook} into the {@link ReceivePack} + * that it returns. + */ +public class WLReceivePackFactory + implements ReceivePackFactory { + + private final RepoStore repoStore; + + private final Bridge bridge; + + public WLReceivePackFactory(RepoStore repoStore, Bridge bridge) { + this.repoStore = repoStore; + this.bridge = bridge; + } + + /** + * Puts a {@link WriteLatexPutHook} into the returned {@link ReceivePack}. + * + * The {@link WriteLatexPutHook} needs our hostname, which we get from the + * original {@link HttpServletRequest}, used to provide a postback URL to + * the {@link SnapshotApi}. We also give it the oauth2 that we injected in + * the {@link Oauth2Filter}, and the {@link Bridge}. + * + * At this point, the repository will have been synced to the latest on + * Overleaf, but it's possible that an update happens on Overleaf while our + * put hook is running. In this case, we fail, and the user tries again, + * triggering another sync, and so on. + * @param httpServletRequest the original request + * @param repository the JGit {@link Repository} provided by + * {@link WLRepositoryResolver} + * @return a correctly hooked {@link ReceivePack} + */ + @Override + public ReceivePack create( + HttpServletRequest httpServletRequest, + Repository repository + ) { + Log.info( + "[{}] Creating receive-pack", + repository.getWorkTree().getName() + ); + Optional oauth2 = Optional.ofNullable( + (Credential) httpServletRequest.getAttribute( + Oauth2Filter.ATTRIBUTE_KEY)); + ReceivePack receivePack = new ReceivePack(repository); + String hostname = Util.getPostbackURL(); + if (hostname == null) { + hostname = httpServletRequest.getLocalName(); + } + receivePack.setPreReceiveHook( + new WriteLatexPutHook(repoStore, bridge, hostname, oauth2) + ); + return receivePack; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/WLRepositoryResolver.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/WLRepositoryResolver.java new file mode 100644 index 0000000000..ec7cc47d3d --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/WLRepositoryResolver.java @@ -0,0 +1,126 @@ +package uk.ac.ic.wlgitbridge.git.handler; + +import com.google.api.client.auth.oauth2.Credential; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.ServiceMayNotContinueException; +import org.eclipse.jgit.transport.resolver.RepositoryResolver; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; +import uk.ac.ic.wlgitbridge.git.handler.hook.WriteLatexPutHook; +import uk.ac.ic.wlgitbridge.git.servlet.WLGitServlet; +import uk.ac.ic.wlgitbridge.server.GitBridgeServer; +import uk.ac.ic.wlgitbridge.server.Oauth2Filter; +import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.Util; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Arrays; +import java.util.Optional; + +/** + * Created by Winston on 02/11/14. + */ +/** + * One of the "big three" interfaces created by {@link WLGitServlet} to handle + * user Git requests. + * + * This class is used by all Git requests to resolve a project name to a + * JGit {@link Repository}, or fail by throwing an exception. + * + * It has a single method, {@link #open(HttpServletRequest, String)}, which + * calls into the {@link Bridge} to synchronise the project with Overleaf, i.e. + * bringing it onto disk and applying commits to it until it is up-to-date with + * Overleaf. + */ +public class WLRepositoryResolver + implements RepositoryResolver { + + private final Bridge bridge; + + public WLRepositoryResolver(Bridge bridge) { + this.bridge = bridge; + } + + /** + * Calls into the Bridge to resolve a project name to a JGit + * {@link Repository}, or throw an exception. + * + * On success, the repository will have been brought onto disk and updated + * to the latest (synced). + * + * In the case of clones and fetches, upload packs are created from the + * returned JGit {@link Repository} by the {@link WLUploadPackFactory}. + * + * The project lock is acquired for this process so it can't be swapped out. + * + * However, it can still be swapped out between this and a Git push. The + * push would fail due to the project changed on Overleaf between the sync + * and the actual push to Overleaf (performed by the + * {@link WLReceivePackFactory} and {@link WriteLatexPutHook}. In this case, + * the user will have to try again (which prompts another update, etc. until + * this no longer happens). + * @param httpServletRequest The HttpServletRequest as required by the + * interface. We injected the oauth2 creds into it with + * {@link Oauth2Filter}, which was set up by the {@link GitBridgeServer}. + * @param name The name of the project + * @return the JGit {@link Repository}. + * @throws RepositoryNotFoundException If the project does not exist + * @throws ServiceNotAuthorizedException If the user did not auth when + * required to + * @throws ServiceMayNotContinueException If any other general user + * exception occurs that must be propogated back to the user, e.g. + * internal errors (IOException, etc), too large file, and so on. + */ + @Override + public Repository open( + HttpServletRequest httpServletRequest, + String name + ) throws RepositoryNotFoundException, + ServiceNotAuthorizedException, + ServiceMayNotContinueException { + Log.info("[{}] Request to open git repo", name); + Optional oauth2 = Optional.ofNullable( + (Credential) httpServletRequest.getAttribute( + Oauth2Filter.ATTRIBUTE_KEY)); + String projName = Util.removeAllSuffixes(name, "/", ".git"); + try { + return bridge.getUpdatedRepo(oauth2, projName).getJGitRepository(); + } catch (RepositoryNotFoundException e) { + Log.info("Repository not found: " + name); + throw e; + /* + } catch (ServiceNotAuthorizedException e) { + cannot occur + } catch (ServiceNotEnabledException e) { + cannot occur + */ + } catch (ServiceMayNotContinueException e) { + /* Such as FailedConnectionException */ + throw e; + } catch (RuntimeException e) { + Log.warn( + "Runtime exception when trying to open repo: " + projName, + e + ); + throw new ServiceMayNotContinueException(e); + } catch (ForbiddenException e) { + throw new ServiceNotAuthorizedException(); + } catch (GitUserException e) { + throw new ServiceMayNotContinueException( + e.getMessage() + "\n" + + String.join("\n", e.getDescriptionLines()), + e); + } catch (IOException e) { + Log.warn( + "IOException when trying to open repo: " + projName, + e + ); + throw new ServiceMayNotContinueException("Internal server error."); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/WLUploadPackFactory.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/WLUploadPackFactory.java new file mode 100644 index 0000000000..1cea70a30b --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/WLUploadPackFactory.java @@ -0,0 +1,44 @@ +package uk.ac.ic.wlgitbridge.git.handler; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.UploadPack; +import org.eclipse.jgit.transport.resolver.UploadPackFactory; +import uk.ac.ic.wlgitbridge.git.servlet.WLGitServlet; +import uk.ac.ic.wlgitbridge.util.Log; + +import javax.servlet.http.HttpServletRequest; + +/** + * Created by Winston on 02/11/14. + */ +/** + * One of the "big three" interfaces created by {@link WLGitServlet} to handle + * user Git requests. + * + * The actual class doesn't do much, and most of the work is done when the + * project name is being resolved by the {@link WLRepositoryResolver}. + */ +public class WLUploadPackFactory + implements UploadPackFactory { + + /** + * This does nothing special. Synchronising the project with Overleaf will + * have been performed by {@link WLRepositoryResolver}. + * @param __ Not used, required by the {@link UploadPackFactory} interface + * @param repository The JGit repository provided by the + * {@link WLRepositoryResolver} + * @return the {@link UploadPack}, used by JGit to serve the request + */ + @Override + public UploadPack create( + HttpServletRequest __, + Repository repository + ) { + Log.info( + "[{}] Creating upload-pack", + repository.getWorkTree().getName() + ); + return new UploadPack(repository); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/hook/WriteLatexPutHook.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/hook/WriteLatexPutHook.java new file mode 100644 index 0000000000..a786e831b5 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/hook/WriteLatexPutHook.java @@ -0,0 +1,178 @@ +package uk.ac.ic.wlgitbridge.git.handler.hook; + +import com.google.api.client.auth.oauth2.Credential; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.PreReceiveHook; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.transport.ReceiveCommand.Result; +import org.eclipse.jgit.transport.ReceivePack; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; +import uk.ac.ic.wlgitbridge.git.handler.WLReceivePackFactory; +import uk.ac.ic.wlgitbridge.git.handler.hook.exception.ForcedPushException; +import uk.ac.ic.wlgitbridge.git.handler.hook.exception.WrongBranchException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.InternalErrorException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.OutOfDateException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.SnapshotPostException; +import uk.ac.ic.wlgitbridge.util.Log; + +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; + +/** + * Created by Winston on 03/11/14. + */ +/** + * Created by {@link WLReceivePackFactory} to update the {@link Bridge} for a + * user's Git push request, or fail with an error. The hook is able to approve + * or reject a request. + */ +public class WriteLatexPutHook implements PreReceiveHook { + + private final RepoStore repoStore; + + private final Bridge bridge; + private final String hostname; + private final Optional oauth2; + + /** + * The constructor to use, which provides the hook with the {@link Bridge}, + * the hostname (used to construct a URL to give to Overleaf to postback), + * and the oauth2 (used to authenticate with the Snapshot API). + * @param repoStore + * @param bridge the {@link Bridge} + * @param hostname the hostname used for postback from the Snapshot API + * @param oauth2 used to authenticate with the snapshot API, or null + */ + public WriteLatexPutHook( + RepoStore repoStore, + Bridge bridge, + String hostname, + Optional oauth2 + ) { + this.repoStore = repoStore; + this.bridge = bridge; + this.hostname = hostname; + this.oauth2 = oauth2; + } + + @Override + public void onPreReceive( + ReceivePack receivePack, + Collection receiveCommands + ) { + Log.debug("-> Handling {} commands in {}", receiveCommands.size(), receivePack.getRepository().getDirectory().getAbsolutePath()); + for (ReceiveCommand receiveCommand : receiveCommands) { + try { + handleReceiveCommand( + oauth2, + receivePack.getRepository(), + receiveCommand + ); + } catch (IOException e) { + Log.debug("IOException on pre receive: {}", e.getMessage()); + receivePack.sendError(e.getMessage()); + receiveCommand.setResult( + Result.REJECTED_OTHER_REASON, + e.getMessage() + ); + } catch (OutOfDateException e) { + Log.debug("OutOfDateException on pre receive: {}", e.getMessage()); + receiveCommand.setResult(Result.REJECTED_NONFASTFORWARD); + } catch (GitUserException e) { + Log.debug("GitUserException on pre receive: {}", e.getMessage()); + handleSnapshotPostException(receivePack, receiveCommand, e); + } catch (Throwable t) { + Log.warn("Throwable on pre receive: {}", t.getMessage()); + handleSnapshotPostException( + receivePack, + receiveCommand, + new InternalErrorException() + ); + } + } + Log.debug("-> Handled {} commands in {}", receiveCommands.size(), receivePack.getRepository().getDirectory().getAbsolutePath()); + } + + private void handleSnapshotPostException( + ReceivePack receivePack, + ReceiveCommand receiveCommand, + GitUserException e + ) { + String message = e.getMessage(); + receivePack.sendError(message); + StringBuilder msg = new StringBuilder(); + for ( + Iterator it = e.getDescriptionLines().iterator(); + it.hasNext(); + ) { + String line = it.next(); + msg.append("hint: "); + msg.append(line); + if (it.hasNext()) { + msg.append('\n'); + } + } + receivePack.sendMessage(""); + receivePack.sendMessage(msg.toString()); + receiveCommand.setResult(Result.REJECTED_OTHER_REASON, message); + } + + private void handleReceiveCommand( + Optional oauth2, + Repository repository, + ReceiveCommand receiveCommand + ) throws IOException, GitUserException { + checkBranch(receiveCommand); + checkForcedPush(receiveCommand); + bridge.push( + oauth2, + repository.getWorkTree().getName(), + getPushedDirectoryContents(repository, + receiveCommand), + getOldDirectoryContents(repository), + hostname + ); + } + + private void checkBranch( + ReceiveCommand receiveCommand + ) throws WrongBranchException { + if (!receiveCommand.getRefName().equals("refs/heads/master")) { + throw new WrongBranchException(); + } + } + + private void checkForcedPush( + ReceiveCommand receiveCommand + ) throws ForcedPushException { + if ( + receiveCommand.getType() + == ReceiveCommand.Type.UPDATE_NONFASTFORWARD + ) { + throw new ForcedPushException(); + } + } + + private RawDirectory getPushedDirectoryContents( + Repository repository, + ReceiveCommand receiveCommand + ) throws IOException, GitUserException { + return repoStore + .useJGitRepo(repository, receiveCommand.getNewId()) + .getDirectory(); + } + + private RawDirectory getOldDirectoryContents( + Repository repository + ) throws IOException, GitUserException { + return repoStore + .useJGitRepo(repository, repository.resolve("HEAD")) + .getDirectory(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/hook/exception/ForcedPushException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/hook/exception/ForcedPushException.java new file mode 100644 index 0000000000..834964019f --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/hook/exception/ForcedPushException.java @@ -0,0 +1,37 @@ +package uk.ac.ic.wlgitbridge.git.handler.hook.exception; + +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.util.Util; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.SnapshotPostException; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by Winston on 16/11/14. + */ +public class ForcedPushException extends SnapshotPostException { + + private static final String[] DESCRIPTION_LINES = { + "You can't git push --force to a " + + Util.getServiceName() + + " project.", + "Try to put your changes on top of the current head.", + "If everything else fails, delete and reclone your repository, " + + "make your changes, then push again." + }; + + @Override + public String getMessage() { + return "forced push prohibited"; + } + + @Override + public List getDescriptionLines() { + return Arrays.asList(DESCRIPTION_LINES); + } + + @Override + public void fromJSON(JsonElement json) {} + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/hook/exception/WrongBranchException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/hook/exception/WrongBranchException.java new file mode 100644 index 0000000000..a2d4f32e88 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/handler/hook/exception/WrongBranchException.java @@ -0,0 +1,34 @@ +package uk.ac.ic.wlgitbridge.git.handler.hook.exception; + +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.SnapshotPostException; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by Winston on 19/12/14. + */ +public class WrongBranchException extends SnapshotPostException { + + private static final String[] DESCRIPTION_LINES = { + "You can't push any new branches.", + "Please use the master branch." + }; + + @Override + public String getMessage() { + return "wrong branch"; + } + + @Override + public List getDescriptionLines() { + return Arrays.asList(DESCRIPTION_LINES); + } + + @Override + public void fromJSON(JsonElement json) { + + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/servlet/WLGitServlet.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/servlet/WLGitServlet.java new file mode 100644 index 0000000000..da491046ed --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/servlet/WLGitServlet.java @@ -0,0 +1,52 @@ +package uk.ac.ic.wlgitbridge.git.servlet; + +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jgit.http.server.GitServlet; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.git.handler.WLReceivePackFactory; +import uk.ac.ic.wlgitbridge.git.handler.WLRepositoryResolver; +import uk.ac.ic.wlgitbridge.git.handler.WLUploadPackFactory; +import uk.ac.ic.wlgitbridge.server.GitBridgeServer; + +import javax.servlet.ServletException; + +/** + * Created by Winston on 02/11/14. + */ +/** + * This is the Servlet created by the {@link GitBridgeServer} that does all of + * the work in handling user Git requests and directing them to the + * {@link Bridge}. + * + * The {@link GitServlet} does all of the Git work, and these main three + * interfaces do all of the Git Bridge work: + * + * @see WLRepositoryResolver + * @see WLReceivePackFactory + * @see WLUploadPackFactory + */ +public class WLGitServlet extends GitServlet { + + /** + * Constructor that sets all of the resolvers and factories for the + * {@link GitServlet}. + * + * Also needs to call init with a config ({@link WLGitServletConfig}, as + * required by the {@link GitServlet}. + * @param ctxHandler + * @param bridge + * @throws ServletException + */ + public WLGitServlet( + ServletContextHandler ctxHandler, + RepoStore repoStore, + Bridge bridge + ) throws ServletException { + setRepositoryResolver(new WLRepositoryResolver(bridge)); + setReceivePackFactory(new WLReceivePackFactory(repoStore, bridge)); + setUploadPackFactory(new WLUploadPackFactory()); + init(new WLGitServletConfig(ctxHandler)); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/servlet/WLGitServletConfig.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/servlet/WLGitServletConfig.java new file mode 100644 index 0000000000..64bcd69c98 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/servlet/WLGitServletConfig.java @@ -0,0 +1,42 @@ +package uk.ac.ic.wlgitbridge.git.servlet; + +import org.eclipse.jetty.servlet.ServletContextHandler; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import java.util.Enumeration; + +/** + * Created by Winston on 02/11/14. + */ +public class WLGitServletConfig implements ServletConfig { + + private static final String SERVLET_NAME = "git-servlet"; + + private ServletContext servletContext; + + public WLGitServletConfig(ServletContextHandler ctxHandler) { + servletContext = ctxHandler.getServletContext(); + } + + @Override + public String getServletName() { + return SERVLET_NAME; + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public String getInitParameter(String s) { + return null; + } + + @Override + public Enumeration getInitParameterNames() { + return null; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/util/RepositoryObjectTreeWalker.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/util/RepositoryObjectTreeWalker.java new file mode 100644 index 0000000000..f7c9f555da --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/git/util/RepositoryObjectTreeWalker.java @@ -0,0 +1,102 @@ +package uk.ac.ic.wlgitbridge.git.util; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; +import uk.ac.ic.wlgitbridge.bridge.util.CastUtil; +import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory; +import uk.ac.ic.wlgitbridge.data.filestore.RawFile; +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 java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Created by Winston on 16/11/14. + */ +public class RepositoryObjectTreeWalker { + + private final TreeWalk treeWalk; + private final Repository repository; + + public RepositoryObjectTreeWalker( + Repository repository, + ObjectId objectId + ) throws IOException { + treeWalk = initTreeWalk(repository, objectId); + this.repository = repository; + } + + public RepositoryObjectTreeWalker( + Repository repository + ) throws IOException { + this(repository, 0); + } + + public RepositoryObjectTreeWalker( + Repository repository, + int fromHead + ) throws IOException { + this(repository, repository.resolve("HEAD~" + fromHead)); + } + + public RawDirectory getDirectoryContents(Optional maxFileSize) + throws IOException, + SizeLimitExceededException, + InvalidGitRepository { + return new RawDirectory(walkGitObjectTree(maxFileSize)); + } + + private TreeWalk initTreeWalk( + Repository repository, + ObjectId objectId + ) throws IOException { + if (objectId == null) { + return null; + } + RevWalk walk = new RevWalk(repository); + TreeWalk treeWalk = new TreeWalk(repository); + treeWalk.addTree(walk.parseCommit(objectId).getTree()); + treeWalk.setRecursive(true); + return treeWalk; + } + + private Map walkGitObjectTree(Optional maxFileSize) + throws IOException, + SizeLimitExceededException, + InvalidGitRepository { + Map fileContentsTable = new HashMap<>(); + if (treeWalk == null) { + return fileContentsTable; + } + while (treeWalk.next()) { + String path = treeWalk.getPathString(); + + ObjectId objectId = treeWalk.getObjectId(0); + if (!repository.hasObject(objectId)) { + throw new InvalidGitRepository(); + } + ObjectLoader obj = repository.open(objectId); + long size = obj.getSize(); + if (maxFileSize.isPresent() && size > maxFileSize.get()) { + throw new SizeLimitExceededException( + Optional.ofNullable(path), size, maxFileSize.get()); + } + try (ByteArrayOutputStream o = new ByteArrayOutputStream( + CastUtil.assumeInt(size))) { + obj.copyTo(o); + fileContentsTable.put( + path, new RepositoryFile(path, o.toByteArray())); + }; + } + return fileContentsTable; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/io/http/ning/NingHttpClient.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/io/http/ning/NingHttpClient.java new file mode 100644 index 0000000000..e9d7f5f789 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/io/http/ning/NingHttpClient.java @@ -0,0 +1,81 @@ +package uk.ac.ic.wlgitbridge.io.http.ning; + +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.ic.wlgitbridge.util.FunctionT; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class NingHttpClient implements NingHttpClientFacade { + + private static final Logger log + = LoggerFactory.getLogger(NingHttpClient.class); + + private final AsyncHttpClient http; + + public NingHttpClient(AsyncHttpClient http) { + this.http = http; + } + + @Override + public byte[] get( + String url, + FunctionT handler + ) throws ExecutionException { + try { + return http + .prepareGet(url) + .execute(new AsyncCompletionHandler() { + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + @Override + public State onHeadersReceived( + HttpHeaders headers + ) throws E { + return handler.apply(headers) + ? State.CONTINUE : State.ABORT; + } + + @Override + public State onBodyPartReceived( + HttpResponseBodyPart content + ) throws IOException { + bytes.write(content.getBodyPartBytes()); + return State.CONTINUE; + } + + @Override + public byte[] onCompleted( + Response response + ) throws Exception { + int statusCode = response.getStatusCode(); + if (statusCode >= 400) { + throw new Exception("got status " + statusCode + + " fetching " + url); + } + byte[] ret = bytes.toByteArray(); + bytes.close(); + log.info( + statusCode + + " " + + response.getStatusText() + + " (" + + ret.length + + "B) -> " + + url + ); + return ret; + } + + }).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/io/http/ning/NingHttpClientFacade.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/io/http/ning/NingHttpClientFacade.java new file mode 100644 index 0000000000..2d1e0b3744 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/io/http/ning/NingHttpClientFacade.java @@ -0,0 +1,22 @@ +package uk.ac.ic.wlgitbridge.io.http.ning; + +import io.netty.handler.codec.http.HttpHeaders; +import uk.ac.ic.wlgitbridge.util.FunctionT; + +import java.util.concurrent.ExecutionException; + +public interface NingHttpClientFacade { + + /** + * Performs a GET request + * @param url the target URL + * @param handler handler for the response headers. Returning false + * aborts the request. + * @return + */ + byte[] get( + String url, + FunctionT handler + ) throws ExecutionException; + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/DiagnosticsHandler.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/DiagnosticsHandler.java new file mode 100644 index 0000000000..4ff339de9e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/DiagnosticsHandler.java @@ -0,0 +1,71 @@ +package uk.ac.ic.wlgitbridge.server; + +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.util.Log; + +import javax.management.JMException; +import javax.management.ObjectName; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.Writer; +import java.lang.management.ManagementFactory; + +public class DiagnosticsHandler extends AbstractHandler { + + public DiagnosticsHandler() { + } + + @Override + public void handle( + String target, + Request baseRequest, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException, ServletException { + String method = baseRequest.getMethod(); + if ( + ("GET".equals(method)) + && target != null + && target.matches("^/diags/?$") + ) { + baseRequest.setHandled(true); + + Log.info(method + " <- /diags"); + + String detail; + String summary; + + try { + detail = execute("vmNativeMemory", "detail"); + summary = execute("vmNativeMemory", "summary"); + } catch(JMException e) { + Log.error("Failed to get native memory detail: " + e.getMessage()); + response.setStatus(500); + return; + } + + response.setContentType("text/plain"); + response.setStatus(200); + + response.getWriter().write(summary); + response.getWriter().write("\n----------\n\n"); + response.getWriter().write(detail); + response.getWriter().flush(); + } + } + + public static String execute(String command, String... args) throws JMException { + return (String) ManagementFactory.getPlatformMBeanServer().invoke( + new ObjectName("com.sun.management:type=DiagnosticCommand"), + command, + new Object[]{args}, + new String[]{"[Ljava.lang.String;"}); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/FileHandler.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/FileHandler.java new file mode 100644 index 0000000000..32bdbbb89f --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/FileHandler.java @@ -0,0 +1,64 @@ +package uk.ac.ic.wlgitbridge.server; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.ResourceHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.InvalidPostbackKeyException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Serve files referenced by the snapshot that we send to the Overleaf API. + * + * Requests must include the postback key. + */ +public class FileHandler extends ResourceHandler { + private static final Logger LOG + = LoggerFactory.getLogger(FileHandler.class); + + private final Bridge bridge; + private final Pattern DOC_KEY_PATTERN = Pattern.compile("^/(\\w+)/.+$"); + + public FileHandler(Bridge bridge) { + this.bridge = bridge; + } + + @Override + public void handle( + String target, + Request baseRequest, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException, ServletException { + if (!"GET".equals(baseRequest.getMethod())) return; + LOG.info("GET <- {}", baseRequest.getRequestURI()); + + Matcher docKeyMatcher = DOC_KEY_PATTERN.matcher(target); + if (!docKeyMatcher.matches()) return; + String docKey = docKeyMatcher.group(1); + + String apiKey = request.getParameter("key"); + if (apiKey == null) return; + + try { + bridge.checkPostbackKey(docKey, apiKey); + } catch (InvalidPostbackKeyException e) { + LOG.warn( + "INVALID POST BACK KEY: docKey={} apiKey={}", + docKey, + apiKey + ); + return; + } + + super.handle(target, baseRequest, request, response); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/GitBridgeServer.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/GitBridgeServer.java new file mode 100644 index 0000000000..312e3648b9 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/GitBridgeServer.java @@ -0,0 +1,199 @@ +package uk.ac.ic.wlgitbridge.server; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.*; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import uk.ac.ic.wlgitbridge.application.config.Config; +import uk.ac.ic.wlgitbridge.application.jetty.NullLogger; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SqliteDBStore; +import uk.ac.ic.wlgitbridge.bridge.repo.FSGitRepoStore; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStoreConfig; +import uk.ac.ic.wlgitbridge.bridge.snapshot.NetSnapshotApi; +import uk.ac.ic.wlgitbridge.bridge.snapshot.SnapshotApi; +import uk.ac.ic.wlgitbridge.bridge.swap.store.SwapStore; +import uk.ac.ic.wlgitbridge.git.servlet.WLGitServlet; +import uk.ac.ic.wlgitbridge.snapshot.base.SnapshotAPIRequest; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.Util; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.ServletException; +import java.io.File; +import java.net.BindException; +import java.nio.file.Paths; +import java.util.EnumSet; + +/** + * Created by Winston on 02/11/14. + */ + +/** + * Class for the actual server. + */ +public class GitBridgeServer { + + private final Bridge bridge; + + private final Server jettyServer; + + private final int port; + private String rootGitDirectoryPath; + private String apiBaseURL; + + public GitBridgeServer( + Config config + ) throws ServletException { + org.eclipse.jetty.util.log.Log.setLog(new NullLogger()); + this.port = config.getPort(); + this.rootGitDirectoryPath = config.getRootGitDirectory(); + RepoStore repoStore = new FSGitRepoStore( + rootGitDirectoryPath, + config.getRepoStore().flatMap(RepoStoreConfig::getMaxFileSize) + ); + DBStore dbStore = new SqliteDBStore( + Paths.get( + repoStore.getRootDirectory().getAbsolutePath() + ).resolve(".wlgb").resolve("wlgb.db").toFile(), + config.getSqliteHeapLimitBytes() + ); + SwapStore swapStore = SwapStore.fromConfig(config.getSwapStore()); + SnapshotApi snapshotApi = new NetSnapshotApi(); + bridge = Bridge.make( + config, + repoStore, + dbStore, + swapStore, + snapshotApi + ); + jettyServer = new Server(); + configureJettyServer(config, repoStore, snapshotApi); + apiBaseURL = config.getAPIBaseURL(); + SnapshotAPIRequest.setBaseURL(apiBaseURL); + Util.setServiceName(config.getServiceName()); + Util.setPostbackURL(config.getPostbackURL()); + Util.setPort(config.getPort()); + } + + /** + * Starts the server on the port given on construction. + */ + public void start() { + try { + bridge.checkDB(); + jettyServer.start(); + bridge.startBackgroundJobs(); + Log.info(Util.getServiceName() + "-Git Bridge server started"); + Log.info("Listening on port: " + port); + Log.info("Bridged to: " + apiBaseURL); + Log.info("Postback base URL: " + Util.getPostbackURL()); + Log.info("Root git directory path: " + rootGitDirectoryPath); + } catch (BindException e) { + Log.error("Failed to bind Jetty", e); + } catch (Exception e) { + Log.error("Failed to start Jetty", e); + } + } + + public void stop() { + try { + jettyServer.stop(); + } catch (Exception e) { + Log.error("Failed to stop Jetty", e); + } + } + + private void configureJettyServer( + Config config, + RepoStore repoStore, + SnapshotApi snapshotApi + ) throws ServletException { + ServerConnector connector = new ServerConnector(this.jettyServer); + connector.setPort(config.getPort()); + connector.setHost(config.getBindIp()); + connector.setIdleTimeout(config.getIdleTimeout()); + this.jettyServer.addConnector(connector); + + HandlerCollection handlers = new HandlerList(); + handlers.addHandler(initApiHandler()); + handlers.addHandler(initBaseHandler()); + handlers.addHandler(initGitHandler(config, repoStore, snapshotApi)); + jettyServer.setHandler(handlers); + } + + private Handler initBaseHandler() { + ContextHandler base = new ContextHandler(); + base.setContextPath("/"); + HandlerCollection handlers = new HandlerList(); + handlers.addHandler(new StatusHandler(bridge)); + handlers.addHandler(new HealthCheckHandler(bridge)); + handlers.addHandler(new GitLfsHandler(bridge)); + handlers.addHandler(new PrometheusHandler()); + handlers.addHandler(new DiagnosticsHandler()); + base.setHandler(handlers); + return base; + } + + private Handler initApiHandler() { + ContextHandler api = new ContextHandler(); + api.setContextPath("/api"); + + HandlerCollection handlers = new HandlerList(); + handlers.addHandler(initResourceHandler()); + handlers.addHandler(new PostbackHandler(bridge)); + handlers.addHandler(new DefaultHandler()); + + api.setHandler(handlers); + + ProductionErrorHandler errorHandler = new ProductionErrorHandler(); + api.setErrorHandler(errorHandler); + return api; + } + + private Handler initGitHandler( + Config config, + RepoStore repoStore, + SnapshotApi snapshotApi + ) throws ServletException { + final ServletContextHandler servletContextHandler = + new ServletContextHandler(ServletContextHandler.SESSIONS); + if (config.isUsingOauth2()) { + Filter filter = new Oauth2Filter(snapshotApi, config.getOauth2()); + servletContextHandler.addFilter( + new FilterHolder(filter), + "/*", + EnumSet.of(DispatcherType.REQUEST) + ); + } + servletContextHandler.setContextPath("/"); + servletContextHandler.addServlet( + new ServletHolder( + new WLGitServlet( + servletContextHandler, + repoStore, + bridge + ) + ), + "/*" + ); + ProductionErrorHandler errorHandler = new ProductionErrorHandler(); + servletContextHandler.setErrorHandler(errorHandler); + return servletContextHandler; + } + + private Handler initResourceHandler() { + ResourceHandler resourceHandler = new FileHandler(bridge); + resourceHandler.setResourceBase( + new File(rootGitDirectoryPath, ".wlgb/atts").getAbsolutePath() + ); + return resourceHandler; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/GitLfsHandler.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/GitLfsHandler.java new file mode 100644 index 0000000000..2fde1e6460 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/GitLfsHandler.java @@ -0,0 +1,46 @@ +package uk.ac.ic.wlgitbridge.server; + +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.util.Log; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; + +public class GitLfsHandler extends AbstractHandler { + + private final Bridge bridge; + + public GitLfsHandler(Bridge bridge) { + this.bridge = bridge; + } + + @Override + public void handle( + String target, + Request baseRequest, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException { + String method = baseRequest.getMethod(); + if ( + ("POST".equals(method)) + && target != null + && target.matches("^/[0-9a-z]+\\.git/info/lfs/objects/batch/?$") + ) { + Log.info(method + " <- /.git/info/lfs/objects/batch"); + response.setContentType("application/vnd.git-lfs+json"); + response.setStatus(422); + response.getWriter().println("{\"message\": \"ERROR: Git LFS is not supported on Overleaf\"}"); + baseRequest.setHandled(true); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/HealthCheckHandler.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/HealthCheckHandler.java new file mode 100644 index 0000000000..226fb259fe --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/HealthCheckHandler.java @@ -0,0 +1,50 @@ +package uk.ac.ic.wlgitbridge.server; + +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.util.Log; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class HealthCheckHandler extends AbstractHandler { + + private final Bridge bridge; + + public HealthCheckHandler(Bridge bridge) { + this.bridge = bridge; + } + + @Override + public void handle( + String target, + Request baseRequest, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException { + String method = baseRequest.getMethod(); + if ( + ("GET".equals(method) || "HEAD".equals(method)) + && target != null + && target.matches("^/health_check/?$") + ) { + Log.info(method + " <- /health_check"); + baseRequest.setHandled(true); + response.setContentType("text/plain"); + if (bridge.healthCheck()) { + response.setStatus(200); + response.getWriter().println("ok"); + } else { + response.setStatus(500); + response.getWriter().println("failed"); + } + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/Oauth2Filter.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/Oauth2Filter.java new file mode 100644 index 0000000000..bbf2696cd3 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/Oauth2Filter.java @@ -0,0 +1,265 @@ +package uk.ac.ic.wlgitbridge.server; + +import com.google.api.client.auth.oauth2.*; +import com.google.api.client.http.GenericUrl; +import org.apache.commons.codec.binary.Base64; +import org.eclipse.jetty.server.Request; +import uk.ac.ic.wlgitbridge.application.config.Oauth2; +import uk.ac.ic.wlgitbridge.bridge.snapshot.SnapshotApi; +import uk.ac.ic.wlgitbridge.snapshot.base.MissingRepositoryException; +import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocRequest; +import uk.ac.ic.wlgitbridge.util.Instance; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.Util; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.*; + +/** + * Created by winston on 25/10/15. + */ +public class Oauth2Filter implements Filter { + + public static final String ATTRIBUTE_KEY = "oauth2"; + + private final SnapshotApi snapshotApi; + + private final Oauth2 oauth2; + + public Oauth2Filter(SnapshotApi snapshotApi, Oauth2 oauth2) { + this.snapshotApi = snapshotApi; + this.oauth2 = oauth2; + } + + @Override + public void init(FilterConfig filterConfig) {} + + private void sendResponse(ServletResponse servletResponse, int code, List lines) throws IOException { + HttpServletResponse response = ((HttpServletResponse) servletResponse); + response.setContentType("text/plain"); + response.setStatus(code); + PrintWriter w = response.getWriter(); + for (String line : lines) { + w.println(line); + } + w.close(); + return; + } + + /** + * 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 + public void doFilter( + ServletRequest servletRequest, + ServletResponse servletResponse, + FilterChain filterChain + ) throws IOException, ServletException { + String requestUri = ((Request) servletRequest).getRequestURI(); + if (requestUri.startsWith("/project")) { + Log.info("[{}] Invalid request URI", requestUri); + sendResponse(servletResponse,404, Arrays.asList( + "Invalid Project ID (must not have a '/project' prefix)" + )); + return; + } + String project = Util.removeAllSuffixes( + requestUri.split("/")[1], + ".git" + ); + // Reject v1 ids, the request will be rejected by v1 anyway + if (project.matches("^[0-9]+[bcdfghjklmnpqrstvwxyz]{6,12}$") && !project.matches("^[0-9a-f]{24}$")) { + Log.info("[{}] Request for v1 project, refusing", project); + sendResponse(servletResponse, 404, Arrays.asList( + "This project has not yet been moved into the new version", + "of Overleaf. You will need to move it in order to continue working on it.", + "Please visit this project online on www.overleaf.com to do this.", + "", + "You can find the new git remote url by selecting \"Git\" from", + "the left sidebar in the project view.", + "", + "If this is unexpected, please contact us at support@overleaf.com, or", + "see https://www.overleaf.com/help/342 for more information." + )); + return; + } + Log.info("[{}] Checking if auth needed", project); + GetDocRequest doc = new GetDocRequest(project); + doc.request(); + try { + SnapshotApi.getResult( + snapshotApi.getDoc(Optional.empty(), project)); + } catch (ForbiddenException e) { + Log.info("[{}] Auth needed", project); + getAndInjectCredentials( + project, + servletRequest, + servletResponse, + filterChain + ); + return; + } catch (MissingRepositoryException e) { + handleMissingRepository(project, e, (HttpServletResponse) servletResponse); + } + Log.info("[{}] Auth not needed", project); + filterChain.doFilter(servletRequest, servletResponse); + } + + // TODO: this is ridiculous. Check for error cases first, then return/throw + // TODO: also, use an Optional credential, since we treat it as optional + private void getAndInjectCredentials( + String projectName, + ServletRequest servletRequest, + ServletResponse servletResponse, + FilterChain filterChain + ) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + String capturedUsername = "(unknown)"; + + String authHeader = request.getHeader("Authorization"); + if (authHeader != null) { + String clientIp = request.getHeader("X-Forwarded-For"); + if (clientIp == null) { + clientIp = request.getRemoteAddr(); + } + Log.info("[{}] Authorization header present", clientIp); + StringTokenizer st = new StringTokenizer(authHeader); + if (st.hasMoreTokens()) { + String basic = st.nextToken(); + if (basic.equalsIgnoreCase("Basic")) { + try { + String credentials = new String( + Base64.decodeBase64(st.nextToken()), + "UTF-8" + ); + String[] split = credentials.split(":",2); + if (split.length == 2) { + String username = split[0]; + String password = split[1]; + String accessToken = null; + if (username.length() > 0) { + capturedUsername = username; + } + try { + accessToken = new PasswordTokenRequest( + Instance.httpTransport, + Instance.jsonFactory, + new GenericUrl( + oauth2.getOauth2Server() + + "/oauth/token?client_ip=" + + clientIp + ), + username, + password + ).setClientAuthentication( + new ClientParametersAuthentication( + oauth2.getOauth2ClientID(), + oauth2.getOauth2ClientSecret() + ) + ).execute().getAccessToken(); + } catch (TokenResponseException e) { + handleNeedAuthorization(projectName, capturedUsername, e.getStatusCode(), request, response); + return; + } + final Credential cred = new Credential.Builder( + BearerToken.authorizationHeaderAccessMethod( + ) + ).build(); + cred.setAccessToken(accessToken); + servletRequest.setAttribute(ATTRIBUTE_KEY, cred); + + filterChain.doFilter( + servletRequest, + servletResponse + ); + } else { + handleNeedAuthorization(projectName, capturedUsername, 0, request, response); + } + } catch (UnsupportedEncodingException e) { + throw new Error("Couldn't retrieve authentication", e); + } + } + } + } else { + handleNeedAuthorization(projectName, capturedUsername, 0, request, response); + } + } + + @Override + public void destroy() {} + + private void handleNeedAuthorization( + String projectName, + String userName, + int statusCode, + HttpServletRequest servletRequest, + HttpServletResponse servletResponse + ) throws IOException { + Log.info( + "[{}] Unauthorized, User '{}' status={} ip={}", + projectName, + userName, + statusCode, + servletRequest.getRemoteAddr() + ); + HttpServletResponse response = servletResponse; + response.setContentType("text/plain"); + response.setHeader("WWW-Authenticate", "Basic realm=\"Git Bridge\""); + response.setStatus(401); + + PrintWriter w = response.getWriter(); + w.println( + "Please sign in using your email address and Overleaf password." + ); + w.println(); + w.println( + "*Note*: if you sign in to Overleaf using another provider, " + + "such " + ); + w.println( + "as Google or Twitter, you need to set a password " + + "on your Overleaf " + ); + w.println( + "account first. " + + "Please see https://www.overleaf.com/blog/195 for " + ); + w.println("more information."); + w.close(); + } + + private void handleMissingRepository( + String projectName, + MissingRepositoryException e, + HttpServletResponse response + ) throws IOException { + Log.info("[{}] Project missing.", projectName); + + response.setContentType("text/plain"); + + // git special-cases 404 to give "repository '%s' not found", + // rather than displaying the raw status code. + response.setStatus(404); + + PrintWriter w = response.getWriter(); + for (String line : e.getDescriptionLines()) { + w.println(line); + } + w.close(); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/PostbackContents.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/PostbackContents.java new file mode 100644 index 0000000000..3ef85620b0 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/PostbackContents.java @@ -0,0 +1,88 @@ +package uk.ac.ic.wlgitbridge.server; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.SnapshotPostException; +import uk.ac.ic.wlgitbridge.snapshot.base.JSONSource; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.UnexpectedPostbackException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.SnapshotPostExceptionBuilder; +import uk.ac.ic.wlgitbridge.util.Util; + +/** + * Created by Winston on 17/11/14. + */ +public class PostbackContents implements JSONSource { + + private static final String CODE_SUCCESS = "upToDate"; + + private final Bridge bridge; + private final String projectName; + private final String postbackKey; + + private final SnapshotPostExceptionBuilder snapshotPostExceptionBuilder; + + private int versionID; + private SnapshotPostException exception; + + public PostbackContents( + Bridge bridge, + String projectName, + String postbackKey, + String contents + ) { + this.bridge = bridge; + this.projectName = projectName; + this.postbackKey = postbackKey; + snapshotPostExceptionBuilder = new SnapshotPostExceptionBuilder(); + fromJSON(new Gson().fromJson(contents, JsonElement.class)); + } + + @Override + public void fromJSON(JsonElement json) { + JsonObject responseObject = json.getAsJsonObject(); + String code = Util.getCodeFromResponse(responseObject); + setResult(responseObject, code); + } + + public void processPostback() throws UnexpectedPostbackException { + if (exception == null) { + bridge.postbackReceivedSuccessfully( + projectName, + postbackKey, + versionID + ); + } else { + bridge.postbackReceivedWithException( + projectName, + postbackKey, + exception + ); + } + } + + private void setResult(JsonObject responseObject, String code) { + if (code.equals(CODE_SUCCESS)) { + setVersionID(responseObject); + } else { + setException(responseObject, code); + } + } + + private void setVersionID(JsonObject responseObject) { + versionID = responseObject.get("latestVerId").getAsInt(); + } + + private void setException(JsonObject responseObject, String code) { + try { + exception = snapshotPostExceptionBuilder.build( + code, + responseObject + ); + } catch (UnexpectedPostbackException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/PostbackHandler.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/PostbackHandler.java new file mode 100644 index 0000000000..ea874c4c63 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/PostbackHandler.java @@ -0,0 +1,98 @@ +package uk.ac.ic.wlgitbridge.server; + +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.UnexpectedPostbackException; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.Util; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Created by Winston on 16/11/14. + */ +public class PostbackHandler extends AbstractHandler { + + private final Bridge bridge; + + public PostbackHandler(Bridge bridge) { + this.bridge = bridge; + } + + @Override + public void handle( + String target, + Request baseRequest, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException, ServletException { + Log.info( + "PostbackHandler: " + baseRequest.getMethod() + + " <- " + + baseRequest.getHttpURI() + ); + try { + if ( + request.getMethod().equals("POST") + && target.endsWith("postback") + ) { + response.setContentType("application/json"); + String contents = Util.getContentsOfReader(request.getReader()); + String[] parts = target.split("/"); + if (parts.length < 4) { + throw new ServletException(); + } + String projectName = parts[1]; + String postbackKey = parts[2]; + PostbackContents postbackContents = new PostbackContents( + bridge, + projectName, + postbackKey, + contents + ); + JsonObject body = new JsonObject(); + + try { + postbackContents.processPostback(); + } catch (UnexpectedPostbackException e) { + response.setStatus(HttpServletResponse.SC_CONFLICT); + body.add("code", new JsonPrimitive("unexpectedPostback")); + response.getWriter().println(body); + baseRequest.setHandled(true); + return; + } + response.setStatus(HttpServletResponse.SC_OK); + body.add("code", new JsonPrimitive("success")); + response.getWriter().println(body); + baseRequest.setHandled(true); + } + } catch (IOException e) { + Log.warn( + "IOException when handling postback to target: " + target, + e + ); + throw e; + } catch (ServletException e) { + Log.warn( + "ServletException when handling postback to target: " + + target, + e + ); + throw e; + } catch (RuntimeException e) { + Log.warn( + "RuntimeException when handling postback to target: " + + target, + e + ); + throw e; + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/ProductionErrorHandler.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/ProductionErrorHandler.java new file mode 100644 index 0000000000..d4b19e26fe --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/ProductionErrorHandler.java @@ -0,0 +1,21 @@ +package uk.ac.ic.wlgitbridge.server; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.handler.ErrorHandler; +import java.io.IOException; + +public class ProductionErrorHandler extends ErrorHandler { + @Override + public void handle( + String target, + org.eclipse.jetty.server.Request baseRequest, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException { + response.getWriter() + .append("{\"message\":\"HTTP error ") + .append(String.valueOf(response.getStatus())) + .append("\"}"); + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/PrometheusHandler.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/PrometheusHandler.java new file mode 100644 index 0000000000..6c3d6d52a8 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/PrometheusHandler.java @@ -0,0 +1,78 @@ +package uk.ac.ic.wlgitbridge.server; + +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exporter.common.TextFormat; +import io.prometheus.client.hotspot.DefaultExports; + +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.util.Log; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class PrometheusHandler extends AbstractHandler { + + public PrometheusHandler() { + DefaultExports.initialize(); + } + + @Override + public void handle( + String target, + Request baseRequest, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException, ServletException { + String method = baseRequest.getMethod(); + if ( + ("GET".equals(method)) + && target != null + && target.matches("^/metrics/?$") + ) { + Log.info(method + " <- /metrics"); + this.printMetrics(request, response); + baseRequest.setHandled(true); + } + } + + private void printMetrics( + HttpServletRequest request, + HttpServletResponse response + ) throws ServletException, IOException { + response.setStatus(200); + String contentType = TextFormat.chooseContentType(request.getHeader("Accept")); + response.setContentType(contentType); + + Writer writer = new BufferedWriter(response.getWriter()); + + try { + TextFormat.writeFormat(contentType, writer, CollectorRegistry.defaultRegistry.filteredMetricFamilySamples(parse(request))); + writer.flush(); + } finally { + writer.close(); + } + } + + private Set parse(HttpServletRequest req) { + String[] includedParam = req.getParameterValues("name[]"); + if (includedParam == null) { + return Collections.emptySet(); + } else { + return new HashSet(Arrays.asList(includedParam)); + } + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/StatusHandler.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/StatusHandler.java new file mode 100644 index 0000000000..45acfe12f0 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/StatusHandler.java @@ -0,0 +1,46 @@ +package uk.ac.ic.wlgitbridge.server; + +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.ic.wlgitbridge.bridge.Bridge; +import uk.ac.ic.wlgitbridge.util.Log; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; + +public class StatusHandler extends AbstractHandler { + + private final Bridge bridge; + + public StatusHandler(Bridge bridge) { + this.bridge = bridge; + } + + @Override + public void handle( + String target, + Request baseRequest, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException { + String method = baseRequest.getMethod(); + if ( + ("GET".equals(method) || "HEAD".equals(method)) + && target != null + && target.matches("^/status/?$") + ) { + Log.info(method + " <- /status"); + baseRequest.setHandled(true); + response.setContentType("text/plain"); + response.setStatus(200); + response.getWriter().println("ok"); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/ForbiddenException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/ForbiddenException.java new file mode 100644 index 0000000000..726278aaad --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/ForbiddenException.java @@ -0,0 +1,27 @@ +package uk.ac.ic.wlgitbridge.snapshot.base; + +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.git.exception.SnapshotAPIException; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by winston on 25/10/15. + */ +public class ForbiddenException extends SnapshotAPIException { + + @Override + public void fromJSON(JsonElement json) {} + + @Override + public String getMessage() { + return "forbidden"; + } + + @Override + public List getDescriptionLines() { + return Arrays.asList(getMessage()); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/HTTPMethod.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/HTTPMethod.java new file mode 100644 index 0000000000..6fdf488483 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/HTTPMethod.java @@ -0,0 +1,11 @@ +package uk.ac.ic.wlgitbridge.snapshot.base; + +/** + * Created by Winston on 16/11/14. + */ +public enum HTTPMethod { + + POST, + GET + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/JSONSource.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/JSONSource.java new file mode 100644 index 0000000000..e1a13ac5bb --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/JSONSource.java @@ -0,0 +1,12 @@ +package uk.ac.ic.wlgitbridge.snapshot.base; + +import com.google.gson.JsonElement; + +/** + * Created by Winston on 06/11/14. + */ +public interface JSONSource { + + void fromJSON(JsonElement json); + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/MissingRepositoryException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/MissingRepositoryException.java new file mode 100644 index 0000000000..9b0fef28ce --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/MissingRepositoryException.java @@ -0,0 +1,99 @@ +package uk.ac.ic.wlgitbridge.snapshot.base; + +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.git.exception.SnapshotAPIException; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +public class MissingRepositoryException extends SnapshotAPIException { + + public static final List GENERIC_REASON = Arrays.asList( + "This Overleaf project currently has no git access, either because", + "the project does not exist, or because git access is not enabled", + "for the project.", + "", + "If this is unexpected, please contact us at support@overleaf.com, or", + "see https://www.overleaf.com/help/342 for more information." + ); + + static List buildDeprecatedMessage(String newUrl) { + if (newUrl == null) { + return Arrays.asList( + "This project has not yet been moved into the new version of Overleaf. You will", + "need to move it in order to continue working on it. Please visit this project", + "online on www.overleaf.com to do this.", + "", + "After migrating this project to the new version of Overleaf, you will be", + "prompted to update your git remote to the project's new identifier.", + "", + "If this is unexpected, please contact us at support@overleaf.com, or", + "see https://www.overleaf.com/help/342 for more information." + ); + } else { + return Arrays.asList( + "This project has not yet been moved into the new version of Overleaf. You will", + "need to move it in order to continue working on it. Please visit this project", + "online to do this:", + "", + " " + newUrl, + "", + "After migrating this project to the new version of Overleaf, you will be", + "prompted to update your git remote to the project's new identifier.", + "", + "If this is unexpected, please contact us at support@overleaf.com, or", + "see https://www.overleaf.com/help/342 for more information." + ); + } + + } + + static List buildExportedToV2Message(String remoteUrl) { + if (remoteUrl == null) { + return Arrays.asList( + "This Overleaf project has been moved to Overleaf v2 and cannot be used with git at this time.", + "", + "If this error persists, please contact us at support@overleaf.com, or", + "see https://www.overleaf.com/help/342 for more information." + ); + } else { + return Arrays.asList( + "This Overleaf project has been moved to Overleaf v2 and has a new identifier.", + "Please update your remote to:", + "", + " " + remoteUrl, + "", + "Assuming you are using the default \"origin\" remote, the following commands", + "will change the remote for you:", + "", + " git remote set-url origin " + remoteUrl, + "", + "If this does not work, please contact us at support@overleaf.com, or", + "see https://www.overleaf.com/help/342 for more information." + ); + } + } + + private List descriptionLines; + + public MissingRepositoryException() { this.descriptionLines = GENERIC_REASON; } + + public MissingRepositoryException(List descriptionLines) { + this.descriptionLines = descriptionLines; + } + + @Override + public void fromJSON(JsonElement json) {} + + @Override + public String getMessage() { + return "no git access"; + } + + @Override + public List getDescriptionLines() { + return this.descriptionLines; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/Request.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/Request.java new file mode 100644 index 0000000000..3cb3e51816 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/Request.java @@ -0,0 +1,204 @@ +package uk.ac.ic.wlgitbridge.snapshot.base; + +import com.google.api.client.http.*; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.asynchttpclient.AsyncHttpClient; +import static org.asynchttpclient.Dsl.*; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.util.Instance; +import uk.ac.ic.wlgitbridge.util.Log; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.*; + +/** + * Created by Winston on 06/11/14. + */ +public abstract class Request { + + public static final AsyncHttpClient httpClient = asyncHttpClient(); + + private static final Executor executor = Executors.newCachedThreadPool(); + + private final String url; + + private Future future; + + public Request(String url) { + this.url = url; + } + + public CompletableFuture request() { + switch (httpMethod()) { + case GET: + performGetRequest(); + break; + case POST: + performPostRequest(); + break; + default: + break; + } + CompletableFuture ret = new CompletableFuture<>(); + executor.execute(() -> { + try { + ret.complete(getResult()); + } catch (Throwable t) { + ret.completeExceptionally(t); + } + }); + return ret; + } + + private T getResult() throws MissingRepositoryException, FailedConnectionException, ForbiddenException { + try { + HttpResponse response = future.get(); + Log.info( + "{} {} ({}B) -> " + url, + response.getStatusCode(), + response.getStatusMessage(), + response.getHeaders().getContentLength() + ); + JsonElement json = Instance.gson.fromJson( + response.parseAsString(), + JsonElement.class + ); + return parseResponse(json); + } catch (InterruptedException e) { + throw new FailedConnectionException(); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof HttpResponseException) { + HttpResponseException httpCause = (HttpResponseException) cause; + int sc = httpCause.getStatusCode(); + if (sc == HttpServletResponse.SC_UNAUTHORIZED || sc == HttpServletResponse.SC_FORBIDDEN) { // 401, 403 + throw new ForbiddenException(); + } else if (sc == HttpServletResponse.SC_CONFLICT) { // 409 + try { + JsonObject json = Instance.gson.fromJson(httpCause.getContent(), JsonObject.class); + String code = json.get("code").getAsString(); + if ("projectHasDotGit".equals(code)) { + throw new MissingRepositoryException(Arrays.asList( + "This project contains a '.git' entity at the top level, indicating that it is", + "already a git repository. The Overleaf git-bridge cannot work with this project", + "due to a known problem with handling these '.git' folders.", + "", + "We recommend removing the .git folder before trying again.", + "", + "If this is unexpected, please contact us at support@overleaf.com, or", + "see https://www.overleaf.com/help/342 for more information." + )); + } else { + throw new MissingRepositoryException(Arrays.asList("Conflict: 409")); + } + } catch (IllegalStateException + | ClassCastException + | NullPointerException _e) { // json parse errors + throw new MissingRepositoryException(Arrays.asList("Conflict: 409")); + } + } else if (sc == HttpServletResponse.SC_NOT_FOUND) { // 404 + try { + JsonObject json = Instance.gson.fromJson(httpCause.getContent(), JsonObject.class); + String message = json.get("message").getAsString(); + String newRemote; + if (json.has("newRemote")) { + newRemote = json.get("newRemote").getAsString(); + } else { + newRemote = null; + } + + if ("Exported to v2".equals(message)) { + throw new MissingRepositoryException( + MissingRepositoryException.buildExportedToV2Message(newRemote) + ); + } else if ("Overleaf v1 is Deprecated".equals(message)) { + String newUrl; + if (json.has("newUrl")) { + newUrl = json.get("newUrl").getAsString(); + } else { + newUrl = null; + } + throw new MissingRepositoryException( + MissingRepositoryException.buildDeprecatedMessage(newUrl) + ); + } + } catch (IllegalStateException + | ClassCastException + | NullPointerException _) { + // disregard any errors that arose while handling the JSON + } + + throw new MissingRepositoryException(); + } else if (sc >= 400 && sc < 500) { + throw new MissingRepositoryException(MissingRepositoryException.GENERIC_REASON); + } + throw new FailedConnectionException(cause); + } else { + throw new FailedConnectionException(cause); + } + } catch (IOException e) { + Log.error("Failed to parse JSON.", e); + throw new FailedConnectionException(); + } + } + + protected abstract HTTPMethod httpMethod(); + + protected void onBeforeRequest(HttpRequest request) throws IOException { + + } + + protected abstract + T parseResponse(JsonElement json) throws FailedConnectionException; + + protected String getPostBody() { + return null; + } + + private void performGetRequest() { + Log.info("GET -> " + url); + try { + HttpRequest request = Instance.httpRequestFactory.buildGetRequest( + new GenericUrl(url) + ); + setTimeouts(request); + request(request); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + private void performPostRequest() { + Log.info("POST -> " + url); + try { + HttpRequest request = Instance.httpRequestFactory.buildPostRequest( + new GenericUrl(url), + new ByteArrayContent( + "application/json", + getPostBody().getBytes() + ) + ); + setTimeouts(request); + request(request); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + private void request(HttpRequest request) throws IOException { + onBeforeRequest(request); + future = request.executeAsync(); + } + + private void setTimeouts(HttpRequest request) { + // timeouts are 20s by default + int threeMinutesInMs = 1000 * 60 * 3; + request.setReadTimeout(threeMinutesInMs); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/Result.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/Result.java new file mode 100644 index 0000000000..05dc4c8af4 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/Result.java @@ -0,0 +1,36 @@ +package uk.ac.ic.wlgitbridge.snapshot.base; + +import com.google.gson.JsonElement; + +/** + * Created by Winston on 06/11/14. + */ +public abstract class Result implements JSONSource { + + private JsonElement json; + private Request request; + + public Result(Request request, JsonElement json) { + this.request = request; + this.json = json; + fromJSON(json); + } + + protected Result() { + } + + public Request getRequest() { + return request; + } + + public abstract JsonElement toJson(); + + @Override + public String toString() { + if (json == null) { + return "result"; + } + return json.toString(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/SnapshotAPIRequest.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/SnapshotAPIRequest.java new file mode 100644 index 0000000000..8d318653c2 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/base/SnapshotAPIRequest.java @@ -0,0 +1,43 @@ +package uk.ac.ic.wlgitbridge.snapshot.base; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.http.BasicAuthentication; +import com.google.api.client.http.HttpRequest; + +import java.io.IOException; + +/** + * Created by Winston on 06/11/14. + */ +public abstract class SnapshotAPIRequest extends Request { + + private static String BASE_URL; + + private final Credential oauth2; + + public SnapshotAPIRequest( + String projectName, + String apiCall, + Credential oauth2 + ) { + super(BASE_URL + projectName + apiCall); + this.oauth2 = oauth2; + } + + @Override + protected void onBeforeRequest( + HttpRequest request + ) { + if (oauth2 != null) { + request.setInterceptor(request1 -> { + oauth2.intercept(request1); + }); + } + } + + /* baseURL ends with / */ + public static void setBaseURL(String baseURL) { + BASE_URL = baseURL + "docs/"; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/exception/FailedConnectionException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/exception/FailedConnectionException.java new file mode 100644 index 0000000000..c81b8abade --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/exception/FailedConnectionException.java @@ -0,0 +1,20 @@ +package uk.ac.ic.wlgitbridge.snapshot.exception; + +import org.eclipse.jgit.transport.ServiceMayNotContinueException; +import uk.ac.ic.wlgitbridge.util.Util; + +/** + * Created by Winston on 08/11/14. + */ +public class FailedConnectionException extends ServiceMayNotContinueException { + + public FailedConnectionException() { + super(Util.getServiceName() + + " server not available. Please try again later."); + } + + public FailedConnectionException(Throwable cause) { + super(cause); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getdoc/GetDocRequest.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getdoc/GetDocRequest.java new file mode 100644 index 0000000000..0d654b2925 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getdoc/GetDocRequest.java @@ -0,0 +1,45 @@ +package uk.ac.ic.wlgitbridge.snapshot.getdoc; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.http.HttpRequest; +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.snapshot.base.HTTPMethod; +import uk.ac.ic.wlgitbridge.snapshot.base.SnapshotAPIRequest; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.util.Log; + +import java.io.IOException; + +/** + * Created by Winston on 06/11/14. + */ +public class GetDocRequest extends SnapshotAPIRequest { + + public static final String API_CALL = ""; + + public GetDocRequest(Credential oauth2, String projectName) { + super(projectName, API_CALL, oauth2); + Log.info( + "GetDocRequest({}, {})", + "oauth2: ", + "projectName: " + projectName + ); + } + + public GetDocRequest(String projectName) { + this(null, projectName); + } + + @Override + protected HTTPMethod httpMethod() { + return HTTPMethod.GET; + } + + @Override + protected GetDocResult parseResponse( + JsonElement json + ) throws FailedConnectionException { + return new GetDocResult(this, json); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getdoc/GetDocResult.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getdoc/GetDocResult.java new file mode 100644 index 0000000000..bfb659769a --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getdoc/GetDocResult.java @@ -0,0 +1,149 @@ +package uk.ac.ic.wlgitbridge.snapshot.getdoc; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; +import uk.ac.ic.wlgitbridge.git.exception.SnapshotAPIException; +import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException; +import uk.ac.ic.wlgitbridge.snapshot.base.Request; +import uk.ac.ic.wlgitbridge.snapshot.base.Result; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.exception.InvalidProjectException; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.WLUser; +import uk.ac.ic.wlgitbridge.util.Log; + +/** + * Created by Winston on 06/11/14. + */ +public class GetDocResult extends Result { + + private int error; + private int versionID; + private String migratedFromID; + private String createdAt; + private WLUser user; + + private SnapshotAPIException exception; + private ForbiddenException forbidden; + + public GetDocResult( + Request request, + JsonElement json + ) throws FailedConnectionException { + super(request, json); + } + + public GetDocResult( + JsonElement error, + int versionID, + String createdAt, + String email, + String name, + String migratedFromID + ) { + if (error == null) { + this.error = -1; + } else { + this.error = error.getAsInt(); + } + this.versionID = versionID; + this.createdAt = createdAt; + this.user = new WLUser(name, email); + this.migratedFromID = migratedFromID; + } + + @Override + public JsonElement toJson() { + JsonObject jsonThis = new JsonObject(); + if (error == -1) { + jsonThis.addProperty("latestVerId", versionID); + jsonThis.addProperty("latestVerAt", createdAt); + JsonObject latestVerBy = new JsonObject(); + latestVerBy.addProperty("email", getEmail()); + latestVerBy.addProperty("name", getName()); + jsonThis.add("latestVerBy", latestVerBy); + if (migratedFromID != null) { + jsonThis.addProperty("migratedFromId", migratedFromID); + } + } else { + jsonThis.addProperty("status", error); + String message; + if (error == 403) { + message = "Forbidden"; + } else { + message = "Not Found"; + } + jsonThis.addProperty("message", message); + } + return jsonThis; + } + + @Override + public void fromJSON(JsonElement json) { + Log.info("GetDocResult: " + json); + JsonObject jsonObject = json.getAsJsonObject(); + if (jsonObject.has("status")) { + switch (jsonObject.get("status").getAsInt()) { + case 401: + case 403: + forbidden = new ForbiddenException(); + break; + case 404: + exception = new InvalidProjectException(); + break; + default: + throw new IllegalArgumentException( + "unknown get doc error code" + ); + } + } else { + versionID = jsonObject.get("latestVerId").getAsInt(); + // Handle edge-case for projects with no changes, that were imported + // to v2. In which case `latestVerAt` will not be present. + // See: https://github.com/overleaf/writelatex-git-bridge/pull/50 + if (jsonObject.has("latestVerAt")) { + createdAt = jsonObject.get("latestVerAt").getAsString(); + } else { + createdAt = null; + } + if (jsonObject.has("migratedFromId")) { + migratedFromID = jsonObject.get("migratedFromId").getAsString(); + } else { + migratedFromID = null; + } + String name = null; + String email = null; + JsonElement latestVerBy = jsonObject.get("latestVerBy"); + + if (latestVerBy.isJsonObject()) { + JsonObject userObject = latestVerBy.getAsJsonObject(); + name = userObject.get("name").getAsString(); + email = userObject.get("email").getAsString(); + } + + user = new WLUser(name, email); + } + } + + public int getVersionID() throws GitUserException { + if (exception != null) { + throw exception; + } + return versionID; + } + + public String getCreatedAt() { + return createdAt; + } + + public String getName() { + return user.getName(); + } + + public String getEmail() { + return user.getEmail(); + } + + public String getMigratedFromID() { return migratedFromID; } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getdoc/exception/InvalidProjectException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getdoc/exception/InvalidProjectException.java new file mode 100644 index 0000000000..a9d4bd1c6e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getdoc/exception/InvalidProjectException.java @@ -0,0 +1,41 @@ +package uk.ac.ic.wlgitbridge.snapshot.getdoc.exception; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.git.exception.SnapshotAPIException; + +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Winston on 08/11/14. + */ +public class InvalidProjectException extends SnapshotAPIException { + + private List errors; + + public InvalidProjectException() { + super(); + errors = new LinkedList(); + } + + @Override + public String getMessage() { + return "invalid project"; + } + + @Override + public List getDescriptionLines() { + return errors; + } + + @Override + public void fromJSON(JsonElement json) { + errors = new LinkedList(); + JsonArray errors = + json.getAsJsonObject().get("errors").getAsJsonArray(); + for (JsonElement error : errors) { + this.errors.add(error.getAsString()); + } + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/GetForVersionRequest.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/GetForVersionRequest.java new file mode 100644 index 0000000000..6ed6638c20 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/GetForVersionRequest.java @@ -0,0 +1,51 @@ +package uk.ac.ic.wlgitbridge.snapshot.getforversion; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.snapshot.base.HTTPMethod; +import uk.ac.ic.wlgitbridge.snapshot.base.SnapshotAPIRequest; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.util.Log; + +/** + * Created by Winston on 06/11/14. + */ +public class GetForVersionRequest + extends SnapshotAPIRequest { + + public static final String API_CALL = "/snapshots"; + + private int versionID; + + public GetForVersionRequest( + Credential oauth2, + String projectName, + int versionID + ) { + super(projectName, API_CALL + "/" + versionID, oauth2); + this.versionID = versionID; + Log.info( + "GetForVersionRequest({}, {}, {})", + "oauth2: ", + "projectName: " + projectName, + "versionID: " + versionID + ); + } + + @Override + protected HTTPMethod httpMethod() { + return HTTPMethod.GET; + } + + @Override + protected GetForVersionResult parseResponse( + JsonElement json + ) throws FailedConnectionException { + return new GetForVersionResult(this, json); + } + + public int getVersionID() { + return versionID; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/GetForVersionResult.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/GetForVersionResult.java new file mode 100644 index 0000000000..c5c290ea57 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/GetForVersionResult.java @@ -0,0 +1,38 @@ +package uk.ac.ic.wlgitbridge.snapshot.getforversion; + +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.snapshot.base.Request; +import uk.ac.ic.wlgitbridge.snapshot.base.Result; +import uk.ac.ic.wlgitbridge.util.Log; + +/** + * Created by Winston on 06/11/14. + */ +public class GetForVersionResult extends Result { + + private SnapshotData snapshotData; + + public GetForVersionResult(Request request, JsonElement json) { + super(request, json); + } + + public GetForVersionResult(SnapshotData snapshotData) { + this.snapshotData = snapshotData; + } + + @Override + public JsonElement toJson() { + return snapshotData.toJson(); + } + + @Override + public void fromJSON(JsonElement json) { + snapshotData = new SnapshotData(json); + Log.info("GetForVersionResult({})", snapshotData); + } + + public SnapshotData getSnapshotData() { + return snapshotData; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/SnapshotAttachment.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/SnapshotAttachment.java new file mode 100644 index 0000000000..bc65c4171c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/SnapshotAttachment.java @@ -0,0 +1,54 @@ +package uk.ac.ic.wlgitbridge.snapshot.getforversion; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import uk.ac.ic.wlgitbridge.snapshot.base.JSONSource; + +/** + * Created by Winston on 06/11/14. + */ +public class SnapshotAttachment implements JSONSource { + + private String url; + private String path; + + public SnapshotAttachment(JsonElement json) { + fromJSON(json); + } + + @Override + public String toString() { + return "SnapshotAttachment(url: " + url + ", path: " + path + ")"; + } + + @Override + public void fromJSON(JsonElement json) { + JsonArray jsonArray = json.getAsJsonArray(); + url = jsonArray.get(0).getAsString(); + path = jsonArray.get(1).getAsString(); + } + + public String getUrl() { + return url; + } + + public String getPath() { + return path; + } + + /* For the Mock Snapshot server */ + + public SnapshotAttachment(String url, String path) { + this.url = url; + this.path = path; + } + + public JsonElement toJson() { + JsonArray jsonThis = new JsonArray(); + jsonThis.add(new JsonPrimitive(url)); + jsonThis.add(new JsonPrimitive(getPath())); + return jsonThis; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/SnapshotData.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/SnapshotData.java new file mode 100644 index 0000000000..a382f16d09 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/SnapshotData.java @@ -0,0 +1,86 @@ +package uk.ac.ic.wlgitbridge.snapshot.getforversion; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.snapshot.base.JSONSource; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Winston on 06/11/14. + */ +public class SnapshotData implements JSONSource { + + public static final String JSON_KEY_SRCS = "srcs"; + public static final String JSON_KEY_ATTS = "atts"; + + private List srcs; + private List atts; + + public SnapshotData(JsonElement json) { + srcs = new ArrayList<>(); + atts = new ArrayList<>(); + fromJSON(json); + } + + public SnapshotData( + List srcs, + List atts + ) { + this.srcs = srcs; + this.atts = atts; + } + + @Override + public String toString() { + return "SnapshotData(srcs: " + srcs + ", atts: " + atts + ")"; + } + + public JsonElement toJson() { + JsonObject jsonThis = new JsonObject(); + JsonArray jsonSrcs = new JsonArray(); + for (SnapshotFile src : srcs) { + jsonSrcs.add(src.toJson()); + } + jsonThis.add("srcs", jsonSrcs); + JsonArray jsonAtts = new JsonArray(); + for (SnapshotAttachment att : atts) { + jsonAtts.add(att.toJson()); + } + jsonThis.add("atts", jsonAtts); + return jsonThis; + } + + @Override + public void fromJSON(JsonElement json) { + populateSrcs( + json.getAsJsonObject().get(JSON_KEY_SRCS).getAsJsonArray() + ); + populateAtts( + json.getAsJsonObject().get(JSON_KEY_ATTS).getAsJsonArray() + ); + } + + private void populateSrcs(JsonArray jsonArray) { + for (JsonElement json : jsonArray) { + srcs.add(new SnapshotFile(json)); + } + } + + private void populateAtts(JsonArray jsonArray) { + for (JsonElement json : jsonArray) { + atts.add(new SnapshotAttachment(json)); + } + } + + public List getSrcs() { + return srcs; + } + + public List getAtts() { + return atts; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/SnapshotFile.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/SnapshotFile.java new file mode 100644 index 0000000000..a74ec53eb3 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getforversion/SnapshotFile.java @@ -0,0 +1,70 @@ +package uk.ac.ic.wlgitbridge.snapshot.getforversion; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import uk.ac.ic.wlgitbridge.data.filestore.RawFile; +import uk.ac.ic.wlgitbridge.snapshot.base.JSONSource; + +/** + * Created by Winston on 06/11/14. + */ +public class SnapshotFile extends RawFile implements JSONSource { + + private String path; + private byte[] contents; + + public SnapshotFile(JsonElement json) { + fromJSON(json); + } + + @Override + public String toString() { + return "SnapshotFile(path: " + + path + + ", contents: byte[" + + contents.length + + "])"; + } + + @Override + public void fromJSON(JsonElement json) { + JsonArray jsonArray = json.getAsJsonArray(); + contents = jsonArray.get(0).getAsString().getBytes(); + path = jsonArray.get(1).getAsString(); + } + + @Override + public String getPath() { + return path; + } + + @Override + public byte[] getContents() { + return contents; + } + + @Override + public long size() { + return contents.length; + } + + /* Mock server */ + + public SnapshotFile(String contents, String path) { + this.path = path; + if (contents != null) { + this.contents = contents.getBytes(); + } else { + this.contents = new byte[0]; + } + } + + public JsonElement toJson() { + JsonArray jsonThis = new JsonArray(); + jsonThis.add(new JsonPrimitive(new String(contents))); + jsonThis.add(new JsonPrimitive(path)); + return jsonThis; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/GetSavedVersRequest.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/GetSavedVersRequest.java new file mode 100644 index 0000000000..fdd63feaee --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/GetSavedVersRequest.java @@ -0,0 +1,39 @@ +package uk.ac.ic.wlgitbridge.snapshot.getsavedvers; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.snapshot.base.SnapshotAPIRequest; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.snapshot.base.HTTPMethod; +import uk.ac.ic.wlgitbridge.util.Log; + +/** + * Created by Winston on 06/11/14. + */ +public class GetSavedVersRequest + extends SnapshotAPIRequest { + + public static final String API_CALL = "/saved_vers"; + + public GetSavedVersRequest(Credential oauth2, String projectName) { + super(projectName, API_CALL, oauth2); + Log.info( + "GetSavedVersRequest({}, {})", + "oauth2: ", + "projectName: " + projectName + ); + } + + @Override + protected HTTPMethod httpMethod() { + return HTTPMethod.GET; + } + + @Override + protected GetSavedVersResult parseResponse( + JsonElement json + ) throws FailedConnectionException { + return new GetSavedVersResult(this, json); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/GetSavedVersResult.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/GetSavedVersResult.java new file mode 100644 index 0000000000..b5b59f0ad5 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/GetSavedVersResult.java @@ -0,0 +1,69 @@ +package uk.ac.ic.wlgitbridge.snapshot.getsavedvers; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.snapshot.base.Request; +import uk.ac.ic.wlgitbridge.snapshot.base.Result; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Winston on 06/11/14. + */ +public class GetSavedVersResult extends Result { + + private List savedVers; + + public GetSavedVersResult( + Request request, + JsonElement json + ) throws FailedConnectionException { + super(request, json); + } + + public GetSavedVersResult(List savedVers) { + this.savedVers = savedVers; + } + + @Override + public JsonElement toJson() { + JsonArray jsonThis = new JsonArray(); + for (SnapshotInfo savedVer : savedVers) { + JsonObject jsonSavedVer = new JsonObject(); + jsonSavedVer.addProperty("versionId", savedVer.getVersionId()); + jsonSavedVer.addProperty("comment", savedVer.getComment()); + WLUser user = savedVer.getUser(); + JsonObject jsonUser = new JsonObject(); + jsonUser.addProperty("email", user.getEmail()); + jsonUser.addProperty("name", user.getName()); + jsonSavedVer.add("user", jsonUser); + jsonSavedVer.addProperty("createdAt", savedVer.getCreatedAt()); + jsonThis.add(jsonSavedVer); + } + return jsonThis; + } + + @Override + public void fromJSON(JsonElement json) { + Log.info("GetSavedVersResult({})", json); + savedVers = new ArrayList<>(); + for (JsonElement elem : json.getAsJsonArray()) { + savedVers.add( + new Gson().fromJson( + elem.getAsJsonObject(), + SnapshotInfo.class + ) + ); + } + } + + public List getSavedVers() { + return savedVers; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/SnapshotInfo.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/SnapshotInfo.java new file mode 100644 index 0000000000..3fe1a2e36c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/SnapshotInfo.java @@ -0,0 +1,73 @@ +package uk.ac.ic.wlgitbridge.snapshot.getsavedvers; + +import uk.ac.ic.wlgitbridge.util.Util; + +/** + * Created by Winston on 06/11/14. + */ +public class SnapshotInfo implements Comparable { + + private int versionId; + private String comment; + private WLUser user; + private String createdAt; + + public SnapshotInfo( + int versionID, + String createdAt, + String name, + String email + ) { + this( + versionID, + "Update on " + Util.getServiceName() + ".", + email, + name, + createdAt + ); + } + + public SnapshotInfo( + int versionID, + String comment, + String email, + String name, + String createdAt + ) { + versionId = versionID; + this.comment = comment; + user = new WLUser(name, email); + this.createdAt = createdAt; + } + + public int getVersionId() { + return versionId; + } + + public String getComment() { + return comment; + } + + public WLUser getUser() { + return user != null ? user : new WLUser(); + } + + public String getCreatedAt() { + return createdAt; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SnapshotInfo)) { + return false; + } + SnapshotInfo that = (SnapshotInfo) obj; + return versionId == that.versionId; + } + + @Override + public int compareTo(SnapshotInfo o) { + return Integer.compare(versionId, o.versionId); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/WLUser.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/WLUser.java new file mode 100644 index 0000000000..3ff2742416 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/getsavedvers/WLUser.java @@ -0,0 +1,41 @@ +package uk.ac.ic.wlgitbridge.snapshot.getsavedvers; + +import uk.ac.ic.wlgitbridge.util.Util; + +/** + * Created by Winston on 06/11/14. + */ +public class WLUser { + + private final String name; + private final String email; + + public WLUser() { + this(null, null); + } + + public WLUser(String name, String email) { + if (name != null && email != null) { + this.name = name; + this.email = email; + } else { + this.name = "Anonymous"; + this.email = "anonymous@" + + Util.getServiceName().toLowerCase() + + ".com"; + } + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + @Override + public String toString() { + return "(" + name + ", " + email + ")"; + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PostbackManager.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PostbackManager.java new file mode 100644 index 0000000000..b16427b5cf --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PostbackManager.java @@ -0,0 +1,97 @@ +package uk.ac.ic.wlgitbridge.snapshot.push; + +import com.google.common.base.Preconditions; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.InvalidPostbackKeyException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.SnapshotPostException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.UnexpectedPostbackException; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by Winston on 17/11/14. + */ +public class PostbackManager { + + private final SecureRandom random; + final Map postbackContentsTable; + + PostbackManager(SecureRandom random) { + this.random = random; + postbackContentsTable = Collections.synchronizedMap( + new HashMap() + ); + } + + public PostbackManager() { + this(new SecureRandom()); + } + + public int waitForVersionIdOrThrow( + String projectName + ) throws SnapshotPostException { + try { + PostbackPromise postbackPromise = + postbackContentsTable.get(projectName); + Preconditions.checkNotNull(postbackPromise); + return postbackPromise.waitForPostback(); + } finally { + postbackContentsTable.remove(projectName); + } + } + + public void postVersionIDForProject( + String projectName, + int versionID, + String postbackKey + ) throws UnexpectedPostbackException { + getPostbackForProject( + projectName + ).receivedVersionID(versionID, postbackKey); + } + + public void postExceptionForProject( + String projectName, + SnapshotPostException exception, + String postbackKey + ) throws UnexpectedPostbackException { + getPostbackForProject( + projectName + ).receivedException(exception, postbackKey); + } + + private PostbackPromise getPostbackForProject(String projectName) + throws UnexpectedPostbackException { + PostbackPromise contents = postbackContentsTable.get(projectName); + if (contents == null) { + throw new UnexpectedPostbackException(); + } + return contents; + } + + public String makeKeyForProject(String projectName) { + String key = System.currentTimeMillis() + randomString(); + PostbackPromise contents = new PostbackPromise(key); + postbackContentsTable.put(projectName, contents); + return key; + } + + public void checkPostbackKey(String projectName, String postbackKey) + throws InvalidPostbackKeyException { + PostbackPromise postbackPromise = postbackContentsTable.get(projectName); + if (postbackPromise == null) { + // project not found; can't check key + throw new InvalidPostbackKeyException(); + } else { + postbackPromise.checkPostbackKey(postbackKey); + } + } + + private String randomString() { + return new BigInteger(130, random).toString(32); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PostbackPromise.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PostbackPromise.java new file mode 100644 index 0000000000..24587a5c5c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PostbackPromise.java @@ -0,0 +1,93 @@ +package uk.ac.ic.wlgitbridge.snapshot.push; + +import uk.ac.ic.wlgitbridge.snapshot.push.exception.InternalErrorException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.PostbackTimeoutException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.SnapshotPostException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.InvalidPostbackKeyException; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by Winston on 17/11/14. + */ +public class PostbackPromise { + + private static int TIMEOUT_SECONDS = 60 * 6; + + private final String postbackKey; + private final ReentrantLock lock; + private final Condition cond; + + private boolean received; + private int versionID; + private SnapshotPostException exception; + + public PostbackPromise(String postbackKey) { + this.postbackKey = postbackKey; + lock = new ReentrantLock(); + cond = lock.newCondition(); + received = false; + exception = null; + } + + public int waitForPostback() throws SnapshotPostException { + lock.lock(); + try { + while (!received) { + try { + if (!cond.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new PostbackTimeoutException(TIMEOUT_SECONDS); + } + } catch (InterruptedException e) { + throw new InternalErrorException(); + } + } + if (exception != null) { + throw exception; + } + return versionID; + } finally { + lock.unlock(); + } + } + + public void receivedVersionID(int versionID, String postbackKey) { + lock.lock(); + try { + if (postbackKey.equals(this.postbackKey)) { + this.versionID = versionID; + received = true; + cond.signalAll(); + } + } finally { + lock.unlock(); + } + } + + public void receivedException( + SnapshotPostException exception, + String postbackKey + ) { + lock.lock(); + try { + if (postbackKey.equals(this.postbackKey)) { + this.exception = exception; + received = true; + cond.signalAll(); + } + } finally { + lock.unlock(); + } + } + + public void checkPostbackKey( + String postbackKey + ) throws InvalidPostbackKeyException { + if (!postbackKey.equals(this.postbackKey)) { + throw new InvalidPostbackKeyException(); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PushRequest.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PushRequest.java new file mode 100644 index 0000000000..c9f2c0c3b2 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PushRequest.java @@ -0,0 +1,54 @@ +package uk.ac.ic.wlgitbridge.snapshot.push; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.data.CandidateSnapshot; +import uk.ac.ic.wlgitbridge.snapshot.base.HTTPMethod; +import uk.ac.ic.wlgitbridge.snapshot.base.SnapshotAPIRequest; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.util.Log; + +/** + * Created by Winston on 16/11/14. + */ +public class PushRequest extends SnapshotAPIRequest { + + private static final String API_CALL = "/snapshots"; + + private final CandidateSnapshot candidateSnapshot; + private final String postbackKey; + + public PushRequest( + Credential oauth2, + CandidateSnapshot candidateSnapshot, + String postbackKey + ) { + super(candidateSnapshot.getProjectName(), API_CALL, oauth2); + this.candidateSnapshot = candidateSnapshot; + this.postbackKey = postbackKey; + Log.info( + "PushRequest({}, {}, {})", + "oauth2: ", + "candidateSnapshot: " + candidateSnapshot, + "postbackKey: " + postbackKey + ); + } + + @Override + protected HTTPMethod httpMethod() { + return HTTPMethod.POST; + } + + @Override + protected String getPostBody() { + return candidateSnapshot.getJsonRepresentation(postbackKey).toString(); + } + + @Override + protected PushResult parseResponse( + JsonElement json + ) throws FailedConnectionException { + return new PushResult(this, json); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PushResult.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PushResult.java new file mode 100644 index 0000000000..32ff8819f1 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/PushResult.java @@ -0,0 +1,48 @@ +package uk.ac.ic.wlgitbridge.snapshot.push; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.snapshot.base.Result; +import uk.ac.ic.wlgitbridge.snapshot.base.Request; +import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException; +import uk.ac.ic.wlgitbridge.util.Log; +import uk.ac.ic.wlgitbridge.util.Util; + +/** + * Created by Winston on 16/11/14. + */ +public class PushResult extends Result { + + private boolean success; + + public PushResult( + Request request, + JsonElement json + ) throws FailedConnectionException { + super(request, json); + } + + @Override + public JsonElement toJson() { + return null; + } + + public boolean wasSuccessful() { + return success; + } + + @Override + public void fromJSON(JsonElement json) { + Log.info("PushResult({})", json); + JsonObject responseObject = json.getAsJsonObject(); + String code = Util.getCodeFromResponse(responseObject); + + if (code.equals("accepted")) { + success = true; + } else if (code.equals("outOfDate")) { + success = false; + } else { + throw new RuntimeException(); + } + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InternalErrorException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InternalErrorException.java new file mode 100644 index 0000000000..3d3dbd0f23 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InternalErrorException.java @@ -0,0 +1,30 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.util.Util; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by Winston on 09/01/15. + */ +public class InternalErrorException extends SevereSnapshotPostException { + + @Override + public String getMessage() { + return "internal error"; + } + + @Override + public List getDescriptionLines() { + return Arrays.asList( + "There was an internal error with the Git server.", + "Please contact " + Util.getServiceName() + "." + ); + } + + @Override + public void fromJSON(JsonElement json) {} + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InvalidFilesException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InvalidFilesException.java new file mode 100644 index 0000000000..12d3f5ed9e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InvalidFilesException.java @@ -0,0 +1,74 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.util.Util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Winston on 16/11/14. + */ +public class InvalidFilesException extends SnapshotPostException { + + private List descriptionLines; + + public InvalidFilesException(JsonObject json) { + super(json); + } + + @Override + public String getMessage() { + return "invalid files"; + } + + @Override + public List getDescriptionLines() { + return descriptionLines; + } + + @Override + public void fromJSON(JsonElement json) { + descriptionLines = new ArrayList<>(); + JsonArray errors = + json.getAsJsonObject().get("errors").getAsJsonArray(); + descriptionLines.add( + "You have " + + errors.size() + + " invalid files in your " + + Util.getServiceName() + + " project:" + ); + for (JsonElement error : errors) { + descriptionLines.add(describeError(error.getAsJsonObject())); + } + } + + private String describeError(JsonObject jsonObject) { + return jsonObject.get("file").getAsString() + + " (" + describeFile(jsonObject) + ")"; + } + + private String describeFile(JsonObject file) { + if (file.has("cleanFile")) { + return describeCleanFile(file.get("cleanFile").getAsString()); + } else { + return describeErrorState(file.get("state").getAsString()); + } + } + + private String describeCleanFile(String cleanFile) { + return "rename to: " + cleanFile; + } + + private String describeErrorState(String state) { + if (state.equals("disallowed")) { + return "invalid file extension"; + } else { + return "error"; + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InvalidPostbackKeyException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InvalidPostbackKeyException.java new file mode 100644 index 0000000000..49e20097ec --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InvalidPostbackKeyException.java @@ -0,0 +1,6 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +/** + * Created by Winston on 04/12/14. + */ +public class InvalidPostbackKeyException extends Exception {} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InvalidProjectException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InvalidProjectException.java new file mode 100644 index 0000000000..5a36d54bfb --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/InvalidProjectException.java @@ -0,0 +1,40 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; + +import java.util.LinkedList; +import java.util.List; + +/** + * Created by Winston on 16/11/14. + */ +public class InvalidProjectException extends SnapshotPostException { + + private LinkedList descriptionLines; + + public InvalidProjectException(JsonElement jsonElement) { + super(jsonElement); + } + + @Override + public String getMessage() { + return "invalid project"; + } + + @Override + public List getDescriptionLines() { + return descriptionLines; + } + + @Override + public void fromJSON(JsonElement json) { + descriptionLines = new LinkedList(); + JsonArray errors = + json.getAsJsonObject().get("errors").getAsJsonArray(); + for (JsonElement error : errors) { + descriptionLines.add(error.getAsString()); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/OutOfDateException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/OutOfDateException.java new file mode 100644 index 0000000000..1ae0852424 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/OutOfDateException.java @@ -0,0 +1,33 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by Winston on 16/11/14. + */ +public class OutOfDateException extends SnapshotPostException { + + public OutOfDateException(JsonObject json) { + super(json); + } + + public OutOfDateException() {} + + @Override + public String getMessage() { + return "out of date"; + } + + @Override + public List getDescriptionLines() { + return Arrays.asList("out of date (shouldn't print this)"); + } + + @Override + public void fromJSON(JsonElement json) {} + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/PostbackTimeoutException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/PostbackTimeoutException.java new file mode 100644 index 0000000000..ca19448c3e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/PostbackTimeoutException.java @@ -0,0 +1,38 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.util.Util; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by Winston on 09/01/15. + */ +public class PostbackTimeoutException extends SevereSnapshotPostException { + + private int timeout; + + public PostbackTimeoutException(int timeout) { + this.timeout = timeout; + } + + @Override + public String getMessage() { + return "Request timed out (after " + this.timeout + " seconds)"; + } + + @Override + public List getDescriptionLines() { + return Arrays.asList( + "The " + + Util.getServiceName() + + " server is currently unavailable.", + "Please try again later." + ); + } + + @Override + public void fromJSON(JsonElement json) {} + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/SevereSnapshotPostException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/SevereSnapshotPostException.java new file mode 100644 index 0000000000..fd389822f5 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/SevereSnapshotPostException.java @@ -0,0 +1,18 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +import com.google.gson.JsonElement; + +/** + * Created by Winston on 10/01/15. + */ +public abstract class SevereSnapshotPostException extends SnapshotPostException { + + public SevereSnapshotPostException() { + super(); + } + + public SevereSnapshotPostException(JsonElement json) { + super(json); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/SnapshotPostException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/SnapshotPostException.java new file mode 100644 index 0000000000..0913e65ba3 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/SnapshotPostException.java @@ -0,0 +1,17 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +import com.google.gson.JsonElement; +import uk.ac.ic.wlgitbridge.git.exception.SnapshotAPIException; + +/** + * Created by Winston on 16/11/14. + */ +public abstract class SnapshotPostException extends SnapshotAPIException { + + public SnapshotPostException() {} + + public SnapshotPostException(JsonElement jsonElement) { + fromJSON(jsonElement); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/SnapshotPostExceptionBuilder.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/SnapshotPostExceptionBuilder.java new file mode 100644 index 0000000000..97e4118368 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/SnapshotPostExceptionBuilder.java @@ -0,0 +1,32 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +import com.google.gson.JsonObject; + +/** + * Created by Winston on 17/11/14. + */ +public class SnapshotPostExceptionBuilder { + + private static final String CODE_ERROR_OUT_OF_DATE = "outOfDate"; + private static final String CODE_ERROR_INVALID_FILES = "invalidFiles"; + private static final String CODE_ERROR_INVALID_PROJECT = "invalidProject"; + private static final String CODE_ERROR_UNKNOWN = "error"; + + public SnapshotPostException build( + String errorCode, + JsonObject json + ) throws UnexpectedPostbackException { + if (errorCode.equals(CODE_ERROR_OUT_OF_DATE)) { + return new OutOfDateException(json); + } else if (errorCode.equals(CODE_ERROR_INVALID_FILES)) { + return new InvalidFilesException(json); + } else if (errorCode.equals(CODE_ERROR_INVALID_PROJECT)) { + return new InvalidProjectException(json); + } else if (errorCode.equals(CODE_ERROR_UNKNOWN)) { + return new UnexpectedErrorException(json); + } else { + throw new UnexpectedPostbackException(); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/UnexpectedErrorException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/UnexpectedErrorException.java new file mode 100644 index 0000000000..d6ca9578ec --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/UnexpectedErrorException.java @@ -0,0 +1,38 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.util.Util; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by Winston on 16/11/14. + */ +public class UnexpectedErrorException extends SevereSnapshotPostException { + + private static final String[] DESCRIPTION_LINES = { + "There was an internal error with the " + + Util.getServiceName() + " server.", + "Please contact " + Util.getServiceName() + "." + }; + + public UnexpectedErrorException(JsonObject json) { + super(json); + } + + @Override + public String getMessage() { + return Util.getServiceName() + " error"; + } + + @Override + public List getDescriptionLines() { + return Arrays.asList(DESCRIPTION_LINES); + } + + @Override + public void fromJSON(JsonElement json) {} + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/UnexpectedPostbackException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/UnexpectedPostbackException.java new file mode 100644 index 0000000000..1b7adb17d4 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/push/exception/UnexpectedPostbackException.java @@ -0,0 +1,6 @@ +package uk.ac.ic.wlgitbridge.snapshot.push.exception; + +/** + * Created by Winston on 17/11/14. + */ +public class UnexpectedPostbackException extends Exception {} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/Main.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/Main.java new file mode 100644 index 0000000000..812da70341 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/Main.java @@ -0,0 +1,30 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock; + +import uk.ac.ic.wlgitbridge.snapshot.servermock.server.MockSnapshotServer; +import uk.ac.ic.wlgitbridge.snapshot.servermock.state.SnapshotAPIStateBuilder; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +/** + * Created by Winston on 10/01/15. + */ +public class Main { + + public static void main(String[] args) throws FileNotFoundException { + MockSnapshotServer server = new MockSnapshotServer( + 60000, + new File("/Users/Roxy/Code/java/writelatex-git-bridge") + ); + server.setState( + new SnapshotAPIStateBuilder( + new FileInputStream( + new File("/Users/Roxy/Desktop/state.json") + ) + ).build() + ); + server.start(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/exception/InvalidAPICallException.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/exception/InvalidAPICallException.java new file mode 100644 index 0000000000..ac80157ee1 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/exception/InvalidAPICallException.java @@ -0,0 +1,12 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.exception; + +/** + * Created by Winston on 09/01/15. + */ +public class InvalidAPICallException extends Exception { + + public InvalidAPICallException(String target) { + super(target); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/SnapshotResponse.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/SnapshotResponse.java new file mode 100644 index 0000000000..fadad1deb0 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/SnapshotResponse.java @@ -0,0 +1,14 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response; + +/** + * Created by Winston on 09/01/15. + */ +public abstract class SnapshotResponse { + + public abstract String respond(); + + public String postback() { + return null; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/SnapshotResponseBuilder.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/SnapshotResponseBuilder.java new file mode 100644 index 0000000000..d122ad82ba --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/SnapshotResponseBuilder.java @@ -0,0 +1,80 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response; + +import uk.ac.ic.wlgitbridge.snapshot.servermock.exception + .InvalidAPICallException; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.getdoc + .SnapshotGetDocResponse; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.getforver + .SnapshotGetForVerResponse; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.getsavedver + .SnapshotGetSavedVersResponse; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push + .SnapshotPushResponse; +import uk.ac.ic.wlgitbridge.snapshot.servermock.state.SnapshotAPIState; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotResponseBuilder { + + private SnapshotAPIState state; + + public SnapshotResponse buildWithTarget( + String target, + String method + ) throws InvalidAPICallException { + checkPrefix(target); + return parseTarget(target, target.split("/"), method); + } + + private void checkPrefix(String target) throws InvalidAPICallException { + if (!target.startsWith("/api/v0/docs/")) { + throw new InvalidAPICallException(target); + } + } + + private SnapshotResponse parseTarget( + String target, + String[] parts, + String method + ) throws InvalidAPICallException { + String projectName = parts[4]; + if (parts.length == 5) { + if (method.equals("GET")) { + return new SnapshotGetDocResponse( + state.getStateForGetDoc(projectName) + ); + } + } else if (parts.length == 6) { + String type = parts[5]; + if (type.equals("snapshots") && method.equals("POST")) { + return new SnapshotPushResponse( + state.getStateForPush(projectName), + state.getStateForPostback(projectName) + ); + } else if (type.equals("saved_vers") && method.equals("GET")) { + return new SnapshotGetSavedVersResponse( + state.getStateForGetSavedVers(projectName) + ); + } + } else if (parts.length == 7) { + if (parts[5].equals("snapshots") && method.equals("GET")) { + try { + return new SnapshotGetForVerResponse( + state.getStateForGetForVers( + projectName, Integer.parseInt(parts[6]) + ) + ); + } catch (NumberFormatException e) { + + } + } + } + throw new InvalidAPICallException(target); + } + + public void setState(SnapshotAPIState state) { + this.state = state; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/getdoc/SnapshotGetDocResponse.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/getdoc/SnapshotGetDocResponse.java new file mode 100644 index 0000000000..eec526e328 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/getdoc/SnapshotGetDocResponse.java @@ -0,0 +1,22 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.getdoc; + +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.SnapshotResponse; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotGetDocResponse extends SnapshotResponse { + + private final GetDocResult state; + + public SnapshotGetDocResponse(GetDocResult state) { + this.state = state; + } + + @Override + public String respond() { + return state.toJson().toString(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/getforver/SnapshotGetForVerResponse.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/getforver/SnapshotGetForVerResponse.java new file mode 100644 index 0000000000..0266842ba1 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/getforver/SnapshotGetForVerResponse.java @@ -0,0 +1,22 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.getforver; + +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.SnapshotResponse; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.GetForVersionResult; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotGetForVerResponse extends SnapshotResponse { + + private final GetForVersionResult state; + + public SnapshotGetForVerResponse(GetForVersionResult state) { + this.state = state; + } + + @Override + public String respond() { + return state.toJson().toString(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/getsavedver/SnapshotGetSavedVersResponse.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/getsavedver/SnapshotGetSavedVersResponse.java new file mode 100644 index 0000000000..08cc628a3e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/getsavedver/SnapshotGetSavedVersResponse.java @@ -0,0 +1,22 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.getsavedver; + +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.SnapshotResponse; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.GetSavedVersResult; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotGetSavedVersResponse extends SnapshotResponse { + + private final GetSavedVersResult state; + + public SnapshotGetSavedVersResponse(GetSavedVersResult state) { + this.state = state; + } + + @Override + public String respond() { + return state.toJson().toString(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/SnapshotPushResponse.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/SnapshotPushResponse.java new file mode 100644 index 0000000000..ec8ed23011 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/SnapshotPushResponse.java @@ -0,0 +1,37 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push; + +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.SnapshotResponse; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.SnapshotPostbackRequest; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.data.SnapshotPushResult; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotPushResponse extends SnapshotResponse { + + private final SnapshotPushResult stateForPush; + private final SnapshotPostbackRequest stateForPostback; + + public SnapshotPushResponse( + SnapshotPushResult stateForPush, + SnapshotPostbackRequest stateForPostback + ) { + this.stateForPush = stateForPush; + this.stateForPostback = stateForPostback; + } + + @Override + public String respond() { + return stateForPush.toJson().toString(); + } + + @Override + public String postback() { + if (stateForPush.hasPostback()) { + return stateForPostback.toJson().toString(); + } else { + return null; + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/data/SnapshotPushResult.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/data/SnapshotPushResult.java new file mode 100644 index 0000000000..81b6d93c46 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/data/SnapshotPushResult.java @@ -0,0 +1,31 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.data; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +/** + * Created by Winston on 09/01/15. + */ +public abstract class SnapshotPushResult { + + private final int status; + private final String code; + private final String message; + + public SnapshotPushResult(int status, String code, String message) { + this.status = status; + this.code = code; + this.message = message; + } + + public JsonElement toJson() { + JsonObject jsonThis = new JsonObject(); + jsonThis.addProperty("status", status); + jsonThis.addProperty("code", code); + jsonThis.addProperty("message", message); + return jsonThis; + } + + public abstract boolean hasPostback(); + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/data/SnapshotPushResultOutOfDate.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/data/SnapshotPushResultOutOfDate.java new file mode 100644 index 0000000000..26edb59fb5 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/data/SnapshotPushResultOutOfDate.java @@ -0,0 +1,17 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.data; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotPushResultOutOfDate extends SnapshotPushResult { + + public SnapshotPushResultOutOfDate() { + super(409, "outOfDate", "Out of Date"); + } + + @Override + public boolean hasPostback() { + return false; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/data/SnapshotPushResultSuccess.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/data/SnapshotPushResultSuccess.java new file mode 100644 index 0000000000..327dabaa86 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/data/SnapshotPushResultSuccess.java @@ -0,0 +1,17 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.data; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotPushResultSuccess extends SnapshotPushResult { + + public SnapshotPushResultSuccess() { + super(402, "accepted", "Accepted"); + } + + @Override + public boolean hasPostback() { + return true; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequest.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequest.java new file mode 100644 index 0000000000..9799f8f4f0 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequest.java @@ -0,0 +1,22 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback; + +import com.google.gson.JsonObject; + +/** + * Created by Winston on 09/01/15. + */ +public abstract class SnapshotPostbackRequest { + + private final String code; + + public SnapshotPostbackRequest(String code) { + this.code = code; + } + + public JsonObject toJson() { + JsonObject jsonThis = new JsonObject(); + jsonThis.addProperty("code", code); + return jsonThis; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestError.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestError.java new file mode 100644 index 0000000000..2d68fafa33 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestError.java @@ -0,0 +1,21 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback; + +import com.google.gson.JsonObject; + +/** + * Created by Winston on 10/01/15. + */ +public class SnapshotPostbackRequestError extends SnapshotPostbackRequest { + + public SnapshotPostbackRequestError() { + super("error"); + } + + @Override + public JsonObject toJson() { + JsonObject jsonThis = super.toJson(); + jsonThis.addProperty("message", "Unexpected Error"); + return jsonThis; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestInvalidFiles.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestInvalidFiles.java new file mode 100644 index 0000000000..dd1d24db22 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestInvalidFiles.java @@ -0,0 +1,43 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.invalidfile.InvalidFileError; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotPostbackRequestInvalidFiles extends SnapshotPostbackRequest { + + private final List errors; + + public SnapshotPostbackRequestInvalidFiles(List errors) { + super("invalidFiles"); + this.errors = errors; + } + + public SnapshotPostbackRequestInvalidFiles(JsonArray errors) { + this(new ArrayList()); + for (JsonElement error : errors) { + this.errors.add(InvalidFileError.buildFromJsonError( + error.getAsJsonObject() + )); + } + } + + @Override + public JsonObject toJson() { + JsonObject jsonThis = super.toJson(); + JsonArray jsonErrors = new JsonArray(); + for (InvalidFileError error : errors) { + jsonErrors.add(error.toJson()); + } + jsonThis.add("errors", jsonErrors); + return jsonThis; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestInvalidProject.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestInvalidProject.java new file mode 100644 index 0000000000..e870b43158 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestInvalidProject.java @@ -0,0 +1,43 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Winston on 10/01/15. + */ +public class SnapshotPostbackRequestInvalidProject + extends SnapshotPostbackRequest { + + private final List errors; + + public SnapshotPostbackRequestInvalidProject(List errors) { + super("invalidProject"); + this.errors = errors; + } + + public SnapshotPostbackRequestInvalidProject(JsonArray errors) { + this(new ArrayList()); + for (JsonElement error : errors) { + this.errors.add(error.getAsString()); + } + } + + @Override + public JsonObject toJson() { + JsonObject jsonThis = super.toJson(); + jsonThis.addProperty("message", "short string message for debugging"); + JsonArray jsonErrors = new JsonArray(); + for (String error : errors) { + jsonErrors.add(new JsonPrimitive(error)); + } + jsonThis.add("errors", jsonErrors); + return jsonThis; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestOutOfDate.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestOutOfDate.java new file mode 100644 index 0000000000..def04e92b1 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestOutOfDate.java @@ -0,0 +1,21 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback; + +import com.google.gson.JsonObject; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotPostbackRequestOutOfDate extends SnapshotPostbackRequest { + + public SnapshotPostbackRequestOutOfDate() { + super("outOfDate"); + } + + @Override + public JsonObject toJson() { + JsonObject jsonThis = super.toJson(); + jsonThis.addProperty("message", "Out of Date"); + return jsonThis; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestSuccess.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestSuccess.java new file mode 100644 index 0000000000..07dba7e5fd --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/SnapshotPostbackRequestSuccess.java @@ -0,0 +1,24 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback; + +import com.google.gson.JsonObject; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotPostbackRequestSuccess extends SnapshotPostbackRequest { + + private final int latestVerId; + + public SnapshotPostbackRequestSuccess(int latestVerId) { + super("upToDate"); + this.latestVerId = latestVerId; + } + + @Override + public JsonObject toJson() { + JsonObject jsonThis = super.toJson(); + jsonThis.addProperty("latestVerId", latestVerId); + return jsonThis; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileError.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileError.java new file mode 100644 index 0000000000..b2b92eb575 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileError.java @@ -0,0 +1,43 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.invalidfile; + +import com.google.gson.JsonObject; + +/** + * Created by Winston on 09/01/15. + */ +public abstract class InvalidFileError { + + private final String file; + + public InvalidFileError(String file) { + this.file = file; + } + + public JsonObject toJson() { + JsonObject jsonThis = new JsonObject(); + jsonThis.addProperty("file", file); + jsonThis.addProperty("state", getState()); + return jsonThis; + } + + protected abstract String getState(); + + public static InvalidFileError buildFromJsonError(JsonObject error) { + String state = error.get("state").getAsString(); + String file = error.get("file").getAsString(); + if (state.equals("error")) { + return new InvalidFileErrorDefault(file); + } else if (state.equals("disallowed")) { + return new InvalidFileErrorDisallowed(file); + } else if (state.equals("unclean_name")) { + return new InvalidFileErrorUnclean( + file, error.get("cleanFile").getAsString() + ); + } else { + throw new IllegalArgumentException( + "bad invalid file state: " + state + ); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileErrorDefault.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileErrorDefault.java new file mode 100644 index 0000000000..85fc037088 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileErrorDefault.java @@ -0,0 +1,17 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.invalidfile; + +/** + * Created by Winston on 09/01/15. + */ +public class InvalidFileErrorDefault extends InvalidFileError { + + public InvalidFileErrorDefault(String file) { + super(file); + } + + @Override + protected String getState() { + return "error"; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileErrorDisallowed.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileErrorDisallowed.java new file mode 100644 index 0000000000..159f80406c --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileErrorDisallowed.java @@ -0,0 +1,17 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.invalidfile; + +/** + * Created by Winston on 09/01/15. + */ +public class InvalidFileErrorDisallowed extends InvalidFileError { + + public InvalidFileErrorDisallowed(String file) { + super(file); + } + + @Override + protected String getState() { + return "disallowed"; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileErrorUnclean.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileErrorUnclean.java new file mode 100644 index 0000000000..746dc766a2 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/response/push/postback/invalidfile/InvalidFileErrorUnclean.java @@ -0,0 +1,29 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.invalidfile; + +import com.google.gson.JsonObject; + +/** + * Created by Winston on 09/01/15. + */ +public class InvalidFileErrorUnclean extends InvalidFileError { + + private final String cleanFile; + + public InvalidFileErrorUnclean(String file, String cleanFile) { + super(file); + this.cleanFile = cleanFile; + } + + @Override + public JsonObject toJson() { + JsonObject jsonThis = super.toJson(); + jsonThis.addProperty("cleanFile", cleanFile); + return jsonThis; + } + + @Override + protected String getState() { + return "unclean_name"; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/server/MockSnapshotRequestHandler.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/server/MockSnapshotRequestHandler.java new file mode 100644 index 0000000000..649a4450b5 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/server/MockSnapshotRequestHandler.java @@ -0,0 +1,56 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.server; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import uk.ac.ic.wlgitbridge.snapshot.servermock.exception. + InvalidAPICallException; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.*; +import uk.ac.ic.wlgitbridge.util.Log; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Created by Winston on 09/01/15. + */ +public class MockSnapshotRequestHandler extends AbstractHandler { + + private final SnapshotResponseBuilder responseBuilder; + + public MockSnapshotRequestHandler( + SnapshotResponseBuilder responseBuilder + ) { + this.responseBuilder = responseBuilder; + } + + @Override + public void handle( + String target, + final Request baseRequest, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException, ServletException { + boolean handled; + try { + final SnapshotResponse snapshotResponse + = responseBuilder.buildWithTarget( + target, baseRequest.getMethod() + ); + response.getWriter().println(snapshotResponse.respond()); + new PostbackThread( + baseRequest.getReader(), + snapshotResponse.postback() + ).startIfNotNull(); + handled = true; + } catch (InvalidAPICallException e) { + handled = false; + } catch (RuntimeException e) { + Log.warn("Runtime exception when handling request", e); + handled = true; + } + baseRequest.setHandled(handled); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/server/MockSnapshotServer.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/server/MockSnapshotServer.java new file mode 100644 index 0000000000..b6bebcb44f --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/server/MockSnapshotServer.java @@ -0,0 +1,62 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.server; + +import org.eclipse.jetty.server.NetworkConnector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.ResourceHandler; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.SnapshotResponseBuilder; +import uk.ac.ic.wlgitbridge.snapshot.servermock.state.SnapshotAPIState; +import uk.ac.ic.wlgitbridge.util.Log; + +import java.io.File; + +/** + * Created by Winston on 09/01/15. + */ +public class MockSnapshotServer { + + private final Server server; + private final SnapshotResponseBuilder responseBuilder; + private int port; + + public MockSnapshotServer(int port, File resourceBase) { + server = new Server(port); + responseBuilder = new SnapshotResponseBuilder(); + server.setHandler(getHandlerForResourceBase(resourceBase)); + } + + private HandlerCollection getHandlerForResourceBase(File resourceBase) { + HandlerCollection handlers = new HandlerCollection(); + handlers.addHandler(new MockSnapshotRequestHandler(responseBuilder)); + handlers.addHandler(resourceHandlerWithBase(resourceBase)); + return handlers; + } + + private ResourceHandler resourceHandlerWithBase(File resourceBase) { + ResourceHandler resourceHandler = new ResourceHandler(); + resourceHandler.setResourceBase(resourceBase.getAbsolutePath()); + return resourceHandler; + } + + public void start() { + try { + server.start(); + } catch (Exception e) { + Log.warn("Exception when trying to start server", e); + } + port = ((NetworkConnector) server.getConnectors()[0]).getLocalPort(); + } + + public void stop() { + try { + server.stop(); + } catch (Exception e) { + Log.warn("Exception when trying to stop server", e); + } + } + + public void setState(SnapshotAPIState state) { + responseBuilder.setState(state); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/server/PostbackThread.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/server/PostbackThread.java new file mode 100644 index 0000000000..c3559552ed --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/server/PostbackThread.java @@ -0,0 +1,61 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.server; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import static org.asynchttpclient.Dsl.*; +import uk.ac.ic.wlgitbridge.util.Log; + +import java.io.IOException; +import java.io.Reader; +import java.util.concurrent.ExecutionException; + +/** + * Created by Winston on 10/01/15. + */ +public class PostbackThread extends Thread { + + private String url; + private String postback; + + public PostbackThread(Reader reader, String postback) { + if (postback != null) { + url = new Gson().fromJson( + reader, + JsonObject.class + ).get("postbackUrl").getAsString(); + this.postback = postback; + } + } + + @Override + public void run() { + try { + asyncHttpClient().preparePost( + url + ).setBody(postback).execute().get().getResponseBody(); + } catch (InterruptedException e) { + Log.warn( + "Interrupted on postback, url: " + + url + + ", postback: " + + postback, + e + ); + } catch (ExecutionException e) { + Log.warn( + "ExecutionException on postback, url: " + + url + + ", postback: " + + postback, + e + ); + } + } + + public void startIfNotNull() { + if (url != null && postback != null) { + start(); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/state/SnapshotAPIState.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/state/SnapshotAPIState.java new file mode 100644 index 0000000000..b3095874f9 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/state/SnapshotAPIState.java @@ -0,0 +1,190 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.state; + +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.data.SnapshotPushResult; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.data.SnapshotPushResultSuccess; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.SnapshotPostbackRequest; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.SnapshotPostbackRequestInvalidProject; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotAttachment; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotData; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotFile; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.GetForVersionResult; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.GetSavedVersResult; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.SnapshotInfo; + +import java.util.*; + +/** + * Created by Winston on 09/01/15. + */ +public class SnapshotAPIState { + + private Map getDoc; + private Map getSavedVers; + private Map> getForVers; + private Map push; + private Map postback; + + public SnapshotAPIState( + Map getDoc, + Map getSavedVers, + Map> getForVers, + Map push, + Map postback + ) { + this.getDoc = getDoc; + this.getSavedVers = getSavedVers; + this.getForVers = getForVers; + this.push = push; + this.postback = postback; + } + + public SnapshotAPIState() { + getDoc = new HashMap<>(); + getDoc.put( + "1826rqgsdb", + new GetDocResult( + null, + 243, + "2014-11-30T18:40:58Z", + "jdleesmiller+1@gmail.com", + "John+1", + null + ) + ); + + getSavedVers = new HashMap(); + List savedVers = new LinkedList(); + savedVers.add(new SnapshotInfo( + 243, + "added more info on doc GET and error details", + "jdleesmiller+1@gmail.com", + "John+1", + "2014-11-30T18:47:01Z" + )); + savedVers.add(new SnapshotInfo( + 185, "with more details on POST request", "jdleesmiller+1@gmail.com", "John+1", "2014-11-11T17:18:40Z" + )); + savedVers.add(new SnapshotInfo( + 175, "with updated PUT/POST request", "jdleesmiller+1@gmail.com", "John+1", "2014-11-09T23:09:13Z" + )); + savedVers.add(new SnapshotInfo( + 146, "added PUT format", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-07T15:11:35Z" + )); + savedVers.add(new SnapshotInfo( + 74, "with example output", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-05T18:09:41Z" + )); + savedVers.add(new SnapshotInfo( + 39, "with more files", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-05T18:02:19Z" + )); + savedVers.add(new SnapshotInfo( + 24, "first draft", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-05T17:56:58Z" + )); + getSavedVers.put("1826rqgsdb", new GetSavedVersResult(savedVers)); + + getForVers = new HashMap>() {{ + put("1826rqgsdb", new HashMap() {{ + put(243, new GetForVersionResult(new SnapshotData(Arrays.asList( + new SnapshotFile("\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. This endpoint returns the latest version id, when the latest version was created (ISO8601), and the user that last edited the project (if any, otherwise null).\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => {\\n \\\"latestVerId\\\": 39,\\n \\\"latestVerAt\\\": \\\"2014-11-30T18:35:27Z\\\",\\n \\\"latestVerBy\\\": {\\n \\\"email\\\": \\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\": \\\"John Lees-Miller\\\"\\n }}\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/servermock.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\n\\\\subsection{Push a Project}\\n\\n\\\\begin{lstlisting}\\n# NB: JLM originally said PUT, but he now thinks POST is better\\n# NB: you must set a Content-Type: application/json header for this request\\n# in order to specify the data as JSON in the request body\\nPOST https://.../api/v0/docs/1826rqgsdb/snapshots\\nData:\\n{\\n latestVerId: integer,\\n files: [\\n {\\n name: string path (forward slashes, relative to root)\\n url: string (but only if the file is modified; else no url given)\\n }, ...\\n ]\\n postbackUrl: url to post result back to\\n}\\nResponse on success:\\n{\\n status: 202,\\n code: \\\"accepted\\\",\\n message: \\\"Accepted\\\"\\n}\\nResponse on out of date:\\n{\\n status: 409, # Conflict\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\n\\nPostback Data (METHOD POST):\\nOn success:\\n{\\n code: \\\"upToDate\\\",\\n latestVerId: integer\\n}\\nOn out of date:\\n{\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\nOn error with the files list (e.g. file extension not allowed):\\n{\\n code: \\\"invalidFiles\\\",\\n errors: [ {\\n file: the file name from the snapshot,\\n state: \\\"error\\\"|\\\"disallowed\\\"|\\\"unclean_name\\\"\\n }, ... ]\\n}\\nIf the file's error state is unclean_name, the error object will alsocontain a property cleanFile that contains the name of the file after it has been \\\"cleaned\\\" to meet our file naming requirements; for other file error states, this property is not present.\\nOn error with the project as a whole (e.g. over quota):\\n{\\n code: \\\"invalidProject\\\",\\n message: short string message for debugging\\n errors: [ array of zero or more string messages for the user ]\\n}\\nOn unexpected error (bug):\\n{\\n code: \\\"error\\\",\\n message: \\\"Unexpected Error\\\"\\n}\\n\\\\end{lstlisting}\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as one of your servermock projects. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/servermock}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", "main.tex"), + new SnapshotFile("This text is from another file.", "foo/bar/servermock.tex") + ), Arrays.asList( + new SnapshotAttachment("https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", "min_mean_wait_evm_7_eps_150dpi.png") + )))); + put(185, new GetForVersionResult(new SnapshotData(Arrays.asList( + new SnapshotFile("\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => { latestVerId: 39 }\\nTODO will also include updatedAt time and user (if it was not anonymous)\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/servermock.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\n\\\\subsection{Push a Project}\\n\\n\\\\begin{lstlisting}\\n# NB: JLM originally said PUT, but he now thinks POST is better\\n# NB: you must set a Content-Type: application/json header for this request\\n# in order to specify the data as JSON in the request body\\nPOST https://.../api/v0/docs/1826rqgsdb/snapshots\\nData:\\n{\\n latestVerId: integer,\\n files: [\\n {\\n name: string path (forward slashes, relative to root)\\n url: string (but only if the file is modified; else no url given)\\n }, ...\\n ]\\n postbackUrl: url to post result back to\\n}\\nResponse on success:\\n{\\n status: 202,\\n code: \\\"accepted\\\",\\n message: \\\"Accepted\\\"\\n}\\nResponse on out of date:\\n{\\n status: 409, # Conflict\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\n\\nPostback Data (METHOD POST):\\nOn success:\\n{\\n code: \\\"upToDate\\\",\\n latestVerId: integer\\n}\\nOn out of date:\\n{\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\nOn error with the files list (e.g. file extension not allowed):\\n{\\n code: \\\"invalidFiles\\\",\\n errors: TODO\\n}\\nOn error with the project as a whole (e.g. over quota):\\n{\\n code: \\\"invalidProject\\\",\\n errors: TODO\\n}\\nOn unexpected error (bug):\\n{\\n code: \\\"error\\\",\\n message: \\\"Unexpected Error\\\"\\n}\\n\\\\end{lstlisting}\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as one of your servermock projects. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/servermock}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", "main.tex"), + new SnapshotFile("This text is from another file.", "foo/bar/servermock.tex") + ), Arrays.asList( + new SnapshotAttachment("https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", "min_mean_wait_evm_7_eps_150dpi.png") + )))); + put(175, new GetForVersionResult(new SnapshotData(Arrays.asList( + new SnapshotFile("\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => { latestVerId: 39 }\\nTODO will also include updatedAt time and user (if it was not anonymous)\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/servermock.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\n\\\\subsection{Push a Project}\\n\\n\\\\begin{lstlisting}\\n# NB: JLM originally said PUT, but he now thinks POST is better\\n# NB: you must set a Content-Type: application/json header for this request\\n# in order to specify the data as JSON in the request body\\nPOST https://.../api/v0/docs/1826rqgsdb/snapshots\\nData:\\n{\\n latestVerId: integer,\\n files: [\\n {\\n name: string path (forward slashes, relative to root)\\n url: string (but only if the file is modified; else no url given)\\n }, ...\\n ]\\n postbackUrl: url to post result back to\\n}\\nResponse on success:\\n{\\n status: 202,\\n code: \\\"accepted\\\",\\n message: \\\"Accepted\\\"\\n}\\nResponse on out of date:\\n{\\n status: 409, # Conflict\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\n\\nPostback Data (METHOD POST):\\nOn success:\\n{\\n code: \\\"upToDate\\\",\\n latestVerId: integer\\n}\\nOn out of date:\\n{\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\nOn error:\\n{\\n code: \\\"invalidFile\\\",\\n TODO\\n}\\n\\\\end{lstlisting}\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as one of your servermock projects. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/servermock}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", "main.tex"), + new SnapshotFile("This text is from another file.", "foo/bar/servermock.tex") + ), Arrays.asList( + new SnapshotAttachment("https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", "min_mean_wait_evm_7_eps_150dpi.png") + )))); + put(146, new GetForVersionResult(new SnapshotData(Arrays.asList( + new SnapshotFile("\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => { latestVerId: 39 }\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/servermock.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\n\\\\subsection{Push a Project}\\n\\n\\\\begin{lstlisting}\\nPUT https://.../api/v0/docs/1826rqgsdb/snapshots\\nData:\\n{\\n latestVerId: integer,\\n files: [\\n {\\n name: string path (forward slashes, relative to root)\\n url: string (but only if the file is modified; else no url given)\\n }, ...\\n ]\\n postbackUrl: url to post result back to\\n}\\nResponse on success:\\n{\\n status: 20x,\\n}\\nResponse on out of date:\\n{\\n status: 40x,\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\n\\nPostback Data (METHOD POST):\\nOn success:\\n{\\n code: \\\"upToDate\\\",\\n latestVerId: integer\\n}\\nOn out of date:\\n{\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\nOn error:\\n{\\n code: \\\"invalidFile\\\",\\n TODO\\n}\\n\\\\end{lstlisting}\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as one of your servermock projects. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/servermock}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", "main.tex"), + new SnapshotFile("This text is from another file.", "foo/bar/servermock.tex") + ), Arrays.asList( + new SnapshotAttachment("https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", "min_mean_wait_evm_7_eps_150dpi.png") + )))); + put(74, new GetForVersionResult(new SnapshotData(Arrays.asList( + new SnapshotFile("\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => { latestVerId: 39 }\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/servermock.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\nTODO still working on this part\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as a servermock project. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/servermock}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", "main.tex"), + new SnapshotFile("This text is from another file.", "foo/bar/servermock.tex") + ), Arrays.asList( + new SnapshotAttachment("https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", "min_mean_wait_evm_7_eps_150dpi.png") + )))); + put(39, new GetForVersionResult(new SnapshotData(Arrays.asList( + new SnapshotFile("\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. To list saved versions for a doc:\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/1\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\nTODO still working on this part\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as a servermock project. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/servermock}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", "main.tex"), + new SnapshotFile("This text is from another file.", "foo/bar/servermock.tex") + ), Arrays.asList( + new SnapshotAttachment("https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", "min_mean_wait_evm_7_eps_150dpi.png") + )))); + put(24, new GetForVersionResult(new SnapshotData(Arrays.asList( + new SnapshotFile("\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server (radiant-wind-3058.herokuapp.com).\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://radiant-wind.....com/api/v0/docs/1826rqgsdb\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. To list saved versions for a doc:\\n\\n\\\\begin{lstlisting}\\nGET https://radiant-wind.....com/api/v0/docs/1826rqgsdb/saved_vers\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\n\\\\begin{lstlisting}\\nGET https://radiant-wind.....com/api/v0/docs/1826rqgsdb/snapshots/1\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\nTODO still working on this part\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as a servermock project. Here is an extra file to make it a bit more interesting.\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", "main.tex") + ), Arrays.asList( + new SnapshotAttachment("https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", "min_mean_wait_evm_7_eps_150dpi.png") + )))); + }}); + }}; + + push = new HashMap() {{ + put("1826rqgsdb", new SnapshotPushResultSuccess()); + }}; + + postback = new HashMap() {{ +// put( +// "1826rqgsdb", +// new SnapshotPostbackRequestInvalidFiles( +// Arrays.asList( +// new InvalidFileErrorDefault( +// "file1.invalid" +// ), +// new InvalidFileErrorDisallowed( +// "file2.exe" +// ), +// new InvalidFileErrorUnclean( +// "hello world.png", +// "hello_world.png" +// ) +// ) +// ) +// ); +// put("1826rqgsdb", new SnapshotPostbackRequestOutOfDate()); + put( + "1826rqgsdb", + new SnapshotPostbackRequestInvalidProject( + Arrays.asList( + "Your project is missing main.tex.", + "Please name your main latex file main.tex." + ) + ) + ); +// put("1826rqgsdb", new SnapshotPostbackRequestError()); + }}; + } + + public GetDocResult getStateForGetDoc(String projectName) { + return getDoc.get(projectName); + } + + public GetSavedVersResult getStateForGetSavedVers(String projectName) { + return getSavedVers.get(projectName); + } + + public GetForVersionResult getStateForGetForVers( + String projectName, + int versionID + ) { + return getForVers.get(projectName).get(versionID); + } + + public SnapshotPushResult getStateForPush(String projectName) { + return push.get(projectName); + } + + public SnapshotPostbackRequest getStateForPostback(String projectName) { + return postback.get(projectName); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/state/SnapshotAPIStateBuilder.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/state/SnapshotAPIStateBuilder.java new file mode 100644 index 0000000000..502a67aaa8 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/state/SnapshotAPIStateBuilder.java @@ -0,0 +1,233 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.state; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.GetForVersionResult; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotAttachment; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotData; +import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotFile; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.GetSavedVersResult; +import uk.ac.ic.wlgitbridge.snapshot.getsavedvers.SnapshotInfo; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.data.SnapshotPushResult; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.data.SnapshotPushResultOutOfDate; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.data.SnapshotPushResultSuccess; +import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.*; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; + +/** + * Created by Winston on 11/01/15. + */ +public class SnapshotAPIStateBuilder { + + private final JsonArray projects; + + private Map getDoc = new HashMap<>(); + private Map getSavedVers = new HashMap<>(); + private Map> getForVers + = new HashMap<>(); + private Map push + = new HashMap<>(); + private Map postback + = new HashMap<>(); + + public SnapshotAPIStateBuilder(InputStream stream) { + projects = new Gson().fromJson( + new InputStreamReader(stream), JsonArray.class + ); + } + + public SnapshotAPIState build() { + for (JsonElement project : projects) { + addProject(project.getAsJsonObject()); + } + return new SnapshotAPIState( + getDoc, + getSavedVers, + getForVers, + push, + postback + ); + } + + private void addProject(JsonObject project) { + String projectName = project.get("project").getAsString(); + addGetDocForProject( + projectName, + project.get("getDoc").getAsJsonObject() + ); + addGetSavedVersForProject( + projectName, + project.get("getSavedVers").getAsJsonArray() + ); + addGetForVersForProject( + projectName, + project.get("getForVers").getAsJsonArray() + ); + addPushForProject( + projectName, + project.get("push").getAsString() + ); + addPostbackForProject( + projectName, + project.get("postback").getAsJsonObject() + ); + } + + private void addGetDocForProject( + String projectName, + JsonObject jsonGetDoc + ) { + int versionID = jsonGetDoc.get("versionID").getAsInt(); + String createdAt = null; + String email = null; + String name = null; + String migratedFromId = null; + if (jsonGetDoc.has("createdAt")) { + createdAt = jsonGetDoc.get("createdAt").getAsString(); + } + if (jsonGetDoc.has("email")) { + email = jsonGetDoc.get("email").getAsString(); + } + if (jsonGetDoc.has("name")) { + name = jsonGetDoc.get("name").getAsString(); + } + if (jsonGetDoc.has("migratedFromId")) { + migratedFromId = jsonGetDoc.get("migratedFromId").getAsString(); + } + getDoc.put( + projectName, + new GetDocResult( + jsonGetDoc.get("error"), + versionID, + createdAt, + email, + name, + migratedFromId + ) + ); + } + + private void addGetSavedVersForProject( + String projectName, + JsonArray jsonGetSavedVers + ) { + List savedVers = new ArrayList<>(); + for (JsonElement ver : jsonGetSavedVers) { + savedVers.add(getSnapshotInfo(ver.getAsJsonObject())); + } + getSavedVers.put(projectName, new GetSavedVersResult(savedVers)); + } + + private SnapshotInfo getSnapshotInfo( + JsonObject jsonSnapshotInfo + ) { + return new SnapshotInfo( + jsonSnapshotInfo.get("versionID").getAsInt(), + jsonSnapshotInfo.get("comment").getAsString(), + jsonSnapshotInfo.get("email").getAsString(), + jsonSnapshotInfo.get("name").getAsString(), + jsonSnapshotInfo.get("createdAt").getAsString() + ); + } + + private void addGetForVersForProject( + String projectName, + JsonArray jsonGetForVers + ) { + Map forVers = new HashMap<>(); + for (JsonElement forVer : jsonGetForVers) { + JsonObject forVerObj = forVer.getAsJsonObject(); + forVers.put( + forVerObj.get("versionID").getAsInt(), + new GetForVersionResult( + new SnapshotData( + getSrcs( + forVerObj.get( + "srcs" + ).getAsJsonArray() + ), + getAtts( + forVerObj.get( + "atts" + ).getAsJsonArray() + ) + ) + ) + ); + } + getForVers.put(projectName, forVers); + } + + private List getSrcs(JsonArray jsonSrcs) { + List srcs = new ArrayList<>(); + for (JsonElement src : jsonSrcs) { + srcs.add(getSrc(src.getAsJsonObject())); + } + return srcs; + } + + private SnapshotFile getSrc(JsonObject jsonSrc) { + return new SnapshotFile(jsonSrc.get("content").getAsString(), + jsonSrc.get("path").getAsString()); + } + + private List getAtts(JsonArray jsonAtts) { + List atts = new LinkedList<>(); + for (JsonElement att : jsonAtts) { + atts.add(getAtt(att.getAsJsonObject())); + } + return atts; + } + + private SnapshotAttachment getAtt(JsonObject jsonAtt) { + return new SnapshotAttachment(jsonAtt.get("url").getAsString(), + jsonAtt.get("path").getAsString()); + } + + private void addPushForProject(String projectName, String jsonPush) { + SnapshotPushResult p; + if (jsonPush.equals("success")) { + p = new SnapshotPushResultSuccess(); + } else if (jsonPush.equals("outOfDate")) { + p = new SnapshotPushResultOutOfDate(); + } else { + throw new IllegalArgumentException("invalid push"); + } + push.put(projectName, p); + } + + private void addPostbackForProject( + String projectName, + JsonObject jsonPostback + ) { + SnapshotPostbackRequest p; + String type = jsonPostback.get("type").getAsString(); + if (type.equals("success")) { + p = new SnapshotPostbackRequestSuccess( + jsonPostback.get("versionID").getAsInt() + ); + } else if (type.equals("outOfDate")) { + p = new SnapshotPostbackRequestOutOfDate(); + } else if (type.equals("invalidFiles")) { + p = new SnapshotPostbackRequestInvalidFiles( + jsonPostback.get("errors").getAsJsonArray() + ); + } else if (type.equals("invalidProject")) { + p = new SnapshotPostbackRequestInvalidProject( + jsonPostback.get("errors").getAsJsonArray() + ); + } else if (type.equals("error")) { + p = new SnapshotPostbackRequestError(); + } else { + throw new IllegalArgumentException("invalid postback type"); + } + postback.put(projectName, p); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtil.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtil.java new file mode 100644 index 0000000000..2e798b03d5 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtil.java @@ -0,0 +1,191 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.util; + +import com.google.common.collect.ImmutableSet; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoHeadException; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Created by Winston on 11/01/15. + */ +public class FileUtil { + + public static boolean currentCommitsAreEqual(Path dir1, Path dir2) { + try { + RevCommit commit1 = new Git( + new FileRepositoryBuilder().setWorkTree( + dir1.toFile().getAbsoluteFile() + ).build() + ).log().call().iterator().next(); + RevCommit commit2 = new Git( + new FileRepositoryBuilder().setWorkTree( + dir2.toFile().getAbsoluteFile() + ).build() + ).log().call().iterator().next(); + return commit1.equals(commit2); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (NoHeadException e) { + return false; + } catch (GitAPIException e) { + throw new RuntimeException(e); + } + } + + public static boolean gitDirectoriesAreEqual(Path dir1, Path dir2) { + Set dir1Contents = getAllRecursivelyInDirectoryApartFrom( + dir1, + dir1.resolve(".git") + ); + Set dir2Contents = getAllRecursivelyInDirectoryApartFrom( + dir2, + dir2.resolve(".git") + ); + return filesAreEqual(dir1, dir2, dir1Contents, dir2Contents); + } + + public static boolean directoryDeepEquals(File dir, File dir_) { + return directoryDeepEquals(dir.toPath(), dir_.toPath()); + } + + public static boolean directoryDeepEquals(Path path, Path path_) { + List> contents = Stream.of(path, path_).map(p -> + getAllFilesRecursively( + p, p, Collections.emptySet(), true + ) + ).collect(Collectors.toList()); + return filesAreEqual(path, path_, contents.get(0), contents.get(1)); + } + + private static boolean filesAreEqual( + Path dir1, Path dir2, + Set dir1Contents, Set dir2Contents + ) { + boolean filesEqual = dir1Contents.equals(dir2Contents); + if (!filesEqual) { + System.out.println( + "Not equal: (" + + dir1Contents + + ", " + + dir2Contents + + ")" + ); + System.out.println(dir1 + ": " + dir1Contents); + System.out.println(dir2 + ": " + dir2Contents); + } + return filesEqual && directoryContentsEqual(dir1Contents, dir1, dir2); + } + + static boolean directoryContentsEqual( + Set dirContents, + Path dir1, + Path dir2 + ) { + for (String file : dirContents) { + Path path1 = dir1.resolve(file); + Path path2 = dir2.resolve(file); + if ( + !path1.toFile().isDirectory() + && !path2.toFile().isDirectory() + && !fileContentsEqual(path1, path2) + ) { + return false; + } + } + return true; + } + + private static boolean fileContentsEqual(Path first, Path second) { + try { + byte[] firstContents = Files.readAllBytes(first); + byte[] secondContents = Files.readAllBytes(second); + boolean equals = Arrays.equals(firstContents, secondContents); + if (!equals) { + System.out.println( + "Not equal: (" + first + ", " + second + ")" + ); + System.out.println(first + ": " + new String(firstContents)); + System.out.println(second + ": " + new String(secondContents)); + } + return equals; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Set getAllRecursivelyInDirectoryApartFrom( + Path dir, + Path excluded + ) { + return getAllRecursivelyInDirectoryApartFrom( + dir, excluded, true + ); + } + + public static Set getOnlyFilesRecursivelyInDirectoryApartFrom( + Path dir, + Path excluded + ) { + return getAllRecursivelyInDirectoryApartFrom( + dir, excluded, false + ); + } + + private static Set getAllRecursivelyInDirectoryApartFrom( + Path dir, + Path excluded, + boolean directories + ) { + if (!dir.toFile().isDirectory()) { + throw new IllegalArgumentException("need a directory"); + } + return getAllFilesRecursively( + dir, dir, ImmutableSet.of(excluded.toFile()), directories + ); + } + + private static final Set ExcludedNames = ImmutableSet.of( + ".DS_Store" + ); + + static Set getAllFilesRecursively( + Path baseDir, + Path dir, + Set excluded, + boolean directories + ) { + Set files = new HashSet(); + for (File file : dir.toFile().listFiles()) { + if (excluded.contains(file)) { + continue; + } + if (ExcludedNames.contains(file.getName())) { + continue; + } + boolean isDirectory = file.isDirectory(); + if (directories || !isDirectory) { + files.add(baseDir.relativize(file.toPath()).toString()); + } + if (isDirectory) { + files.addAll(getAllFilesRecursively( + baseDir, + file.toPath(), + excluded, + directories + )); + } + } + return files; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/BiConsumerT.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/BiConsumerT.java new file mode 100644 index 0000000000..dae03e46ff --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/BiConsumerT.java @@ -0,0 +1,11 @@ +package uk.ac.ic.wlgitbridge.util; + +/** + * BiConsumer interface that allows checked exceptions. + */ +@FunctionalInterface +public interface BiConsumerT { + + void accept(T t, U u) throws E; + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/DeletingFileInputStream.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/DeletingFileInputStream.java new file mode 100644 index 0000000000..5ba4832979 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/DeletingFileInputStream.java @@ -0,0 +1,60 @@ +package uk.ac.ic.wlgitbridge.util; + +import java.io.*; + +/** + * A {@link java.io.FileInputStream} which deletes the underlying + * {@link java.io.File} on close. + * + * @author Michael Walker (barrucadu) {@literal } + */ +public class DeletingFileInputStream extends FileInputStream { + private File file; + + /** + * Creates a {@link java.io.FileInputStream} by opening a + * connection to an actual file, the file named by the + * {@link java.io.File} object file in the file system. + * + * When the {@link close} method is called, the {@code File} will + * be deleted. + */ + public DeletingFileInputStream(File file) throws FileNotFoundException { + super(file); + this.file = file; + } + + /** + * Closes this input stream and deletes the underlying file. + */ + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + if(file != null) { + file.delete(); + file = null; + } + } + } + + /** + * We shouldn't rely on this for correctness! + */ + @Override + protected void finalize() throws IOException { + try { + super.finalize(); + } finally { + if(file != null) { + Log.warn("File open at finalization time: {}", file.getCanonicalPath()); + try { + close(); + } catch (IOException e) { + Log.error("Failed to delete file", e); + } + } + } + } +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Files.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Files.java new file mode 100644 index 0000000000..d5f20a0f97 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Files.java @@ -0,0 +1,130 @@ +package uk.ac.ic.wlgitbridge.util; + +import com.google.api.client.repackaged.com.google.common.base.Preconditions; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.TrueFileFilter; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Created by winston on 23/08/2016. + */ +public class Files { + + private Files() {} + + public static boolean contentsAreEqual( + File f0, + File f1 + ) throws IOException { + try { + return uncheckedContentsAreEqual(f0, f1); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + public static void renameAll( + File fileOrDir, + String from, + String to + ) { + if (fileOrDir.isDirectory()) { + File f = doRename(fileOrDir, from, to); + for (File c : f.listFiles()) { + renameAll(c, from, to); + } + } else if (fileOrDir.isFile()) { + doRename(fileOrDir, from, to); + } else { + throw new IllegalArgumentException( + "not a file or dir: " + fileOrDir + ); + } + } + + private static File doRename(File fileOrDir, String from, String to) { + if (!fileOrDir.getName().equals(from)) { + return fileOrDir; + } + File renamed = new File(fileOrDir.getParent(), to); + Preconditions.checkState(fileOrDir.renameTo(renamed)); + return renamed; + } + + private static boolean uncheckedContentsAreEqual( + File f0, + File f1 + ) throws IOException { + if (f0.equals(f1)) { + return true; + } + if (!f0.isDirectory() || !f1.isDirectory()) { + return !f0.isDirectory() && !f1.isDirectory() && + Arrays.equals( + FileUtils.readFileToByteArray(f0), + FileUtils.readFileToByteArray(f1) + ); + } + Path f0Base = Paths.get(f0.getAbsolutePath()); + Path f1Base = Paths.get(f1.getAbsolutePath()); + Set children0 = getChildren(f0, f0Base); + Set children1 = getChildren(f1, f1Base); + if (children0.size() != children1.size()) { + return false; + } + return children0.stream( + ).allMatch(c0 -> + children1.contains(c0) && childEquals(c0, f0Base, f1Base) + ); + } + + private static Set getChildren(File f0, Path f0Base) { + return FileUtils.listFilesAndDirs( + f0, + TrueFileFilter.TRUE, + TrueFileFilter.TRUE + ).stream( + ).map( + File::getAbsolutePath + ).map( + Paths::get + ).map(p -> + f0Base.relativize(p) + ).filter(p -> + !p.toString().isEmpty() + ).collect( + Collectors.toSet() + ); + } + + private static boolean childEquals( + Path child, + Path f0Base, + Path f1Base + ) throws UncheckedIOException { + File c0 = f0Base.resolve(child).toFile(); + File c1 = f1Base.resolve(child).toFile(); + boolean c0IsDir = c0.isDirectory(); + boolean c1IsDir = c1.isDirectory(); + if (c0IsDir || c1IsDir) { + return c0IsDir && c1IsDir; + } + try { + return c0.isFile() && c1.isFile() && Arrays.equals( + FileUtils.readFileToByteArray(c0), + FileUtils.readFileToByteArray(c1) + ); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/FunctionT.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/FunctionT.java new file mode 100644 index 0000000000..8920830f46 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/FunctionT.java @@ -0,0 +1,14 @@ +package uk.ac.ic.wlgitbridge.util; + +/** + * Function interface that allows checked exceptions. + * @param + * @param + * @param + */ +@FunctionalInterface +public interface FunctionT { + + R apply(T t) throws E; + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Instance.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Instance.java new file mode 100644 index 0000000000..75e72924aa --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Instance.java @@ -0,0 +1,32 @@ +package uk.ac.ic.wlgitbridge.util; + +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.gson.GsonFactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Created by winston on 25/10/15. + */ +public class Instance { + + public static final HttpTransport httpTransport = + new NetHttpTransport(); + + public static final HttpRequestFactory httpRequestFactory = + httpTransport.createRequestFactory(); + + public static final JsonFactory jsonFactory = new GsonFactory(); + + public static final Gson prettyGson = new GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .disableHtmlEscaping() + .create(); + + public static final Gson gson = new Gson(); + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Log.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Log.java new file mode 100644 index 0000000000..31286ce90d --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Log.java @@ -0,0 +1,86 @@ +package uk.ac.ic.wlgitbridge.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.ac.ic.wlgitbridge.application.GitBridgeApp; + +/** + * Created by winston on 19/01/2016. + */ +public class Log { + + private static Logger logger = LoggerFactory.getLogger(GitBridgeApp.class); + + public static void trace(String msg) { + logger.trace(msg); + } + + public static void trace(String msg, Throwable t) { + logger.trace(msg, t); + } + + public static void debug(String msg) { + logger.debug(msg); + } + + public static void debug(String msg, Throwable t) { + logger.debug(msg, t); + } + + public static void debug(String format, Object... args) { + logger.info(format, args); + } + + public static void info(String msg) { + logger.info(msg); + } + + public static void info(String format, Object arg) { + logger.info(format, arg); + } + + public static void info(String format, Object arg1, Object arg2) { + logger.info(format, arg1, arg2); + } + + public static void info(String format, Object... args) { + logger.info(format, args); + } + + public static void info(String msg, Throwable t) { + logger.info(msg, t); + } + + public static void warn(String msg) { + logger.warn(msg); + } + + public static void warn(String msg, Object arg) { + logger.warn(msg, arg); + } + + public static void warn(String msg, Object arg1, Object arg2) { + logger.warn(msg, arg1, arg2); + } + + public static void warn(String msg, Object... args) { + logger.warn(msg, args); + } + + public static void warn(String msg, Throwable t) { + logger.warn(msg, t); + } + + public static void error(String msg) { + logger.error(msg); + } + + public static void error(String msg, Object... args) { + logger.error(msg, args); + } + + public static void error(String msg, Throwable t) { + logger.error(msg, t); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Project.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Project.java new file mode 100644 index 0000000000..4e091e2489 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Project.java @@ -0,0 +1,20 @@ +package uk.ac.ic.wlgitbridge.util; + +import com.google.common.base.Preconditions; + +/** + * Created by winston on 23/08/2016. + */ +public class Project { + + public static boolean isValidProjectName(String projectName) { + return projectName != null && !projectName.isEmpty() + && !projectName.startsWith("."); + } + + public static void checkValidProjectName(String projectName) { + Preconditions.checkArgument(isValidProjectName(projectName), + "[%s] invalid project name", projectName); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/ResourceUtil.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/ResourceUtil.java new file mode 100644 index 0000000000..57c22287b8 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/ResourceUtil.java @@ -0,0 +1,32 @@ +package uk.ac.ic.wlgitbridge.util; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +public class ResourceUtil { + + /** + * Creates a copy of a resource folder. Mainly used for testing to prevent + * the original folder from being mangled. + * + * It will have the same name as the original. + * @param resource the resource name, e.g. "/uk/ac/ic/wlgitbridge/file.txt" + * @param folderProvider function used to create the folder. + * E.g. TemporaryFolder from junit + * @return + * @throws IOException + */ + public static File copyOfFolderResource( + String resource, + FunctionT folderProvider + ) throws IOException { + File original + = new File(ResourceUtil.class.getResource(resource).getFile()); + File tmp = folderProvider.apply(original.getName()); + FileUtils.copyDirectory(original, tmp); + return tmp; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Tar.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Tar.java new file mode 100644 index 0000000000..6de56e7acf --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Tar.java @@ -0,0 +1,220 @@ +package uk.ac.ic.wlgitbridge.util; + +import com.google.api.client.repackaged.com.google.common.base.Preconditions; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; +import org.apache.commons.compress.utils.IOUtils; +import org.apache.commons.io.FileUtils; + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Tar utilities. + * + * The resource returned by zip and tar are treated as unowned. + * + * The resource given to unzip is treated as unowned. + * + * Caller is responsible for all resources. + */ +public class Tar { + public static class gzip { + + public static InputStream zip( + File fileOrDir + ) throws IOException { + return zip(fileOrDir, null); + } + + public static InputStream zip( + File fileOrDir, + long[] sizePtr + ) throws IOException { + File tmp = File.createTempFile(fileOrDir.getName(), ".tar.gz"); + tmp.deleteOnExit(); + OutputStream target = new FileOutputStream(tmp); + /* Closes target */ + try (OutputStream gz = new GzipCompressorOutputStream(target)) { + tarTo(fileOrDir, gz); + } catch (IOException e) { + tmp.delete(); + throw e; + } + if (sizePtr != null) { + sizePtr[0] = tmp.length(); + } + return new DeletingFileInputStream(tmp); + } + + public static void unzip( + InputStream targz, + File parentDir + ) throws IOException { + /* GzipCompressorInputStream does not need closing + Closing it would close targz which we should not do */ + InputStream tar = new GzipCompressorInputStream(targz); + untar(tar, parentDir); + } + } + + public static class bz2 { + + public static InputStream zip( + File fileOrDir + ) throws IOException { + return zip(fileOrDir, null); + } + + public static InputStream zip( + File fileOrDir, + long[] sizePtr + ) throws IOException { + File tmp = File.createTempFile(fileOrDir.getName(), ".tar.bz2"); + tmp.deleteOnExit(); + OutputStream target = new FileOutputStream(tmp); + /* Closes target */ + try (OutputStream bzip2 = new BZip2CompressorOutputStream(target)) { + tarTo(fileOrDir, bzip2); + } catch (IOException e) { + tmp.delete(); + throw e; + } + if (sizePtr != null) { + sizePtr[0] = tmp.length(); + } + return new DeletingFileInputStream(tmp); + } + + public static void unzip( + InputStream tarbz2, + File parentDir + ) throws IOException { + /* BZip2CompressorInputStream does not need closing + Closing it would close tarbz2 which we should not do */ + InputStream tar = new BZip2CompressorInputStream(tarbz2); + untar(tar, parentDir); + } + + } + + private Tar() {} + + public static InputStream tar(File fileOrDir) throws IOException { + File tmp = File.createTempFile(fileOrDir.getName(), ".tar"); + tmp.deleteOnExit(); + try (FileOutputStream target = new FileOutputStream(tmp)) { + tarTo(fileOrDir, target); + return new DeletingFileInputStream(tmp); + } catch (IOException e) { + tmp.delete(); + throw e; + } + } + + public static void tarTo( + File fileOrDir, + OutputStream target + ) throws IOException { + try (TarArchiveOutputStream tout = new TarArchiveOutputStream(target)) { + addTarEntry( + tout, + Paths.get(fileOrDir.getParentFile().getAbsolutePath()), + fileOrDir + ); + } + } + + public static void untar( + InputStream tar, + File parentDir + ) throws IOException { + TarArchiveInputStream tin = new TarArchiveInputStream(tar); + ArchiveEntry e; + while ((e = tin.getNextEntry()) != null) { + File f = new File(parentDir, e.getName()); + f.setLastModified(e.getLastModifiedDate().getTime()); + f.getParentFile().mkdirs(); + if (e.isDirectory()) { + f.mkdir(); + continue; + } + long size = e.getSize(); + checkFileSize(size); + try (OutputStream out = new FileOutputStream(f)) { + /* TarInputStream pretends each + entry's EOF is the stream's EOF */ + IOUtils.copy(tin, out); + } + } + } + + private static void checkFileSize(long size) { + Preconditions.checkArgument( + size >= 0 && size <= Integer.MAX_VALUE, + "file too big (" + size + " B): " + + "tarTo should have thrown an IOException" + ); + } + + private static void addTarEntry( + TarArchiveOutputStream tout, + Path base, + File fileOrDir + ) throws IOException { + if (fileOrDir.isDirectory()) { + addTarDir(tout, base, fileOrDir); + } else if (fileOrDir.isFile()) { + addTarFile(tout, base, fileOrDir); + } else { + throw new IllegalArgumentException( + "invalid file or dir: " + fileOrDir + ); + } + } + + private static void addTarDir( + TarArchiveOutputStream tout, + Path base, + File dir + ) throws IOException { + Preconditions.checkArgument(dir.isDirectory()); + String name = base.relativize( + Paths.get(dir.getAbsolutePath()) + ).toString(); + ArchiveEntry entry = tout.createArchiveEntry(dir, name); + tout.putArchiveEntry(entry); + tout.closeArchiveEntry(); + for (File f : dir.listFiles()) { + addTarEntry(tout, base, f); + } + } + + private static void addTarFile( + TarArchiveOutputStream tout, + Path base, + File file + ) throws IOException { + Preconditions.checkArgument( + file.isFile(), + "given file" + + " is not file: %s", file); + checkFileSize(file.length()); + String name = base.relativize( + Paths.get(file.getAbsolutePath()) + ).toString(); + ArchiveEntry entry = tout.createArchiveEntry(file, name); + tout.putArchiveEntry(entry); + try (InputStream in = new FileInputStream(file)) { + IOUtils.copy(in, tout); + } + tout.closeArchiveEntry(); + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/TimerUtils.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/TimerUtils.java new file mode 100644 index 0000000000..5b2893870e --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/TimerUtils.java @@ -0,0 +1,23 @@ +package uk.ac.ic.wlgitbridge.util; + +import java.util.TimerTask; + +/** + * Created by winston on 23/08/2016. + */ +public class TimerUtils { + + public static TimerTask makeTimerTask(Runnable lamb) { + return new TimerTask() { + @Override + public void run() { + try { + lamb.run(); + } catch (Throwable t) { + Log.warn("Error on timer", t); + } + } + }; + } + +} diff --git a/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Util.java b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Util.java new file mode 100644 index 0000000000..1727ee9510 --- /dev/null +++ b/services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/util/Util.java @@ -0,0 +1,205 @@ +package uk.ac.ic.wlgitbridge.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.io.*; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Created by Winston on 19/11/14. + */ +public class Util { + + private static String SERVICE_NAME; + private static String HOSTNAME; + private static int PORT; + private static String POSTBACK_URL; + private static final DateFormat dateFormat + = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS"); + + public static String entries(int entries) { + if (entries == 1) { + return "entry"; + } else { + return "entries"; + } + } + + public static int booleanToInt(boolean b) { + if (b) { + return 1; + } else { + return 0; + } + } + + public static boolean intToBoolean(int i) { + return i != 0; + } + + private static String removeAllSuffix(String str, String suffix) { + int lastIndexOfSuffix; + String result = str; + while ((lastIndexOfSuffix = result.lastIndexOf(suffix)) > -1) { + result = result.substring(0, lastIndexOfSuffix); + } + return result; + } + + /* removeAllSuffixes("something.git///", "/", ".git") => "something" */ + public static String removeAllSuffixes(String str, String... suffixes) { + String result = str; + for (String suffix : suffixes) { + result = removeAllSuffix(result, suffix); + } + return result; + } + + public static String getContentsOfReader( + BufferedReader reader + ) throws IOException { + StringBuilder sb = new StringBuilder(); + for (String line; (line = reader.readLine()) != null;) { + sb.append(line); + } + return sb.toString(); + } + + public static void setServiceName(String serviceName) { + SERVICE_NAME = serviceName; + } + + public static String getServiceName() { + return SERVICE_NAME; + } + + public static int getPort() { + return PORT; + } + + public static void setPort(int port) { + PORT = port; + } + + public static void setPostbackURL(String postbackURL) { + POSTBACK_URL = postbackURL; + } + + public static String getPostbackURL() { + return POSTBACK_URL; + } + + public static void deleteDirectory(File directory) { + if (directory != null) { + deleteInDirectory(directory); + directory.delete(); + } + } + + public static void deleteInDirectory(File directory) { + if (directory != null) { + deleteInDirectoryApartFrom(directory); + } + } + + public static void deleteInDirectoryApartFrom( + File directory, + String... apartFrom + ) { + if (directory != null) { + Set excluded = new HashSet<>(Arrays.asList(apartFrom)); + File [] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (!excluded.contains(file.getName())) { + if (file.isDirectory()) { + deleteInDirectory(file); + } + file.delete(); + Log.info("Deleted file: {}", file.getAbsolutePath()); + } + } + } + } + } + + public static List linesFromStream( + InputStream stream, + int skip, + String trimSuffix + ) throws IOException { + List lines = new ArrayList<>(); + BufferedReader reader = new BufferedReader( + new InputStreamReader(stream) + ); + String line; + for (int i = 0; i < skip; i++) { + reader.readLine(); + } + while ((line = reader.readLine()) != null) { + String trim = line.trim(); + trim = trim.replaceAll("\\p{C}", ""); + int endIndex = trim.lastIndexOf(trimSuffix); + if (endIndex >= 0) { + trim = trim.substring(0, endIndex); + } + lines.add(trim); + } + return lines; + } + + public static String fromStream( + InputStream stream, + int skip + ) throws IOException { + BufferedReader reader = new BufferedReader( + new InputStreamReader(stream) + ); + StringBuilder out = new StringBuilder(); + String newLine = System.getProperty("line.separator"); + String line; + for (int i = 0; i < skip; i++) { + reader.readLine(); + } + while ((line = reader.readLine()) != null) { + out.append(line); + out.append(newLine); + } + return out.toString(); + } + + public static String fromStream(InputStream stream) throws IOException { + return fromStream(stream, 0); + } + + public static String getCodeFromResponse(JsonObject json) { + String code = "error"; + JsonElement codeElement = json.get("code"); + + if (codeElement == null) { + String error = "Unexpected error"; + Log.warn("Unexpected response from API:"); + Log.warn(json.toString()); + Log.warn("End of response"); + JsonElement statusElement = json.get("status"); + if (statusElement != null) { + String status = statusElement.getAsString(); + if (status.equals("422")) { + error = "Unprocessable entity"; + } else if (status.equals("404")) { + error = "Not found"; + } else if (status.equals("403")) { + error = "Forbidden"; + } + } + throw new RuntimeException(error); + } else { + code = codeElement.getAsString(); + } + return code; + } + +} diff --git a/services/git-bridge/src/main/resources/logback.xml b/services/git-bridge/src/main/resources/logback.xml new file mode 100644 index 0000000000..a5103a3405 --- /dev/null +++ b/services/git-bridge/src/main/resources/logback.xml @@ -0,0 +1,32 @@ + + + + System.out + + INFO + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{0}: %msg%n + + + + + + System.err + + WARN + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{0}: %msg%n + + + + + + + + + + + + diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/application/WLGitBridgeIntegrationTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/application/WLGitBridgeIntegrationTest.java new file mode 100644 index 0000000000..6eaa776562 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/application/WLGitBridgeIntegrationTest.java @@ -0,0 +1,1131 @@ +package uk.ac.ic.wlgitbridge.application; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import static org.asynchttpclient.Dsl.*; + +import org.apache.http.HttpResponse; +import org.apache.http.ParseException; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.HttpEntity; +import org.apache.http.util.EntityUtils; +import org.apache.http.ParseException; + +import org.asynchttpclient.*; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import uk.ac.ic.wlgitbridge.bridge.swap.job.SwapJobConfig; +import uk.ac.ic.wlgitbridge.snapshot.servermock.server.MockSnapshotServer; +import uk.ac.ic.wlgitbridge.snapshot.servermock.state.SnapshotAPIState; +import uk.ac.ic.wlgitbridge.snapshot.servermock.state.SnapshotAPIStateBuilder; +import uk.ac.ic.wlgitbridge.snapshot.servermock.util.FileUtil; +import uk.ac.ic.wlgitbridge.util.Util; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.*; + +/** + * Created by Winston on 11/01/15. + */ +public class WLGitBridgeIntegrationTest { + + private Runtime runtime = Runtime.getRuntime(); + + private Map> states = new HashMap>() {{ + put("canCloneARepository", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/canCloneARepository/state/state.json")).build()); + }}); + put("canCloneMultipleRepositories", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/canCloneMultipleRepositories/state/state.json")).build()); + }}); + put("cannotCloneAProtectedProjectWithoutAuthentication", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/cannotCloneAProtectedProjectWithoutAuthentication/state/state.json")).build()); + }}); + put("cannotCloneA4xxProject", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/cannotCloneA4xxProject/state/state.json")).build()); + }}); + put("cannotCloneAMissingProject", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/cannotCloneAMissingProject/state/state.json")).build()); + }}); + put("canPullAModifiedTexFile", new HashMap() {{ + put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullAModifiedTexFile/base/state.json")).build()); + put("withModifiedTexFile", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullAModifiedTexFile/withModifiedTexFile/state.json")).build()); + }}); + put("canPullADeletedTexFile", new HashMap() {{ + put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullADeletedTexFile/base/state.json")).build()); + put("withDeletedTexFile", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullADeletedTexFile/withDeletedTexFile/state.json")).build()); + }}); + put("canPullAModifiedBinaryFile", new HashMap() {{ + put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullAModifiedBinaryFile/base/state.json")).build()); + put("withModifiedBinaryFile", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullAModifiedBinaryFile/withModifiedBinaryFile/state.json")).build()); + }}); + put("canPullADeletedBinaryFile", new HashMap() {{ + put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullADeletedBinaryFile/base/state.json")).build()); + put("withDeletedBinaryFile", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullADeletedBinaryFile/withDeletedBinaryFile/state.json")).build()); + }}); + put("canPullADuplicateBinaryFile", new HashMap() {{ + put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullADuplicateBinaryFile/base/state.json")).build()); + put("withDuplicateBinaryFile", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullADuplicateBinaryFile/withDuplicateBinaryFile/state.json")).build()); + }}); + put("canCloneDuplicateBinaryFiles", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/canCloneDuplicateBinaryFiles/state/state.json")).build()); + }}); + put("canPullUpdatedBinaryFiles", new HashMap() {{ + put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullUpdatedBinaryFiles/base/state.json")).build()); + put("withUpdatedBinaryFiles", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/state.json")).build()); + }}); + put("canPullAModifiedNestedFile", new HashMap() {{ + put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullAModifiedNestedFile/base/state.json")).build()); + put("withModifiedNestedFile", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullAModifiedNestedFile/withModifiedNestedFile/state.json")).build()); + }}); + put("canPullDeletedNestedFiles", new HashMap() {{ + put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullDeletedNestedFiles/base/state.json")).build()); + put("withDeletedNestedFiles", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullDeletedNestedFiles/withDeletedNestedFiles/state.json")).build()); + }}); + put("canPushFilesSuccessfully", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/canPushFilesSuccessfully/state/state.json")).build()); + }}); + put("pushFailsOnFirstStageOutOfDate", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/pushFailsOnFirstStageOutOfDate/state/state.json")).build()); + }}); + put("pushFailsOnSecondStageOutOfDate", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/pushFailsOnSecondStageOutOfDate/state/state.json")).build()); + }}); + put("pushFailsOnInvalidFiles", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/pushFailsOnInvalidFiles/state/state.json")).build()); + }}); + put("pushFailsOnInvalidProject", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/pushFailsOnInvalidProject/state/state.json")).build()); + }}); + put("pushFailsOnUnexpectedError", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/pushFailsOnUnexpectedError/state/state.json")).build()); + }}); + put("pushSucceedsAfterRemovingInvalidFiles", new HashMap() {{ + put("invalidState", new SnapshotAPIStateBuilder(getResourceAsStream("/pushSucceedsAfterRemovingInvalidFiles/invalidState/state.json")).build()); + put("validState", new SnapshotAPIStateBuilder(getResourceAsStream("/pushSucceedsAfterRemovingInvalidFiles/validState/state.json")).build()); + }}); + put("canServePushedFiles", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/canServePushedFiles/state/state.json")).build()); + }}); + put("wlgbCanSwapProjects", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/wlgbCanSwapProjects/state/state.json")).build()); + }}); + put("pushSubmoduleFailsWithInvalidGitRepo", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/pushSubmoduleFailsWithInvalidGitRepo/state/state.json")).build()); + }}); + put("canMigrateRepository", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/canMigrateRepository/state/state.json")).build()); + }}); + put("skipMigrationWhenMigratedFromMissing", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/skipMigrationWhenMigratedFromMissing/state/state.json")).build()); + }}); + put("canCloneAMigratedRepositoryWithoutChanges", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/canCloneAMigratedRepositoryWithoutChanges/state/state.json")).build()); + }}); + put("rejectV1Repository", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/rejectV1Repository/state/state.json")).build()); + }}); + put("cannotCloneAHasDotGitProject", new HashMap() {{ + put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/cannotCloneAHasDotGitProject/state/state.json")).build()); + }}); + put("canPullIgnoredForceAddedFile", new HashMap() {{ + put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullIgnoredForceAddedFile/base/state.json")).build()); + put("withUpdatedMainFile", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullIgnoredForceAddedFile/withUpdatedMainFile/state.json")).build()); + }}); + }}; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private MockSnapshotServer server; + private GitBridgeApp wlgb; + private File dir; + + @Before + public void setUp() throws Exception { + dir = folder.newFolder(); + } + + @After + public void tearDown() { + server.stop(); + wlgb.stop(); + } + + private void gitConfig(File dir) throws IOException, InterruptedException { + assertEquals(0, runtime.exec( + "git config user.name TEST", null, dir + ).waitFor()); + assertEquals(0, runtime.exec( + "git config user.email test@test.com", null, dir + ).waitFor()); + assertEquals(0, runtime.exec( + "git config push.default matching", null, dir + ).waitFor()); + } + + private File gitClone(String repositoryName, int port, File dir) throws IOException, InterruptedException { + String repo = "git clone http://127.0.0.1:" + port + "/" + repositoryName + ".git"; + Process gitProcess = runtime.exec(repo, null, dir); + int exitCode = gitProcess.waitFor(); + if (exitCode != 0) { + System.err.println("git clone failed. Dumping stderr and stdout."); + System.err.println( + IOUtils.toString( + gitProcess.getErrorStream(), + StandardCharsets.UTF_8 + ) + ); + System.err.println( + IOUtils.toString( + gitProcess.getInputStream(), + StandardCharsets.UTF_8 + ) + ); + fail("git clone failed"); + } + File repositoryDir = new File(dir, repositoryName); + gitConfig(repositoryDir); + return repositoryDir; + } + + private void gitInit(File dir) throws IOException, InterruptedException { + assertEquals(0, runtime.exec( + "git init", null, dir + ).waitFor()); + gitConfig(dir); + } + + private void gitAdd(File dir) throws IOException, InterruptedException { + assertEquals(0, runtime.exec( + "git add -A", null, dir + ).waitFor()); + } + + private void gitCommit( + File dir, String msg + ) throws IOException, InterruptedException { + assertEquals(0, runtime.exec( + "git commit -m \"" + msg + "\"", null, dir + ).waitFor()); + } + + private Process gitPush(File dir) throws IOException, InterruptedException { + return gitPush(dir, 0); + } + + private Process gitPush( + File dir, int exit + ) throws IOException, InterruptedException { + Process ret = runtime.exec( + "git push", null, dir + ); + assertEquals(exit, ret.waitFor()); + return ret; + } + + private void gitPull(File dir) throws IOException, InterruptedException { + assertEquals(0, runtime.exec( + "git pull", null, dir + ).waitFor()); + } + + @Test + public void canCloneARepository() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3857, getResource("/canCloneARepository").toFile()); + server.start(); + server.setState(states.get("canCloneARepository").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33857, 3857) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33857, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canCloneARepository/state/testproj"), testprojDir.toPath())); + } + + @Test + public void canCloneMultipleRepositories() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3858, getResource("/canCloneMultipleRepositories").toFile()); + server.start(); + server.setState(states.get("canCloneMultipleRepositories").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33858, 3858) + }); + wlgb.run(); + File testproj1Dir = gitClone("testproj1", 33858, dir); + File testproj2Dir = gitClone("testproj2", 33858, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canCloneMultipleRepositories/state/testproj1"), testproj1Dir.toPath())); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canCloneMultipleRepositories/state/testproj2"), testproj2Dir.toPath())); + } + + @Test + public void canPullAModifiedTexFile() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3859, getResource("/canPullAModifiedTexFile").toFile()); + server.start(); + server.setState(states.get("canPullAModifiedTexFile").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33859, 3859) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33859, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullAModifiedTexFile/base/testproj"), testprojDir.toPath())); + server.setState(states.get("canPullAModifiedTexFile").get("withModifiedTexFile")); + gitPull(testprojDir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullAModifiedTexFile/withModifiedTexFile/testproj"), testprojDir.toPath())); + } + + @Test + public void canPullADeletedTexFile() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3860, getResource("/canPullADeletedTexFile").toFile()); + server.start(); + server.setState(states.get("canPullADeletedTexFile").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33860, 3860) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33860, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullADeletedTexFile/base/testproj"), testprojDir.toPath())); + server.setState(states.get("canPullADeletedTexFile").get("withDeletedTexFile")); + gitPull(testprojDir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullADeletedTexFile/withDeletedTexFile/testproj"), testprojDir.toPath())); + } + + @Test + public void canPullAModifiedBinaryFile() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3862, getResource("/canPullAModifiedBinaryFile").toFile()); + server.start(); + server.setState(states.get("canPullAModifiedBinaryFile").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33862, 3862) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33862, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullAModifiedBinaryFile/base/testproj"), testprojDir.toPath())); + server.setState(states.get("canPullAModifiedBinaryFile").get("withModifiedBinaryFile")); + gitPull(testprojDir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj"), testprojDir.toPath())); + } + + @Test + public void canPullADeletedBinaryFile() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3863, getResource("/canPullADeletedBinaryFile").toFile()); + server.start(); + server.setState(states.get("canPullADeletedBinaryFile").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33863, 3863) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33863, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullADeletedBinaryFile/base/testproj"), testprojDir.toPath())); + server.setState(states.get("canPullADeletedBinaryFile").get("withDeletedBinaryFile")); + gitPull(testprojDir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullADeletedBinaryFile/withDeletedBinaryFile/testproj"), testprojDir.toPath())); + } + + @Test + public void canPullADuplicateBinaryFile() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(4001, getResource("/canPullADuplicateBinaryFile").toFile()); + server.start(); + server.setState(states.get("canPullADuplicateBinaryFile").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(44001, 4001) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 44001, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullADuplicateBinaryFile/base/testproj"), testprojDir.toPath())); + server.setState(states.get("canPullADuplicateBinaryFile").get("withDuplicateBinaryFile")); + gitPull(testprojDir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj"), testprojDir.toPath())); + } + + @Test + public void canCloneDuplicateBinaryFiles() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(4002, getResource("/canCloneDuplicateBinaryFiles").toFile()); + server.start(); + server.setState(states.get("canCloneDuplicateBinaryFiles").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(44002, 4002) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 44002, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canCloneDuplicateBinaryFiles/state/testproj"), testprojDir.toPath())); + } + + @Test + public void canPullUpdatedBinaryFiles() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(4003, getResource("/canPullUpdatedBinaryFiles").toFile()); + server.start(); + server.setState(states.get("canPullUpdatedBinaryFiles").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(44003, 4003) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 44003, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullUpdatedBinaryFiles/base/testproj"), testprojDir.toPath())); + server.setState(states.get("canPullUpdatedBinaryFiles").get("withUpdatedBinaryFiles")); + gitPull(testprojDir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj"), testprojDir.toPath())); + } + + @Test + public void canPullAModifiedNestedFile() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3864, getResource("/canPullAModifiedNestedFile").toFile()); + server.start(); + server.setState(states.get("canPullAModifiedNestedFile").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33864, 3864) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33864, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullAModifiedNestedFile/base/testproj"), testprojDir.toPath())); + server.setState(states.get("canPullAModifiedNestedFile").get("withModifiedNestedFile")); + gitPull(testprojDir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullAModifiedNestedFile/withModifiedNestedFile/testproj"), testprojDir.toPath())); + } + + @Test + public void canPullDeletedNestedFiles() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3865, getResource("/canPullDeletedNestedFiles").toFile()); + server.start(); + server.setState(states.get("canPullDeletedNestedFiles").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33865, 3865) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33865, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullDeletedNestedFiles/base/testproj"), testprojDir.toPath())); + server.setState(states.get("canPullDeletedNestedFiles").get("withDeletedNestedFiles")); + gitPull(testprojDir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj"), testprojDir.toPath())); + } + + @Test + public void canPushFilesSuccessfully() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3866, getResource("/canPushFilesSuccessfully").toFile()); + server.start(); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33866, 3866) + }); + wlgb.run(); + server.setState(states.get("canPushFilesSuccessfully").get("state")); + File testprojDir = gitClone("testproj", 33866, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPushFilesSuccessfully/state/testproj"), testprojDir.toPath())); + assertEquals(0, runtime.exec("touch push.tex", null, testprojDir).waitFor()); + gitAdd(testprojDir); + gitCommit(testprojDir, "push"); + gitPush(testprojDir); + } + + private static final String EXPECTED_OUT_PUSH_OUT_OF_DATE_FIRST = + "error: failed to push some refs to 'http://127.0.0.1:33867/testproj.git'\n" + + "hint: Updates were rejected because the tip of your current branch is behind\n" + + "hint: its remote counterpart. Integrate the remote changes (e.g.\n" + + "hint: 'git pull ...') before pushing again.\n" + + "hint: See the 'Note about fast-forwards' in 'git push --help' for details.\n"; + + @Test + public void pushFailsOnFirstStageOutOfDate() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3867, getResource("/pushFailsOnFirstStageOutOfDate").toFile()); + server.start(); + server.setState(states.get("pushFailsOnFirstStageOutOfDate").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33867, 3867) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33867, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/pushFailsOnFirstStageOutOfDate/state/testproj"), testprojDir.toPath())); + runtime.exec("touch push.tex", null, testprojDir).waitFor(); + gitAdd(testprojDir); + gitCommit(testprojDir, "push"); + Process push = gitPush(testprojDir, 1); + assertEquals(EXPECTED_OUT_PUSH_OUT_OF_DATE_FIRST, Util.fromStream(push.getErrorStream(), 2)); + } + + private static final String EXPECTED_OUT_PUSH_OUT_OF_DATE_SECOND = + "error: failed to push some refs to 'http://127.0.0.1:33868/testproj.git'\n" + + "hint: Updates were rejected because the tip of your current branch is behind\n" + + "hint: its remote counterpart. Integrate the remote changes (e.g.\n" + + "hint: 'git pull ...') before pushing again.\n" + + "hint: See the 'Note about fast-forwards' in 'git push --help' for details.\n"; + + @Test + public void pushFailsOnSecondStageOutOfDate() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3868, getResource("/pushFailsOnSecondStageOutOfDate").toFile()); + server.start(); + server.setState(states.get("pushFailsOnSecondStageOutOfDate").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33868, 3868) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33868, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/pushFailsOnSecondStageOutOfDate/state/testproj"), testprojDir.toPath())); + runtime.exec("touch push.tex", null, testprojDir).waitFor(); + gitAdd(testprojDir); + gitCommit(testprojDir, "push"); + Process push = gitPush(testprojDir, 1); + assertEquals(EXPECTED_OUT_PUSH_OUT_OF_DATE_SECOND, Util.fromStream(push.getErrorStream(), 2)); + } + + private static final List EXPECTED_OUT_PUSH_INVALID_FILES = Arrays.asList( + "remote: hint: You have 4 invalid files in your Overleaf project:", + "remote: hint: file1.invalid (error)", + "remote: hint: file2.exe (invalid file extension)", + "remote: hint: hello world.png (rename to: hello_world.png)", + "remote: hint: an image.jpg (rename to: an_image.jpg)", + "To http://127.0.0.1:33869/testproj.git", + "! [remote rejected] master -> master (invalid files)", + "error: failed to push some refs to 'http://127.0.0.1:33869/testproj.git'" + ); + + @Test + public void pushFailsOnInvalidFiles() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3869, getResource("/pushFailsOnInvalidFiles").toFile()); + server.start(); + server.setState(states.get("pushFailsOnInvalidFiles").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33869, 3869) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33869, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/pushFailsOnInvalidFiles/state/testproj"), testprojDir.toPath())); + runtime.exec("touch push.tex", null, testprojDir).waitFor(); + gitAdd(testprojDir); + gitCommit(testprojDir, "push"); + Process push = gitPush(testprojDir, 1); + List actual = Util.linesFromStream(push.getErrorStream(), 2, "[K"); + assertEquals(EXPECTED_OUT_PUSH_INVALID_FILES, actual); + } + + private static final List EXPECTED_OUT_PUSH_INVALID_PROJECT = Arrays.asList( + "remote: hint: project: no main file", + "remote: hint: The project would have no (editable) main .tex file.", + "To http://127.0.0.1:33870/testproj.git", + "! [remote rejected] master -> master (invalid project)", + "error: failed to push some refs to 'http://127.0.0.1:33870/testproj.git'" + ); + + @Test + public void pushFailsOnInvalidProject() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3870, getResource("/pushFailsOnInvalidProject").toFile()); + server.start(); + server.setState(states.get("pushFailsOnInvalidProject").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33870, 3870) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33870, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/pushFailsOnInvalidProject/state/testproj"), testprojDir.toPath())); + runtime.exec("touch push.tex", null, testprojDir).waitFor(); + gitAdd(testprojDir); + gitCommit(testprojDir, "push"); + Process push = gitPush(testprojDir, 1); + List actual = Util.linesFromStream(push.getErrorStream(), 2, "[K"); + assertEquals(EXPECTED_OUT_PUSH_INVALID_PROJECT, actual); + } + + private static final List EXPECTED_OUT_PUSH_UNEXPECTED_ERROR = Arrays.asList( + "remote: hint: There was an internal error with the Overleaf server.", + "remote: hint: Please contact Overleaf.", + "To http://127.0.0.1:33871/testproj.git", + "! [remote rejected] master -> master (Overleaf error)", + "error: failed to push some refs to 'http://127.0.0.1:33871/testproj.git'" + ); + + /* this one prints a stack trace */ + @Test + public void pushFailsOnUnexpectedError() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3871, getResource("/pushFailsOnUnexpectedError").toFile()); + server.start(); + server.setState(states.get("pushFailsOnUnexpectedError").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33871, 3871) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33871, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/pushFailsOnUnexpectedError/state/testproj"), testprojDir.toPath())); + runtime.exec("touch push.tex", null, testprojDir).waitFor(); + gitAdd(testprojDir); + gitCommit(testprojDir, "push"); + Process push = gitPush(testprojDir, 1); + List actual = Util.linesFromStream(push.getErrorStream(), 2, "[K"); + assertEquals(EXPECTED_OUT_PUSH_UNEXPECTED_ERROR, actual); + } + + private static final List EXPECTED_OUT_PUSH_INVALID_EXE_FILE = Arrays.asList( + "remote: error: invalid files", + "remote:", + "remote: hint: You have 1 invalid files in your Overleaf project:", + "remote: hint: file1.exe (invalid file extension)", + "To http://127.0.0.1:33872/testproj.git", + "! [remote rejected] master -> master (invalid files)", + "error: failed to push some refs to 'http://127.0.0.1:33872/testproj.git'" + ); + + @Test + public void pushSucceedsAfterRemovingInvalidFiles() throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer(3872, getResource("/pushSucceedsAfterRemovingInvalidFiles").toFile()); + server.start(); + server.setState(states.get("pushSucceedsAfterRemovingInvalidFiles").get("invalidState")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33872, 3872) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33872, dir); + + // try to push invalid file; it should fail + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/pushSucceedsAfterRemovingInvalidFiles/invalidState/testproj"), testprojDir.toPath())); + assertEquals(0, runtime.exec("touch file1.exe", null, testprojDir).waitFor()); + gitAdd(testprojDir); + gitCommit(testprojDir, "push"); + Process push = gitPush(testprojDir, 1); + List actual = Util.linesFromStream(push.getErrorStream(), 0, "[K"); + assertEquals(EXPECTED_OUT_PUSH_INVALID_EXE_FILE, actual); + + // remove invalid file and push again; it should succeed this time + assertEquals(0, runtime.exec("git rm file1.exe", null, testprojDir).waitFor()); + gitCommit(testprojDir, "remove_invalid_file"); + server.setState(states.get("pushSucceedsAfterRemovingInvalidFiles").get("validState")); + gitPush(testprojDir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/pushSucceedsAfterRemovingInvalidFiles/validState/testproj"), testprojDir.toPath())); + } + + @Test + public void canServePushedFiles() throws IOException, ExecutionException, InterruptedException { + // + // I don't think we can test this completely without some changes to the mock server, because we have no way + // of pausing the test while the push is in progress. Once the push is over, the file isn't actually there for + // us to fetch any more. We can however test the access and error conditions, which comprise most of the logic. + // + int gitBridgePort = 33873; + int mockServerPort = 3873; + + server = new MockSnapshotServer( + mockServerPort, getResource("/canServePushedFiles").toFile()); + server.start(); + server.setState(states.get("canServePushedFiles").get("state")); + + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + + File testprojDir = gitClone("testproj", gitBridgePort, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canServePushedFiles/state/testproj"), testprojDir.toPath())); + runtime.exec("touch push.tex", null, testprojDir).waitFor(); + gitAdd(testprojDir); + gitCommit(testprojDir, "push"); + gitPush(testprojDir); + + // With no key, we should get a 404. + String url = "http://127.0.0.1:" + gitBridgePort + "/api/testproj/push.tex"; + Response response = asyncHttpClient().prepareGet(url).execute().get(); + assertEquals(404, response.getStatusCode()); + + // With an invalid project and no key, we should get a 404. + url = "http://127.0.0.1:" + gitBridgePort + "/api/notavalidproject/push.tex"; + response = asyncHttpClient().prepareGet(url).execute().get(); + assertEquals(404, response.getStatusCode()); + + // With a bad key for a valid project, we should get a 404. + url = "http://127.0.0.1:" + gitBridgePort + "/api/testproj/push.tex?key=notavalidkey"; + response = asyncHttpClient().prepareGet(url).execute().get(); + assertEquals(404, response.getStatusCode()); + + // With a bad key for an invalid project, we should get a 404. + url = "http://127.0.0.1:" + gitBridgePort + "/api/notavalidproject/push.tex?key=notavalidkey"; + response = asyncHttpClient().prepareGet(url).execute().get(); + assertEquals(404, response.getStatusCode()); + + } + + @Test + public void wlgbCanSwapProjects( + ) throws IOException, GitAPIException, InterruptedException { + server = new MockSnapshotServer( + 3874, + getResource("/wlgbCanSwapProjects").toFile() + ); + server.start(); + server.setState(states.get("wlgbCanSwapProjects").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33874, 3874, new SwapJobConfig(1, 0, 0, 250, null)) + }); + wlgb.run(); + File rootGitDir = new File(wlgb.config.getRootGitDirectory()); + File testProj1ServerDir = new File(rootGitDir, "testproj1"); + File testProj2ServerDir = new File(rootGitDir, "testproj2"); + File testProj1Dir = gitClone("testproj1", 33874, dir); + assertTrue(testProj1ServerDir.exists()); + assertFalse(testProj2ServerDir.exists()); + gitClone("testproj2", 33874, dir); + while (testProj1ServerDir.exists()); + assertFalse(testProj1ServerDir.exists()); + assertTrue(testProj2ServerDir.exists()); + FileUtils.deleteDirectory(testProj1Dir); + gitClone("testproj1", 33874, dir); + while (testProj2ServerDir.exists()); + assertTrue(testProj1ServerDir.exists()); + assertFalse(testProj2ServerDir.exists()); + } + + private static final List EXPECTED_OUT_PUSH_SUBMODULE = Arrays.asList( + "remote: hint: Your Git repository contains a reference we cannot resolve.", + "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 { + server = new MockSnapshotServer(3875, getResource("/pushSubmoduleFailsWithInvalidGitRepo").toFile()); + server.start(); + server.setState(states.get("pushSubmoduleFailsWithInvalidGitRepo").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(33875, 3875) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", 33875, dir); + runtime.exec("mkdir sub", null, testprojDir).waitFor(); + File sub = new File(testprojDir, "sub"); + runtime.exec("touch sub.txt", null, sub).waitFor(); + gitInit(sub); + gitAdd(sub); + gitCommit(sub, "sub"); + gitAdd(testprojDir); + gitCommit(testprojDir, "push"); + Process push = gitPush(testprojDir, 1); + List actual = Util.linesFromStream(push.getErrorStream(), 2, "[K"); + assertEquals(EXPECTED_OUT_PUSH_SUBMODULE, actual); + } + + @Test + public void usesCustomErrorHandler( + ) throws IOException, ExecutionException, InterruptedException { + + int gitBridgePort = 33873; + int mockServerPort = 3873; + + server = new MockSnapshotServer( + mockServerPort, getResource("/canServePushedFiles").toFile()); + server.start(); + server.setState(states.get("canServePushedFiles").get("state")); + + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + + // With an invalid project and no key, we should get a 404, + // which is rendered by our custom error handler. + String url = "http://127.0.0.1:" + gitBridgePort + "/api/notavalidproject/main.tex"; + Response response = asyncHttpClient().prepareGet(url).execute().get(); + assertEquals(404, response.getStatusCode()); + assertEquals("{\"message\":\"HTTP error 404\"}", response.getResponseBody()); + + // With an unsupported URL outside the api, we should get a 500, + // which is rendered by our custom error handler. + url = "http://127.0.0.1:" + gitBridgePort + "/foo"; + response = asyncHttpClient().prepareGet(url).execute().get(); + assertEquals(500, response.getStatusCode()); + assertEquals("{\"message\":\"HTTP error 500\"}", response.getResponseBody()); + } + + @Test + public void cannotCloneAProtectedProjectWithoutAuthentication() throws IOException, GitAPIException, InterruptedException { + int gitBridgePort = 33883; + int mockServerPort = 3883; + + server = new MockSnapshotServer(mockServerPort, getResource("/cannotCloneAProtectedProjectWithoutAuthentication").toFile()); + server.start(); + server.setState(states.get("cannotCloneAProtectedProjectWithoutAuthentication").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + + wlgb.run(); + Process gitProcess = runtime.exec("git clone http://127.0.0.1:" + gitBridgePort + "/testproj.git", null, dir); + assertNotEquals(0, gitProcess.waitFor()); + } + + @Test + public void cannotCloneA4xxProject() throws IOException, GitAPIException, InterruptedException { + int gitBridgePort = 33879; + int mockServerPort = 3879; + + server = new MockSnapshotServer(mockServerPort, getResource("/cannotCloneA4xxProject").toFile()); + server.start(); + server.setState(states.get("cannotCloneA4xxProject").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + + wlgb.run(); + Process gitProcess = runtime.exec("git clone http://127.0.0.1:" + gitBridgePort + "/testproj.git", null, dir); + assertNotEquals(0, gitProcess.waitFor()); + } + + @Test + public void cannotCloneAMissingProject() throws IOException, GitAPIException, InterruptedException { + int gitBridgePort = 33880; + int mockServerPort = 3880; + + server = new MockSnapshotServer(mockServerPort, getResource("/cannotCloneAMissingProject").toFile()); + server.start(); + server.setState(states.get("cannotCloneAMissingProject").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + + wlgb.run(); + Process gitProcess = runtime.exec("git clone http://127.0.0.1:" + gitBridgePort + "/testproj.git", null, dir); + assertNotEquals(0, gitProcess.waitFor()); + } + + @Test + public void canMigrateRepository() throws IOException, GitAPIException, InterruptedException { + int gitBridgePort = 33881; + int mockServerPort = 3881; + server = new MockSnapshotServer(mockServerPort, getResource("/canMigrateRepository").toFile()); + server.start(); + server.setState(states.get("canMigrateRepository").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + File testprojDir = gitClone("testproj", gitBridgePort, dir); + File testprojDir2 = gitClone("testproj2", gitBridgePort, dir); + // Second project content is equal to content of the first + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canMigrateRepository/state/testproj"), testprojDir2.toPath())); + } + + @Test + public void skipMigrationWhenMigratedFromMissing() throws IOException, GitAPIException, InterruptedException { + int gitBridgePort = 33882; + int mockServerPort = 3882; + server = new MockSnapshotServer(mockServerPort, getResource("/skipMigrationWhenMigratedFromMissing").toFile()); + server.start(); + server.setState(states.get("skipMigrationWhenMigratedFromMissing").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + // don't clone the source project first + File testprojDir2 = gitClone("testproj2", gitBridgePort, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/skipMigrationWhenMigratedFromMissing/state/testproj2"), testprojDir2.toPath())); + } + + @Test + public void canCloneAMigratedRepositoryWithoutChanges() throws IOException, GitAPIException, InterruptedException { + int gitBridgePort = 33883; + int mockServerPort = 3883; + server = new MockSnapshotServer(mockServerPort, getResource("/canCloneAMigratedRepositoryWithoutChanges").toFile()); + server.start(); + server.setState(states.get("canCloneAMigratedRepositoryWithoutChanges").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + File testprojDir = gitClone("testproj_no_change", gitBridgePort, dir); + assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canCloneAMigratedRepositoryWithoutChanges/state/testproj_no_change"), testprojDir.toPath())); + } + + @Test + public void rejectV1Repository() throws IOException, GitAPIException, InterruptedException { + int gitBridgePort = 33884; + int mockServerPort = 3884; + server = new MockSnapshotServer(mockServerPort, getResource("/rejectV1Repository").toFile()); + server.start(); + server.setState(states.get("rejectV1Repository").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + Process gitProcess = runtime.exec("git clone http://127.0.0.1:" + gitBridgePort + "/1234bbccddff.git", null, dir); + assertNotEquals(0, gitProcess.waitFor()); + } + + @Test + public void cannotCloneAHasDotGitProject() throws IOException, GitAPIException, InterruptedException { + int gitBridgePort = 33885; + int mockServerPort = 3885; + + server = new MockSnapshotServer(mockServerPort, getResource("/cannotCloneAHasDotGitProject").toFile()); + server.start(); + server.setState(states.get("cannotCloneAHasDotGitProject").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + + wlgb.run(); + Process gitProcess = runtime.exec("git clone http://127.0.0.1:" + gitBridgePort + "/conflict.git", null, dir); + assertNotEquals(0, gitProcess.waitFor()); + wlgb.stop(); + } + + @Test + public void cannotCloneProjectWithSlash() throws IOException, GitAPIException, InterruptedException { + int gitBridgePort = 33886; + int mockServerPort = 3886; + + server = new MockSnapshotServer(mockServerPort, getResource("/canCloneARepository").toFile()); + server.start(); + server.setState(states.get("canCloneARepository").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + + wlgb.run(); + Process gitProcess = runtime.exec("git clone http://127.0.0.1:" + gitBridgePort + "/project/1234abcd", null, dir); + assertNotEquals(0, gitProcess.waitFor()); + + List actual = Util.linesFromStream(gitProcess.getErrorStream(), 0, ""); + assertEquals(Arrays.asList( + "Cloning into '1234abcd'...", + "remote: Invalid Project ID (must not have a '/project' prefix)", + "fatal: repository 'http://127.0.0.1:33886/project/1234abcd/' not found" + ), actual); + + wlgb.stop(); + } + + @Test + public void testStatusAndHealthCheckEndpoints() throws ClientProtocolException, IOException { + int gitBridgePort = 33887; + int mockServerPort = 3887; + server = new MockSnapshotServer(mockServerPort, getResource("/canCloneARepository").toFile()); + server.start(); + server.setState(states.get("canCloneARepository").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + HttpClient client = HttpClients.createDefault(); + String urlBase = "http://127.0.0.1:" + gitBridgePort; + // Status + HttpGet statusRequest = new HttpGet(urlBase+"/status"); + HttpResponse statusResponse = client.execute(statusRequest); + assertEquals(200, statusResponse.getStatusLine().getStatusCode()); + // Health Check + HttpGet healthCheckRequest = new HttpGet(urlBase+"/health_check"); + HttpResponse healthCheckResponse = client.execute(healthCheckRequest); + assertEquals(200, healthCheckResponse.getStatusLine().getStatusCode()); + } + + @Test + public void testStatusAndHealthCheckEndpointsWithTrailingSlash() throws ClientProtocolException, IOException { + int gitBridgePort = 33888; + int mockServerPort = 3888; + server = new MockSnapshotServer(mockServerPort, getResource("/canCloneARepository").toFile()); + server.start(); + server.setState(states.get("canCloneARepository").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + HttpClient client = HttpClients.createDefault(); + String urlBase = "http://127.0.0.1:" + gitBridgePort; + // Status + HttpGet statusRequest = new HttpGet(urlBase+"/status/"); + HttpResponse statusResponse = client.execute(statusRequest); + assertEquals(200, statusResponse.getStatusLine().getStatusCode()); + // Health Check + HttpGet healthCheckRequest = new HttpGet(urlBase+"/health_check/"); + HttpResponse healthCheckResponse = client.execute(healthCheckRequest); + assertEquals(200, healthCheckResponse.getStatusLine().getStatusCode()); + } + + @Test + public void testStatusAndHealthCheckEndpointsWithHead() throws ClientProtocolException, IOException { + int gitBridgePort = 33889; + int mockServerPort = 3889; + server = new MockSnapshotServer(mockServerPort, getResource("/canCloneARepository").toFile()); + server.start(); + server.setState(states.get("canCloneARepository").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + HttpClient client = HttpClients.createDefault(); + String urlBase = "http://127.0.0.1:" + gitBridgePort; + // Status + HttpHead statusRequest = new HttpHead(urlBase+"/status"); + HttpResponse statusResponse = client.execute(statusRequest); + assertEquals(200, statusResponse.getStatusLine().getStatusCode()); + // Health Check + HttpHead healthCheckRequest = new HttpHead(urlBase+"/health_check"); + HttpResponse healthCheckResponse = client.execute(healthCheckRequest); + assertEquals(200, healthCheckResponse.getStatusLine().getStatusCode()); + } + + @Test + public void gitLfsBatchEndpoint() throws ClientProtocolException, IOException, ParseException { + int gitBridgePort = 33890; + int mockServerPort = 3890; + server = new MockSnapshotServer(mockServerPort, getResource("/canCloneARepository").toFile()); + server.start(); + server.setState(states.get("canCloneARepository").get("state")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + HttpClient client = HttpClients.createDefault(); + String urlBase = "http://127.0.0.1:" + gitBridgePort; + HttpPost gitLfsRequest = new HttpPost(urlBase+"/5f2419407929eb0026641967.git/info/lfs/objects/batch"); + HttpResponse gitLfsResponse = client.execute(gitLfsRequest); + assertEquals(422, gitLfsResponse.getStatusLine().getStatusCode()); + HttpEntity entity = gitLfsResponse.getEntity(); + String responseString = EntityUtils.toString(entity, "UTF-8"); + assertTrue(responseString.contains("Git LFS is not supported on Overleaf")); + } + + @Test + public void canPullIgnoredForceAddedFile() throws IOException, InterruptedException { + int gitBridgePort = 33891; + int mockServerPort = 3891; + server = new MockSnapshotServer(mockServerPort, getResource("/canPullIgnoredForceAddedFile").toFile()); + server.start(); + server.setState(states.get("canPullIgnoredForceAddedFile").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + File testProjDir = gitClone("testproj", gitBridgePort, dir); + File one = new File(testProjDir, "sub/one.txt"); + one.createNewFile(); + FileWriter fw = new FileWriter(one.getPath()); + fw.write("1"); + fw.close(); + assertEquals(0, runtime.exec( + "git add -A -f", null, testProjDir + ).waitFor()); + gitCommit(testProjDir, "push"); + gitPush(testProjDir); + server.setState(states.get("canPullIgnoredForceAddedFile").get("withUpdatedMainFile")); + gitPull(testProjDir); + File f = new File(testProjDir.getPath() + "/sub/one.txt"); + assertTrue(f.exists()); + } + + @Test + public void canPullIgnoredFileFromOverleaf() throws IOException, InterruptedException { + int gitBridgePort = 33892; + int mockServerPort = 3892; + server = new MockSnapshotServer(mockServerPort, getResource("/canPullIgnoredForceAddedFile").toFile()); + server.start(); + server.setState(states.get("canPullIgnoredForceAddedFile").get("base")); + wlgb = new GitBridgeApp(new String[] { + makeConfigFile(gitBridgePort, mockServerPort) + }); + wlgb.run(); + File testProjDir = gitClone("testproj", gitBridgePort, dir); + server.setState(states.get("canPullIgnoredForceAddedFile").get("withUpdatedMainFile")); + gitPull(testProjDir); + File f = new File(testProjDir.getPath() + "/sub/one.txt"); + assertTrue(f.exists()); + } + + private String makeConfigFile( + int port, + int apiPort + ) throws IOException { + return makeConfigFile(port, apiPort, null); + } + + private String makeConfigFile( + int port, + int apiPort, + SwapJobConfig swapCfg + ) throws IOException { + File wlgb = folder.newFolder(); + File config = folder.newFile(); + PrintWriter writer = new PrintWriter(config); + String cfgStr = + "{\n" + + " \"port\": " + port + ",\n" + + " \"bindIp\": \"127.0.0.1\",\n" + + " \"idleTimeout\": 30000,\n" + + " \"rootGitDirectory\": \"" + + wlgb.getAbsolutePath() + + "\",\n" + + " \"apiBaseUrl\": \"http://127.0.0.1:" + + apiPort + + "/api/v0\",\n" + + " \"postbackBaseUrl\": \"http://127.0.0.1:" + + port + + "\",\n" + + " \"serviceName\": \"Overleaf\",\n" + + " \"oauth2\": {\n" + + " \"oauth2ClientID\": \"clientID\",\n" + + " \"oauth2ClientSecret\": \"oauth2 client secret\",\n" + + " \"oauth2Server\": \"https://www.overleaf.com\"\n" + + " }"; + if (swapCfg != null) { + cfgStr += ",\n" + + " \"swapStore\": {\n" + + " \"type\": \"memory\",\n" + + " \"awsAccessKey\": null,\n" + + " \"awsSecret\": null,\n" + + " \"s3BucketName\": \"com.overleaf.testbucket\"\n" + + " },\n" + + " \"swapJob\": {\n" + + " \"minProjects\": " + + swapCfg.getMinProjects() + + ",\n" + + " \"lowGiB\": " + + swapCfg.getLowGiB() + + ",\n" + + " \"highGiB\": " + + swapCfg.getHighGiB() + + ",\n" + + " \"intervalMillis\": " + + swapCfg.getIntervalMillis() + + "\n" + + " }\n"; + } + cfgStr += "}\n"; + writer.print(cfgStr); + writer.close(); + return config.getAbsolutePath(); + } + + private Path getResource(String path) { + return Paths.get( + "src/test/resources/" + + "uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest" + path + ); + } + + private InputStream getResourceAsStream(String path) { + try { + return new FileInputStream(getResource(path).toFile()); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + private static String withoutWhitespace(String s) { + return s.replaceAll("\\s",""); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/application/config/ConfigTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/application/config/ConfigTest.java new file mode 100644 index 0000000000..02d70def0e --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/application/config/ConfigTest.java @@ -0,0 +1,107 @@ +package uk.ac.ic.wlgitbridge.application.config; + +import org.junit.Test; + +import java.io.Reader; +import java.io.StringReader; + +import static org.junit.Assert.*; + +/** + * Created by winston on 25/10/15. + */ +public class ConfigTest { + + @Test + public void testConstructWithOauth() { + Reader reader = new StringReader("{\n" + + " \"port\": 80,\n" + + " \"bindIp\": \"127.0.0.1\",\n" + + " \"idleTimeout\": 30000,\n" + + " \"rootGitDirectory\": \"/var/wlgb/git\",\n" + + " \"apiBaseUrl\": \"http://127.0.0.1:60000/api/v0\",\n" + + " \"postbackBaseUrl\": \"http://127.0.0.1\",\n" + + " \"serviceName\": \"Overleaf\",\n" + + " \"oauth2\": {\n" + + " \"oauth2ClientID\": \"clientID\",\n" + + " \"oauth2ClientSecret\": \"oauth2 client secret\",\n" + + " \"oauth2Server\": \"https://www.overleaf.com\"\n" + + " }\n" + + "}\n"); + Config config = new Config(reader); + assertEquals(80, config.getPort()); + assertEquals("/var/wlgb/git", config.getRootGitDirectory()); + assertEquals("http://127.0.0.1:60000/api/v0/", config.getAPIBaseURL()); + assertEquals("http://127.0.0.1/", config.getPostbackURL()); + assertEquals("Overleaf", config.getServiceName()); + assertTrue(config.isUsingOauth2()); + assertEquals("clientID", config.getOauth2().getOauth2ClientID()); + assertEquals("oauth2 client secret", config.getOauth2().getOauth2ClientSecret()); + assertEquals("https://www.overleaf.com", config.getOauth2().getOauth2Server()); + } + + @Test (expected = AssertionError.class) + public void testConstructWithoutOauth() { + Reader reader = new StringReader("{\n" + + " \"port\": 80,\n" + + " \"bindIp\": \"127.0.0.1\",\n" + + " \"idleTimeout\": 30000,\n" + + " \"rootGitDirectory\": \"/var/wlgb/git\",\n" + + " \"apiBaseUrl\": \"http://127.0.0.1:60000/api/v0\",\n" + + " \"postbackBaseUrl\": \"http://127.0.0.1\",\n" + + " \"serviceName\": \"Overleaf\",\n" + + " \"oauth2\": null\n" + + "}\n"); + Config config = new Config(reader); + assertEquals(80, config.getPort()); + assertEquals("/var/wlgb/git", config.getRootGitDirectory()); + assertEquals("http://127.0.0.1:60000/api/v0/", config.getAPIBaseURL()); + assertEquals("http://127.0.0.1/", config.getPostbackURL()); + assertEquals("Overleaf", config.getServiceName()); + assertFalse(config.isUsingOauth2()); + config.getOauth2(); + } + + @Test + public void asSanitised() throws Exception { + Reader reader = new StringReader("{\n" + + " \"port\": 80,\n" + + " \"bindIp\": \"127.0.0.1\",\n" + + " \"idleTimeout\": 30000,\n" + + " \"rootGitDirectory\": \"/var/wlgb/git\",\n" + + " \"apiBaseUrl\": \"http://127.0.0.1:60000/api/v0\",\n" + + " \"postbackBaseUrl\": \"http://127.0.0.1\",\n" + + " \"serviceName\": \"Overleaf\",\n" + + " \"oauth2\": {\n" + + " \"oauth2ClientID\": \"my oauth2 client id\",\n" + + " \"oauth2ClientSecret\": \"my oauth2 client secret\",\n" + + " \"oauth2Server\": \"https://www.overleaf.com\"\n" + + " }\n" + + "}\n"); + Config config = new Config(reader); + String expected = "{\n" + + " \"port\": 80,\n" + + " \"bindIp\": \"127.0.0.1\",\n" + + " \"idleTimeout\": 30000,\n" + + " \"rootGitDirectory\": \"/var/wlgb/git\",\n" + + " \"apiBaseURL\": \"http://127.0.0.1:60000/api/v0/\",\n" + + " \"postbackURL\": \"http://127.0.0.1/\",\n" + + " \"serviceName\": \"Overleaf\",\n" + + " \"oauth2\": {\n" + + " \"oauth2ClientID\": \"\",\n" + + " \"oauth2ClientSecret\": \"\",\n" + + " \"oauth2Server\": \"https://www.overleaf.com\"\n" + + " },\n" + + " \"repoStore\": null,\n" + + " \"swapStore\": null,\n" + + " \"swapJob\": null,\n" + + " \"sqliteHeapLimitBytes\": 0\n" + + "}"; + assertEquals( + "sanitised config did not hide sensitive fields", + expected, + config.getSanitisedString() + ); + } + +} \ No newline at end of file diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/BridgeTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/BridgeTest.java new file mode 100644 index 0000000000..9026973d49 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/BridgeTest.java @@ -0,0 +1,110 @@ +package uk.ac.ic.wlgitbridge.bridge; + +import org.junit.Before; +import org.junit.Test; +import uk.ac.ic.wlgitbridge.application.config.Config; +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.db.ProjectState; +import uk.ac.ic.wlgitbridge.bridge.gc.GcJob; +import uk.ac.ic.wlgitbridge.bridge.lock.ProjectLock; +import uk.ac.ic.wlgitbridge.bridge.repo.ProjectRepo; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.bridge.resource.ResourceCache; +import uk.ac.ic.wlgitbridge.bridge.snapshot.SnapshotApiFacade; +import uk.ac.ic.wlgitbridge.bridge.swap.job.SwapJob; +import uk.ac.ic.wlgitbridge.bridge.swap.store.SwapStore; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; +import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult; + +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Optional; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +/** + * Created by winston on 20/08/2016. + */ +public class BridgeTest { + + private Bridge bridge; + + private ProjectLock lock; + private RepoStore repoStore; + private DBStore dbStore; + private SwapStore swapStore; + private SnapshotApiFacade snapshotAPI; + private ResourceCache resourceCache; + private SwapJob swapJob; + private GcJob gcJob; + + @Before + public void setup() { + lock = mock(ProjectLock.class); + repoStore = mock(RepoStore.class); + dbStore = mock(DBStore.class); + swapStore = mock(SwapStore.class); + snapshotAPI = mock(SnapshotApiFacade.class); + resourceCache = mock(ResourceCache.class); + swapJob = mock(SwapJob.class); + gcJob = mock(GcJob.class); + bridge = new Bridge( + new Config( + 0, + "", + 0, + "", + "", + "", + "", + null, + null, + null, + null, + 0), + lock, + repoStore, + dbStore, + swapStore, + swapJob, + gcJob, + snapshotAPI, + resourceCache + ); + } + + @Test + public void shutdownStopsSwapAndGcJobs() { + bridge.startBackgroundJobs(); + verify(swapJob).start(); + verify(gcJob).start(); + bridge.doShutdown(); + verify(swapJob).stop(); + verify(gcJob).stop(); + } + + @Test + public void updatingRepositorySetsLastAccessedTime( + ) throws IOException, GitUserException { + ProjectRepo repo = mock(ProjectRepo.class); + when(repoStore.getExistingRepo("asdf")).thenReturn(repo); + when(dbStore.getProjectState("asdf")).thenReturn(ProjectState.PRESENT); + when(snapshotAPI.projectExists(Optional.empty(), "asdf")).thenReturn(true); + when( + snapshotAPI.getDoc(Optional.empty(), "asdf") + ).thenReturn(Optional.of(mock(GetDocResult.class))); + when( + snapshotAPI.getSnapshots( + any(), + any(), + anyInt() + ) + ).thenReturn(new ArrayDeque<>()); + bridge.getUpdatedRepo(Optional.empty(), "asdf"); + verify(dbStore).setLastAccessedTime(eq("asdf"), any()); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SqliteDBStoreTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SqliteDBStoreTest.java new file mode 100644 index 0000000000..9c88db88d8 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/SqliteDBStoreTest.java @@ -0,0 +1,163 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite; + +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import uk.ac.ic.wlgitbridge.bridge.db.ProjectState; + +import java.io.IOException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Created by winston on 23/08/2016. + */ +public class SqliteDBStoreTest { + + private SqliteDBStore dbStore; + + @Before + public void setup() throws IOException { + TemporaryFolder tmpFolder = new TemporaryFolder(); + tmpFolder.create(); + dbStore = new SqliteDBStore(tmpFolder.newFile("dbStore.db")); + } + + @Test + public void testGetNumProjects() { + assertEquals(0, dbStore.getNumProjects()); + dbStore.setLatestVersionForProject("asdf", 1); + assertEquals(1, dbStore.getNumProjects()); + dbStore.setLatestVersionForProject("asdf1", 2); + assertEquals(2, dbStore.getNumProjects()); + dbStore.setLatestVersionForProject("asdf1", 3); + assertEquals(2, dbStore.getNumProjects()); + } + + @Test + public void swapTableStartsOutEmpty() { + assertNull(dbStore.getOldestUnswappedProject()); + } + + @Test + public void testGetOldestUnswappedProject() { + dbStore.setLatestVersionForProject("older", 3); + dbStore.setLastAccessedTime( + "older", + Timestamp.valueOf( + LocalDateTime.now().minus(5, ChronoUnit.SECONDS) + ) + ); + dbStore.setLatestVersionForProject("asdf", 1); + dbStore.setLastAccessedTime( + "asdf", + Timestamp.valueOf( + LocalDateTime.now().minus(1, ChronoUnit.SECONDS) + ) + ); + assertEquals("older", dbStore.getOldestUnswappedProject()); + dbStore.setLastAccessedTime( + "older", + Timestamp.valueOf( + LocalDateTime.now() + ) + ); + assertEquals("asdf", dbStore.getOldestUnswappedProject()); + } + + @Test + public void swapAndRestore() { + String projectName = "something"; + String compression = "bzip2"; + dbStore.setLatestVersionForProject(projectName, 42); + dbStore.swap(projectName, compression); + assertNull(dbStore.getOldestUnswappedProject()); + assertEquals(dbStore.getSwapCompression(projectName), compression); + // and restore + dbStore.restore(projectName); + assertEquals(dbStore.getSwapCompression(projectName), null); + } + + @Test + public void noOldestProjectIfAllEvicted() { + dbStore.setLatestVersionForProject("older", 3); + dbStore.swap("older", "bzip2"); + assertNull(dbStore.getOldestUnswappedProject()); + } + + @Test + public void nullLastAccessedTimesDoNotCount() { + dbStore.setLatestVersionForProject("older", 2); + dbStore.setLastAccessedTime( + "older", + Timestamp.valueOf( + LocalDateTime.now().minus(5, ChronoUnit.SECONDS) + ) + ); + dbStore.setLatestVersionForProject("newer", 3); + dbStore.setLastAccessedTime( + "newer", + Timestamp.valueOf( + LocalDateTime.now() + ) + ); + assertEquals("older", dbStore.getOldestUnswappedProject()); + dbStore.swap("older", "bzip2"); + assertEquals("newer", dbStore.getOldestUnswappedProject()); + } + + @Test + public void missingProjectLastAccessedTimeCanBeSet() { + dbStore.setLatestVersionForProject("asdf", 1); + dbStore.setLastAccessedTime( + "asdf", + Timestamp.valueOf(LocalDateTime.now()) + ); + assertEquals("asdf", dbStore.getOldestUnswappedProject()); + } + + @Test + public void testGetNumUnswappedProjects() { + dbStore.setLatestVersionForProject("asdf", 1); + dbStore.setLastAccessedTime( + "asdf", + Timestamp.valueOf(LocalDateTime.now()) + ); + assertEquals(1, dbStore.getNumUnswappedProjects()); + dbStore.swap( + "asdf", + "bzip2" + ); + assertEquals(0, dbStore.getNumUnswappedProjects()); + } + + @Test + public void projectStateIsNotPresentIfNotInDBAtAll() { + assertEquals( + ProjectState.NOT_PRESENT, + dbStore.getProjectState("asdf") + ); + } + + @Test + public void projectStateIsPresentIfProjectHasLastAccessed() { + dbStore.setLatestVersionForProject("asdf", 1); + dbStore.setLastAccessedTime( + "asdf", + Timestamp.valueOf(LocalDateTime.now()) + ); + assertEquals(ProjectState.PRESENT, dbStore.getProjectState("asdf")); + } + + @Test + public void projectStateIsSwappedIfLastAccessedIsNull() { + dbStore.setLatestVersionForProject("asdf", 1); + dbStore.swap("asdf", "bzip2"); + assertEquals(ProjectState.SWAPPED, dbStore.getProjectState("asdf")); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/delete/DeleteFilesForProjectSQLUpdateTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/delete/DeleteFilesForProjectSQLUpdateTest.java new file mode 100644 index 0000000000..4cf2c21d57 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/db/sqlite/update/delete/DeleteFilesForProjectSQLUpdateTest.java @@ -0,0 +1,24 @@ +package uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.delete; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class DeleteFilesForProjectSQLUpdateTest { + + @Test + public void testGetSQL() { + DeleteFilesForProjectSQLUpdate update = + new DeleteFilesForProjectSQLUpdate( + "projname", + "path1", + "path2" + ); + assertEquals( + "DELETE FROM `url_index_store` " + + "WHERE `project_name` = ? " + + "AND path IN (?, ?);\n", + update.getSQL() + ); + } + +} \ No newline at end of file diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/gc/GcJobImplTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/gc/GcJobImplTest.java new file mode 100644 index 0000000000..091e6b5692 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/gc/GcJobImplTest.java @@ -0,0 +1,120 @@ +package uk.ac.ic.wlgitbridge.bridge.gc; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.stubbing.OngoingStubbing; +import uk.ac.ic.wlgitbridge.bridge.lock.LockGuard; +import uk.ac.ic.wlgitbridge.bridge.lock.ProjectLock; +import uk.ac.ic.wlgitbridge.bridge.repo.ProjectRepo; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.data.ProjectLockImpl; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.*; + +/** + * Created by winston on 16/02/2017. + */ +public class GcJobImplTest { + + RepoStore repoStore = mock(RepoStore.class); + + ProjectLock locks; + + GcJobImpl gcJob; + + @Before + public void setup() { + locks = new ProjectLockImpl(); + gcJob = new GcJobImpl(repoStore, locks, 5); + } + + @After + public void teardown() { + gcJob.stop(); + } + + @Test + public void addedProjectsAreAllEventuallyGcedOnce() throws Exception { + int numProjects = 5; + /* Make the mocks, make expectations, and keep a reference to them */ + final OngoingStubbing[] o = new OngoingStubbing[] { + when(repoStore.getExistingRepo(anyString())) + }; + List mockRepos = IntStream.range( + 0, numProjects + ).mapToObj(i -> + String.valueOf((char) ('a' + i)) + ).map(proj -> { + gcJob.queueForGc(proj); + ProjectRepo mockRepo = mock(ProjectRepo.class); + o[0] = o[0].thenReturn(mockRepo); + return mockRepo; + }).collect(Collectors.toList()); + CompletableFuture fut = gcJob.waitForRun(); + gcJob.start(); + fut.join(); + for (ProjectRepo mock : mockRepos) { + verify(mock).runGC(); + verify(mock).deleteIncomingPacks(); + } + /* Nothing should happen on the next run */ + when(repoStore.getExistingRepo(anyString())).thenThrow( + new IllegalStateException() + ); + gcJob.waitForRun().join(); + } + + @Test + public void cannotOverlapGcRuns() throws Exception { + CompletableFuture runningForever = new CompletableFuture<>(); + gcJob.onPostGc(() -> { + try { + /* Pretend the GC is taking forever */ + runningForever.join(); + } catch (Throwable e) { + runningForever.completeExceptionally(e); + } + }); + CompletableFuture fut = gcJob.waitForRun(); + gcJob.start(); + fut.join(); + CompletableFuture ranAgain = new CompletableFuture<>(); + gcJob.onPreGc(() -> ranAgain.complete(null)); + /* Should not run again any time soon */ + for (int i = 0; i < 50; ++i) { + assertFalse(ranAgain.isDone()); + /* The gc interval is 5 ms, so 50 1ms sleeps should be more than + enough without making the test slow */ + Thread.sleep(1); + } + assertFalse(runningForever.isCompletedExceptionally()); + } + + @Test + public void willNotGcProjectUntilItIsUnlocked() + throws InterruptedException, IOException { + ProjectRepo repo = mock(ProjectRepo.class); + when(repoStore.getExistingRepo(anyString())).thenReturn(repo); + gcJob.onPostGc(gcJob::stop); + gcJob.queueForGc("a"); + CompletableFuture fut = gcJob.waitForRun(); + try (LockGuard __ = locks.lockGuard("a")) { + gcJob.start(); + for (int i = 0; i < 50; ++i) { + assertFalse(fut.isDone()); + Thread.sleep(1); + } + } + /* Now that we've released the lock, fut should complete */ + fut.join(); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest.java new file mode 100644 index 0000000000..fae03e50a4 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest.java @@ -0,0 +1,93 @@ +package uk.ac.ic.wlgitbridge.bridge.repo; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import uk.ac.ic.wlgitbridge.util.Files; + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Optional; + +import static org.junit.Assert.*; + +/** + * Created by winston on 23/08/2016. + */ +public class FSGitRepoStoreTest { + + public static File makeTempRepoDir( + TemporaryFolder tmpFolder, + String name + ) throws IOException { + File tmp = tmpFolder.newFolder(name); + Path rootdir = Paths.get( + "src/test/resources/uk/ac/ic/wlgitbridge/" + + "bridge/repo/FSGitRepoStoreTest/rootdir" + ); + FileUtils.copyDirectory(rootdir.toFile(), tmp); + Files.renameAll(tmp, "DOTgit", ".git"); + return tmp; + } + + private FSGitRepoStore repoStore; + private File original; + + @Before + public void setup() throws IOException { + TemporaryFolder tmpFolder = new TemporaryFolder(); + tmpFolder.create(); + File tmp = makeTempRepoDir(tmpFolder, "rootdir"); + original = tmpFolder.newFolder("original"); + FileUtils.copyDirectory(tmp, original); + repoStore = new FSGitRepoStore(tmp.getAbsolutePath(), Optional.empty()); + } + + @Test + public void testPurgeNonexistentProjects() { + File toDelete = new File( + repoStore.getRootDirectory(), "idontexist" + ); + File wlgb = new File(repoStore.getRootDirectory(), ".wlgb"); + assertTrue(toDelete.exists()); + assertTrue(wlgb.exists()); + repoStore.purgeNonexistentProjects(Arrays.asList("proj1", "proj2")); + assertFalse(toDelete.exists()); + assertTrue(wlgb.exists()); + } + + @Test + public void totalSizeShouldChangeWhenFilesAreCreatedAndDeleted() + throws IOException { + long old = repoStore.totalSize(); + File temp = new File(repoStore.getRootDirectory(), "__temp.txt"); + try ( + OutputStream out = new FileOutputStream( + temp + ) + ) { + out.write(new byte[16 * 1024 * 1024]); + } + long new_ = repoStore.totalSize(); + assertTrue(new_ > old); + assertTrue(temp.delete()); + long new__ = repoStore.totalSize(); + assertTrue(new__ < new_); + } + + @Test + public void zipAndUnzipShouldBeTheSame() throws IOException { + File expected = new File(original, "proj1"); + File actual = new File(repoStore.getRootDirectory(), "proj1"); + assertTrue(Files.contentsAreEqual(expected, actual)); + InputStream zipped = repoStore.bzip2Project("proj1"); + repoStore.remove("proj1"); + assertFalse(actual.exists()); + repoStore.unbzip2Project("proj1", zipped); + assertTrue(Files.contentsAreEqual(expected, actual)); + } + +} \ No newline at end of file diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest.java new file mode 100644 index 0000000000..3f3504b784 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest.java @@ -0,0 +1,200 @@ +package uk.ac.ic.wlgitbridge.bridge.repo; + +import com.google.api.client.repackaged.com.google.common.base.Preconditions; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents; +import uk.ac.ic.wlgitbridge.data.filestore.RawFile; +import uk.ac.ic.wlgitbridge.data.filestore.RepositoryFile; +import uk.ac.ic.wlgitbridge.snapshot.servermock.util.FileUtil; +import uk.ac.ic.wlgitbridge.util.Files; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.function.Supplier; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThan; +import static org.junit.Assert.*; + +/** + * Created by winston on 08/10/2016. + */ +public class GitProjectRepoTest { + + public static File makeTempRepoDir( + TemporaryFolder tmpFolder, + String name + ) throws IOException { + File tmp = tmpFolder.newFolder(name); + Path rootdir = Paths.get( + "src/test/resources/uk/ac/ic/wlgitbridge/" + + "bridge/repo/GitProjectRepoTest/rootdir" + ); + FileUtils.copyDirectory(rootdir.toFile(), tmp); + Files.renameAll(tmp, "DOTgit", ".git"); + return tmp; + } + + private File rootdir; + FSGitRepoStore repoStore; + GitProjectRepo repo; + GitProjectRepo badGitignore; + GitProjectRepo incoming; + GitProjectRepo withoutIncoming; + + @Rule + public TemporaryFolder tmpFolder = new TemporaryFolder(); + + @Before + public void setup() throws IOException { + rootdir = makeTempRepoDir(tmpFolder, "rootdir"); + repoStore = new FSGitRepoStore(rootdir.getAbsolutePath(), Optional.empty()); + repo = fromExistingDir("repo"); + badGitignore = fromExistingDir("badgitignore"); + incoming = fromExistingDir("incoming"); + withoutIncoming = fromExistingDir("without_incoming"); + } + + private GitProjectRepo fromExistingDir(String dir) throws IOException { + GitProjectRepo ret = GitProjectRepo.fromName(dir); + ret.useExistingRepository(repoStore); + return ret; + } + + private GitDirectoryContents makeDirContents( + String... contents + ) { + Preconditions.checkArgument(contents.length % 2 == 0); + List files = new ArrayList<>(contents.length / 2); + for (int i = 0; i + 1 < contents.length; i += 2) { + files.add( + new RepositoryFile( + contents[i], + contents[i + 1].getBytes(StandardCharsets.UTF_8) + ) + ); + } + return new GitDirectoryContents( + files, + repoStore.getRootDirectory(), + "repo", + "Winston Li", + "git@winston.li", + "Commit Message", + new Date() + ); + } + + @Test + public void deletingIgnoredFileOnAppDeletesFromTheRepo( + ) throws IOException { + GitDirectoryContents contents = makeDirContents( + ".gitignore", + "*.ignored\n" + ); + repo.commitAndGetMissing(contents); + repo.resetHard(); + File dir = repo.getDotGitDir(); + assertEquals( + new HashSet(Arrays.asList(".git", ".gitignore")), + new HashSet(Arrays.asList(dir.list())) + ); + } + + @Test + public void addingIgnoredFilesOnAppAddsToTheRepo() throws IOException { + GitDirectoryContents contents = makeDirContents( + ".gitignore", + "*.ignored\n", + "file1.ignored", + "", + "file1.txt", + "", + "file2.txt", + "", + "added.ignored", + "" + ); + repo.commitAndGetMissing(contents); + repo.resetHard(); + assertEquals( + new HashSet(Arrays.asList( + ".git", + ".gitignore", + "file1.ignored", + "file1.txt", + "file2.txt", + "added.ignored" + )), + new HashSet(Arrays.asList(repo.getDotGitDir().list())) + ); + } + + @Test + public void badGitignoreShouldNotThrow() throws IOException { + GitDirectoryContents contents = makeDirContents( + ".gitignore", + "*.ignored\n", + "file1.ignored", + "", + "file1.txt", + "", + "file2.txt", + "", + "added.ignored", + "" + ); + badGitignore.commitAndGetMissing(contents); + } + + private static long repoSize(ProjectRepo repo) { + return FileUtils.sizeOfDirectory(repo.getProjectDir()); + } + + @Test + public void runGCReducesTheSizeOfARepoWithGarbage() throws IOException { + long beforeSize = repoSize(repo); + repo.runGC(); + long afterSize = repoSize(repo); + assertThat(beforeSize, lessThan(afterSize)); + + } + + @Test + public void runGCDoesNothingOnARepoWithoutGarbage() throws IOException { + repo.runGC(); + long beforeSize = repoSize(repo); + repo.runGC(); + long afterSize = repoSize(repo); + assertThat(beforeSize, equalTo(afterSize)); + } + + @Test + public void deleteIncomingPacksDeletesIncomingPacks() throws IOException { + Supplier dirsAreEq = () -> FileUtil.directoryDeepEquals( + incoming.getProjectDir(), withoutIncoming.getProjectDir() + ); + assertFalse(dirsAreEq.get()); + incoming.deleteIncomingPacks(); + assertTrue(dirsAreEq.get()); + } + + @Test + public void deleteIncomingPacksOnDirWithoutIncomingPacksDoesNothing() + throws IOException { + File actual = withoutIncoming.getProjectDir(); + File expected = tmpFolder.newFolder(); + FileUtils.copyDirectory(actual, expected); + withoutIncoming.deleteIncomingPacks(); + assertTrue(FileUtil.directoryDeepEquals(actual, expected)); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/resource/UrlResourceCacheTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/resource/UrlResourceCacheTest.java new file mode 100644 index 0000000000..1c814770cd --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/resource/UrlResourceCacheTest.java @@ -0,0 +1,123 @@ +package uk.ac.ic.wlgitbridge.bridge.resource; + +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import org.junit.Test; +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.util.CastUtil; +import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException; +import uk.ac.ic.wlgitbridge.io.http.ning.NingHttpClientFacade; +import uk.ac.ic.wlgitbridge.util.FunctionT; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; + +public class UrlResourceCacheTest { + + private static String PROJ = "proj"; + + private static String URL = "http://localhost/file.jpg"; + + private static String NEW_PATH = "file1.jpg"; + + private final NingHttpClientFacade http = mock(NingHttpClientFacade.class); + + private final DBStore dbStore = mock(DBStore.class); + + private final UrlResourceCache cache = new UrlResourceCache(dbStore, http); + + private static HttpHeaders withContentLength(long cl) { + return new DefaultHttpHeaders().add("Content-Length", String.valueOf(cl)); + } + + private void respondWithContentLength(long cl, long actual) + throws ExecutionException { + when(http.get(any(), any())).thenAnswer(invoc -> { + Object[] args = invoc.getArguments(); + //noinspection unchecked + ((FunctionT< + HttpHeaders, Boolean, SizeLimitExceededException + >) args[1]).apply(withContentLength(cl)); + return new byte[CastUtil.assumeInt(actual)]; + }); + } + + private void respondWithContentLength(long cl) throws ExecutionException { + respondWithContentLength(cl, cl); + } + + private void getWithMaxLength(Optional max) + throws IOException, SizeLimitExceededException { + cache.get( + PROJ, URL, NEW_PATH, new HashMap<>(), new HashMap<>(), max); + } + + private void getUrl(String url) throws IOException, SizeLimitExceededException { + cache.get(PROJ, url, NEW_PATH, new HashMap<>(), new HashMap<>(), Optional.empty()); + } + + private void getWithMaxLength(long max) + throws IOException, SizeLimitExceededException { + getWithMaxLength(Optional.of(max)); + } + + private void getWithoutLimit() + throws IOException, SizeLimitExceededException { + getWithMaxLength(Optional.empty()); + } + + @Test + public void getDoesNotThrowWhenContentLengthLT() throws Exception { + respondWithContentLength(1); + getWithMaxLength(2); + } + + @Test + public void getDoesNotThrowWhenContentLengthEQ() throws Exception { + respondWithContentLength(2); + getWithMaxLength(2); + } + + @Test (expected = SizeLimitExceededException.class) + public void getThrowsSizeLimitExceededWhenContentLengthGT() + throws Exception { + respondWithContentLength(3); + getWithMaxLength(2); + } + + @Test + public void getWithEmptyContentIsValid() throws Exception { + respondWithContentLength(0); + getWithMaxLength(0); + } + + @Test + public void getWithoutLimitDoesNotThrow() throws Exception { + respondWithContentLength(Integer.MAX_VALUE, 0); + getWithoutLimit(); + } + + @Test (expected = SizeLimitExceededException.class) + public void getThrowsIfActualContentTooBig() throws Exception { + respondWithContentLength(0, 10); + getWithMaxLength(5); + } + + @Test + public void tokenIsRemovedFromCacheKey() throws Exception { + String url = "http://history.overleaf.com/projects/1234/blobs/abdef?token=secretencryptedstuff&_path=test.tex"; + String cacheKey = "http://history.overleaf.com/projects/1234/blobs/abdef?token=REMOVED&_path=test.tex"; + respondWithContentLength(123); + getUrl(url); + verify(dbStore).getPathForURLInProject(PROJ, cacheKey); + verify(dbStore).addURLIndexForProject(PROJ, cacheKey, NEW_PATH); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJobImplTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJobImplTest.java new file mode 100644 index 0000000000..3e9aa2e491 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/swap/job/SwapJobImplTest.java @@ -0,0 +1,166 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.job; + +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SqliteDBStore; +import uk.ac.ic.wlgitbridge.bridge.lock.ProjectLock; +import uk.ac.ic.wlgitbridge.bridge.repo.FSGitRepoStore; +import uk.ac.ic.wlgitbridge.bridge.repo.FSGitRepoStoreTest; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.bridge.swap.store.InMemorySwapStore; +import uk.ac.ic.wlgitbridge.bridge.swap.store.SwapStore; +import uk.ac.ic.wlgitbridge.data.ProjectLockImpl; + +import java.io.IOException; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Created by winston on 20/08/2016. + */ +public class SwapJobImplTest { + + private SwapJobImpl swapJob; + + private ProjectLock lock; + private RepoStore repoStore; + private DBStore dbStore; + private SwapStore swapStore; + + @Before + public void setup() throws IOException { + TemporaryFolder tmpFolder = new TemporaryFolder(); + tmpFolder.create(); + lock = new ProjectLockImpl(); + repoStore = new FSGitRepoStore( + FSGitRepoStoreTest.makeTempRepoDir( + tmpFolder, + "repostore" + ).getAbsolutePath(), + 100_000, + FileUtils::sizeOfDirectory + ); + dbStore = new SqliteDBStore(tmpFolder.newFile()); + dbStore.setLatestVersionForProject("proj1", 0); + dbStore.setLatestVersionForProject("proj2", 0); + dbStore.setLastAccessedTime( + "proj1", + Timestamp.valueOf(LocalDateTime.now()) + ); + dbStore.setLastAccessedTime( + "proj2", + Timestamp.valueOf( + LocalDateTime.now().minus(1, ChronoUnit.SECONDS) + ) + ); + swapStore = new InMemorySwapStore(); + swapJob = new SwapJobImpl( + 1, + 15000, + 30000, + Duration.ofMillis(100), + SwapJob.CompressionMethod.Bzip2, + lock, + repoStore, + dbStore, + swapStore + ); + } + + @After + public void teardown() { + if(swapJob != null) { + swapJob.stop(); + } + } + + private void waitASecond() { + try { Thread.sleep(1 * 1000); } catch (Exception _e) {} + } + + @Test + public void startingTimerAlwaysCausesASwap() { + swapJob.lowWatermarkBytes = 16384; + swapJob.interval = Duration.ofHours(1); + assertEquals(0, swapJob.swaps.get()); + swapJob.start(); + do { waitASecond(); } while (swapJob.swaps.get() <= 0); + assertTrue(swapJob.swaps.get() > 0); + } + + @Test + public void swapsHappenEveryInterval() { + swapJob.lowWatermarkBytes = 16384; + assertEquals(0, swapJob.swaps.get()); + swapJob.start(); + do { waitASecond(); } while (swapJob.swaps.get() <= 1); + assertTrue(swapJob.swaps.get() > 1); + } + + @Test + public void noProjectsGetSwappedWhenUnderHighWatermark() { + swapJob.highWatermarkBytes = 65536; + assertEquals(2, dbStore.getNumUnswappedProjects()); + swapJob.start(); + do { waitASecond(); } while (swapJob.swaps.get() < 1); + assertEquals(2, dbStore.getNumUnswappedProjects()); + } + + @Test + public void correctProjGetSwappedWhenOverHighWatermark( + ) throws IOException { + swapJob.lowWatermarkBytes = 16384; + assertEquals(2, dbStore.getNumUnswappedProjects()); + assertEquals("proj2", dbStore.getOldestUnswappedProject()); + swapJob.start(); + do { waitASecond(); } while (swapJob.swaps.get() < 1); + assertEquals(1, dbStore.getNumUnswappedProjects()); + assertEquals("proj1", dbStore.getOldestUnswappedProject()); + assertEquals("bzip2", dbStore.getSwapCompression("proj2")); + swapJob.restore("proj2"); + assertEquals(null, dbStore.getSwapCompression("proj2")); + int numSwaps = swapJob.swaps.get(); + do { waitASecond(); } while (swapJob.swaps.get() <= numSwaps); + assertEquals(1, dbStore.getNumUnswappedProjects()); + assertEquals("proj2", dbStore.getOldestUnswappedProject()); + } + + @Test + public void swapCompressionGzip() throws IOException { + swapJob = new SwapJobImpl( + 1, + 15000, + 30000, + Duration.ofMillis(100), + SwapJob.CompressionMethod.Gzip, + lock, + repoStore, + dbStore, + swapStore + ); + swapJob.lowWatermarkBytes = 16384; + assertEquals(2, dbStore.getNumUnswappedProjects()); + assertEquals("proj2", dbStore.getOldestUnswappedProject()); + swapJob.start(); + do { waitASecond(); } while (swapJob.swaps.get() < 1); + assertEquals(1, dbStore.getNumUnswappedProjects()); + assertEquals("proj1", dbStore.getOldestUnswappedProject()); + assertEquals("gzip", dbStore.getSwapCompression("proj2")); + swapJob.restore("proj2"); + assertEquals(null, dbStore.getSwapCompression("proj2")); + int numSwaps = swapJob.swaps.get(); + do { waitASecond(); } while (swapJob.swaps.get() <= numSwaps); + assertEquals(1, dbStore.getNumUnswappedProjects()); + assertEquals("proj2", dbStore.getOldestUnswappedProject()); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/swap/store/InMemorySwapStoreTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/swap/store/InMemorySwapStoreTest.java new file mode 100644 index 0000000000..7e257f2305 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/swap/store/InMemorySwapStoreTest.java @@ -0,0 +1,96 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.store; + +import org.apache.commons.io.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import uk.ac.ic.wlgitbridge.bridge.swap.store.InMemorySwapStore; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import static org.junit.Assert.assertArrayEquals; + +/** + * Created by winston on 23/08/2016. + */ +public class InMemorySwapStoreTest { + + private final InMemorySwapStore swapStore = new InMemorySwapStore(); + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Test + public void downloadingNonExistentFileThrows() { + exception.expect(IllegalArgumentException.class); + swapStore.openDownloadStream("asdf"); + } + + @Test + public void canDownloadUploadedFiles() throws IOException { + byte[] proj1Contents = "helloproj1".getBytes(); + byte[] proj2Contents = "asdfproj2".getBytes(); + swapStore.upload( + "proj1", + new ByteArrayInputStream(proj1Contents), + proj1Contents.length + ); + swapStore.upload( + "proj2", + new ByteArrayInputStream(proj2Contents), + proj2Contents.length + ); + assertArrayEquals( + proj1Contents, + IOUtils.toByteArray(swapStore.openDownloadStream("proj1")) + ); + assertArrayEquals( + proj2Contents, + IOUtils.toByteArray(swapStore.openDownloadStream("proj2")) + ); + } + + @Test + public void uploadingForTheSameProjectOverwritesTheFile( + ) throws IOException { + byte[] proj1Contents = "helloproj1".getBytes(); + byte[] proj1NewContents = "goodbyeproj1".getBytes(); + swapStore.upload( + "proj1", + new ByteArrayInputStream(proj1Contents), + proj1Contents.length + ); + assertArrayEquals( + proj1Contents, + IOUtils.toByteArray(swapStore.openDownloadStream("proj1")) + ); + swapStore.upload( + "proj1", + new ByteArrayInputStream(proj1NewContents), + proj1NewContents.length + ); + assertArrayEquals( + proj1NewContents, + IOUtils.toByteArray(swapStore.openDownloadStream("proj1")) + ); + } + + @Test + public void canRemoveFiles() throws IOException { + byte[] projContents = "total garbage".getBytes(); + swapStore.upload( + "proj", + new ByteArrayInputStream(projContents), + projContents.length + ); + assertArrayEquals( + projContents, + IOUtils.toByteArray(swapStore.openDownloadStream("proj")) + ); + swapStore.remove("proj"); + exception.expect(IllegalArgumentException.class); + swapStore.openDownloadStream("proj"); + } + +} \ No newline at end of file diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/swap/store/S3SwapStoreTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/swap/store/S3SwapStoreTest.java new file mode 100644 index 0000000000..e417025767 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/bridge/swap/store/S3SwapStoreTest.java @@ -0,0 +1,42 @@ +package uk.ac.ic.wlgitbridge.bridge.swap.store; + +import org.junit.Before; + +/** + * Created by winston on 21/08/2016. + */ +public class S3SwapStoreTest { + + private static final String accessKey = null; + private static final String secret = null; + private static final String bucketName = "com.overleaf.testbucket"; + private static final String region = "us-east-1"; + + private S3SwapStore s3; + + @Before + public void setup() { + if (accessKey == null || secret == null) { + s3 = null; + return; + } + s3 = new S3SwapStore(accessKey, secret, bucketName, region); + } + +// @Ignore +// @Test +// public void testUploadDownloadDelete() throws Exception { +// assumeNotNull(s3); +// String projName = "abc123"; +// byte[] contents = "hello".getBytes(); +// s3.upload( +// projName, +// new ByteArrayInputStream(contents), +// contents.length +// ); +// InputStream down = s3.openDownloadStream(projName); +// s3.remove(projName); +// assertArrayEquals(contents, IOUtils.toByteArray(down)); +// } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/data/model/ResourceFetcherTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/data/model/ResourceFetcherTest.java new file mode 100644 index 0000000000..740220ca8f --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/data/model/ResourceFetcherTest.java @@ -0,0 +1,81 @@ +package uk.ac.ic.wlgitbridge.data.model; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockserver.client.server.MockServerClient; +import org.mockserver.junit.MockServerRule; +import uk.ac.ic.wlgitbridge.bridge.db.DBStore; +import uk.ac.ic.wlgitbridge.bridge.repo.FSGitRepoStore; +import uk.ac.ic.wlgitbridge.bridge.repo.ProjectRepo; +import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore; +import uk.ac.ic.wlgitbridge.bridge.resource.ResourceCache; +import uk.ac.ic.wlgitbridge.bridge.resource.UrlResourceCache; +import uk.ac.ic.wlgitbridge.data.filestore.RawFile; +import uk.ac.ic.wlgitbridge.git.exception.GitUserException; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +/** + * Created by m on 20/11/15. + */ +public class ResourceFetcherTest { + @Rule + public MockServerRule mockServerRule = new MockServerRule(this); + + private MockServerClient mockServerClient; + + @Test + public void fetchesFilesThatAreMissingFromUrlStoreCache() throws IOException, GitUserException { + final String testProjectName = "123abc"; + final String testUrl = "http://localhost:" + mockServerRule.getPort() + "/123abc"; + final String oldTestPath = "testPath"; + final String newTestPath = "missingPath"; + + mockServerClient.when( + request() + .withMethod("GET") + .withPath("/123abc") + ) + .respond( + response() + .withStatusCode(200) + .withBody("content") + ); + + final Mockery context = new Mockery(); + final DBStore dbStore = context.mock(DBStore.class); + context.checking(new Expectations() {{ + // It should fetch the file once it finds it is missing. + oneOf(dbStore).getPathForURLInProject(testProjectName, testUrl); + will(returnValue(oldTestPath)); + + // It should update the URL index store once it has fetched; at present, it does not actually change the stored path. + oneOf(dbStore).addURLIndexForProject(testProjectName, testUrl, oldTestPath); + }}); + + ResourceCache resources = new UrlResourceCache(dbStore); + TemporaryFolder repositoryFolder = new TemporaryFolder(); + repositoryFolder.create(); + String repoStorePath = repositoryFolder.getRoot().getAbsolutePath(); + RepoStore repoStore = new FSGitRepoStore(repoStorePath, Optional.empty()); + ProjectRepo repo = repoStore.initRepo("repo"); + Map fileTable = repo.getDirectory().getFileTable(); + Map fetchedUrls = new HashMap<>(); + resources.get( + testProjectName, testUrl, newTestPath, + fileTable, fetchedUrls, Optional.empty()); + + // We don't bother caching in this case, at present. + assertEquals(0, fetchedUrls.size()); + } +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/snapshot/push/PostbackManagerTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/snapshot/push/PostbackManagerTest.java new file mode 100644 index 0000000000..8575127064 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/snapshot/push/PostbackManagerTest.java @@ -0,0 +1,59 @@ +package uk.ac.ic.wlgitbridge.snapshot.push; + +import org.junit.Assert; +import org.junit.Test; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.InternalErrorException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.SnapshotPostException; +import uk.ac.ic.wlgitbridge.snapshot.push.exception.UnexpectedPostbackException; + +import static org.junit.Assert.*; + +/** + * Created by winston on 05/04/2016. + */ +public class PostbackManagerTest { + + private final PostbackManager postbackManager = new PostbackManager(); + + @Test + public void testRaceWithVersionId() + throws UnexpectedPostbackException, + SnapshotPostException { + String key = postbackManager.makeKeyForProject("proj"); + postbackManager.postVersionIDForProject("proj", 1, key); + int versionId = postbackManager.waitForVersionIdOrThrow("proj"); + assertEquals("Version id didn't match posted", 1, versionId); + } + + @Test + public void testRaceWithException() throws UnexpectedPostbackException, + SnapshotPostException { + String key = postbackManager.makeKeyForProject("proj"); + InternalErrorException ex = new InternalErrorException(); + postbackManager.postExceptionForProject("proj", ex, key); + try { + postbackManager.waitForVersionIdOrThrow("proj"); + } catch (InternalErrorException e) { + Assert.assertSame("Wrong exception was thrown", ex, e); + return; + } + Assert.fail("Exception wasn't thrown as required"); + } + + @Test + public void testTableConsistency() throws UnexpectedPostbackException, + SnapshotPostException { + String key1 = postbackManager.makeKeyForProject("proj1"); + assertEquals(1, postbackManager.postbackContentsTable.size()); + String key2 = postbackManager.makeKeyForProject("proj2"); + assertEquals(2, postbackManager.postbackContentsTable.size()); + postbackManager.postVersionIDForProject("proj1", 1, key1); + postbackManager.postVersionIDForProject("proj2", 1, key2); + assertEquals(2, postbackManager.postbackContentsTable.size()); + postbackManager.waitForVersionIdOrThrow("proj1"); + assertEquals(1, postbackManager.postbackContentsTable.size()); + postbackManager.waitForVersionIdOrThrow("proj2"); + Assert.assertTrue(postbackManager.postbackContentsTable.isEmpty()); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest.java new file mode 100644 index 0000000000..12ead45330 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest.java @@ -0,0 +1,61 @@ +package uk.ac.ic.wlgitbridge.snapshot.servermock.util; + +import org.junit.Assert; +import org.junit.Test; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FileUtilTest { + + @Test + public void returnsTrueWhenFilesAreEqualInBothDirectories() throws URISyntaxException { + Path eq1 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenFilesAreEqualInBothDirectories/eq1").toURI()); + Path eq2 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenFilesAreEqualInBothDirectories/eq2").toURI()); + Assert.assertTrue(FileUtil.gitDirectoriesAreEqual(eq1, eq2)); + } + + @Test + public void returnsTrueWhenRecursiveFilesAreEqualInBothDirectores() throws URISyntaxException { + Path eq1 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq1").toURI()); + Path eq2 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq2").toURI()); + Assert.assertTrue(FileUtil.gitDirectoriesAreEqual(eq1, eq2)); + } + + @Test + public void returnsFalseWhenFilesAreNotEqualInBothDirectories() throws URISyntaxException { + Path neq1 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenFilesAreNotEqualInBothDirectories/neq1").toURI()); + Path neq2 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenFilesAreNotEqualInBothDirectories/neq2").toURI()); + Assert.assertFalse(FileUtil.gitDirectoriesAreEqual(neq1, neq2)); + } + + @Test + public void returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories() throws URISyntaxException { + Path neq1 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq1").toURI()); + Path neq2 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq2").toURI()); + Assert.assertFalse(FileUtil.gitDirectoriesAreEqual(neq1, neq2)); + } + + @Test + public void returnsTrueEvenIfGitDirectoriesAreNotEqual() throws URISyntaxException { + Path neq1 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq1").toURI()); + Path neq2 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq2").toURI()); + Assert.assertTrue(FileUtil.gitDirectoriesAreEqual(neq1, neq2)); + } + + @Test + public void returnsFalseIfFileNamesAreNotEqual() throws URISyntaxException { + Path neq1 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq1").toURI()); + Path neq2 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq2").toURI()); + Assert.assertFalse(FileUtil.gitDirectoriesAreEqual(neq1, neq2)); + } + + @Test + public void returnsFalseIfInnerDirectoryNamesAreNotEqual() throws URISyntaxException { + Path neq1 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfInnerDirectoryNamesAreNotEqual/neq1").toURI()); + Path neq2 = Paths.get(getClass().getResource("/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfInnerDirectoryNamesAreNotEqual/neq2").toURI()); + Assert.assertFalse(FileUtil.gitDirectoriesAreEqual(neq1, neq2)); + } + +} \ No newline at end of file diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/util/ProjectTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/util/ProjectTest.java new file mode 100644 index 0000000000..23d664e4ed --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/util/ProjectTest.java @@ -0,0 +1,18 @@ +package uk.ac.ic.wlgitbridge.util; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Created by winston on 23/08/2016. + */ +public class ProjectTest { + + @Test + public void testValidProjectNames() { + Assert.assertFalse(Project.isValidProjectName(null)); + Assert.assertFalse(Project.isValidProjectName("")); + Assert.assertFalse(Project.isValidProjectName(".wlgb")); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/util/TarTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/util/TarTest.java new file mode 100644 index 0000000000..9834d7999e --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/util/TarTest.java @@ -0,0 +1,76 @@ +package uk.ac.ic.wlgitbridge.util; + +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertTrue; + +/** + * Created by winston on 23/08/2016. + */ +public class TarTest { + + private static final String RESOURCE_DIR + = "/uk/ac/ic/wlgitbridge/util/TarTest"; + + private File testDir; + private File dirWithEmptyFile; + private File tmpDir; + + @Before + public void setup() throws IOException { + TemporaryFolder tmpFolder = new TemporaryFolder(); + tmpFolder.create(); + testDir = ResourceUtil.copyOfFolderResource( + RESOURCE_DIR + "/testdir", + tmpFolder::newFolder); + dirWithEmptyFile = ResourceUtil.copyOfFolderResource( + RESOURCE_DIR + "/dir_with_empty_file", + tmpFolder::newFolder); + tmpDir = tmpFolder.newFolder(); + } + + /** + * Compresses inputDir and decompresses to outputDir. Checks equality + * between outputDir and inputDir. + * @param inputDir the directory to compress + * @param outputDir the output directory. Must be empty. + * @param compressFunction compression function + * @param decompressFunction decompression function + * @throws IOException + */ + private static void assertCompDecompEqual( + File inputDir, + File outputDir, + FunctionT compressFunction, + BiConsumerT decompressFunction + ) throws IOException { + try (InputStream tarbz2 = compressFunction.apply(inputDir)) { + decompressFunction.accept(tarbz2, outputDir); + File unzipped = new File(outputDir, inputDir.getName()); + assertTrue(Files.contentsAreEqual(inputDir, unzipped)); + } + } + + @Test + public void tarAndUntarProducesTheSameResult() throws IOException { + assertCompDecompEqual(testDir, tmpDir, Tar::tar, Tar::untar); + } + + @Test + public void tarbz2AndUntarbz2ProducesTheSameResult() throws IOException { + assertCompDecompEqual(testDir, tmpDir, Tar.bz2::zip, Tar.bz2::unzip); + } + + @Test + public void tarbz2WorksOnDirectoriesWithAnEmptyFile() throws IOException { + assertCompDecompEqual( + dirWithEmptyFile, tmpDir, Tar.bz2::zip, Tar.bz2::unzip); + } + +} diff --git a/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/util/TimerUtilsTest.java b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/util/TimerUtilsTest.java new file mode 100644 index 0000000000..0a6b399372 --- /dev/null +++ b/services/git-bridge/src/test/java/uk/ac/ic/wlgitbridge/util/TimerUtilsTest.java @@ -0,0 +1,19 @@ +package uk.ac.ic.wlgitbridge.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Created by winston on 23/08/2016. + */ +public class TimerUtilsTest { + + @Test + public void testMakeTimerTask() { + int[] iPtr = new int[] { 3 }; + TimerUtils.makeTimerTask(() -> iPtr[0] = 5).run(); + assertEquals(5, iPtr[0]); + } + +} diff --git a/services/git-bridge/src/test/resources/logback-test.xml b/services/git-bridge/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..455a379541 --- /dev/null +++ b/services/git-bridge/src/test/resources/logback-test.xml @@ -0,0 +1,17 @@ + + + + System.err + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{0}: %msg%n + + + + + + + + + + + diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneAMigratedRepositoryWithoutChanges/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneAMigratedRepositoryWithoutChanges/state/state.json new file mode 100644 index 0000000000..070bdaa8d6 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneAMigratedRepositoryWithoutChanges/state/state.json @@ -0,0 +1,29 @@ +[ + { + "project": "testproj_no_change", + "getDoc": { + "versionID": 0, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [], + "getForVers": [ + { + "versionID": 0, + "srcs": [ + { + "content": "test content\n", + "path": "main.tex" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 1 + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneAMigratedRepositoryWithoutChanges/state/testproj_no_change/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneAMigratedRepositoryWithoutChanges/state/testproj_no_change/main.tex new file mode 100644 index 0000000000..d670460b4b --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneAMigratedRepositoryWithoutChanges/state/testproj_no_change/main.tex @@ -0,0 +1 @@ +test content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/state.json new file mode 100644 index 0000000000..3d6f119160 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/state.json @@ -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:3857/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 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/testproj/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/testproj/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/testproj/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/testproj/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/testproj/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneARepository/state/testproj/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/state.json new file mode 100644 index 0000000000..3f047d32e8 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/state.json @@ -0,0 +1,50 @@ +[ + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:4002/state/testproj/overleaf-white-410.png", + "path": "overleaf-white-410-copy.png" + }, + { + "url": "http://127.0.0.1:4002/state/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/overleaf-white-410-copy.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/overleaf-white-410-copy.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/overleaf-white-410-copy.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneDuplicateBinaryFiles/state/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/state.json new file mode 100644 index 0000000000..82b3666659 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/state.json @@ -0,0 +1,90 @@ +[ + { + "project": "testproj1", + "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.456Z" + } + ], + "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:3858/state/testproj1/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + }, + { + "project": "testproj2", + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "different content\n", + "path": "main.tex" + }, + { + "content": "a different one", + "path": "foo/bar/test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3858/state/testproj2/editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png", + "path": "editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj1/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj1/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj1/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj1/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj1/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj1/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj1/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj1/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj1/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj2/editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj2/editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png new file mode 100644 index 0000000000..7fa339be7a Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj2/editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj2/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj2/foo/bar/test.tex new file mode 100644 index 0000000000..1c05c0183f --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj2/foo/bar/test.tex @@ -0,0 +1 @@ +a different one \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj2/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj2/main.tex new file mode 100644 index 0000000000..802d4bbd2e --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canCloneMultipleRepositories/state/testproj2/main.tex @@ -0,0 +1 @@ +different content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/state.json new file mode 100644 index 0000000000..b2352496d0 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/state.json @@ -0,0 +1,83 @@ +[ + { + "project": "testproj2", + "getDoc": { + "versionID": 1, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "migratedFromId": "testproj" + }, + "getSavedVers": [], + "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:3881/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 + } + }, + { + "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:3881/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 + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/testproj/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/testproj/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/testproj/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/testproj/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/testproj/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canMigrateRepository/state/testproj/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/state.json new file mode 100644 index 0000000000..5e8b100055 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/state.json @@ -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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3863/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/base/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/withDeletedBinaryFile/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/withDeletedBinaryFile/state.json new file mode 100644 index 0000000000..0b3c0955f4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/withDeletedBinaryFile/state.json @@ -0,0 +1,68 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 2, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 2, + "comment": "i deleted the image", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:48:01.123Z" + }, + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 2, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + ] + }, + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3863/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/withDeletedBinaryFile/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/withDeletedBinaryFile/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/withDeletedBinaryFile/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/withDeletedBinaryFile/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/withDeletedBinaryFile/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedBinaryFile/withDeletedBinaryFile/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/state.json new file mode 100644 index 0000000000..c5d524ce0c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/state.json @@ -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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3860/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/base/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/withDeletedTexFile/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/withDeletedTexFile/state.json new file mode 100644 index 0000000000..1c966d2018 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/withDeletedTexFile/state.json @@ -0,0 +1,68 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 2, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 2, + "comment": "i deleted test.tex", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:48:01.123Z" + }, + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 2, + "srcs": [ + { + "content": "content\nadded more stuff\n", + "path": "main.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3860/withDeletedTexFile/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + }, + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3860/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/withDeletedTexFile/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/withDeletedTexFile/testproj/main.tex new file mode 100644 index 0000000000..933682f779 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/withDeletedTexFile/testproj/main.tex @@ -0,0 +1,2 @@ +content +added more stuff diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/withDeletedTexFile/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/withDeletedTexFile/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADeletedTexFile/withDeletedTexFile/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/state.json new file mode 100644 index 0000000000..9deb2e98bf --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/state.json @@ -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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:4001/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/base/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/state.json new file mode 100644 index 0000000000..1c20b32bd6 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/state.json @@ -0,0 +1,76 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 2, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 2, + "comment": "i deleted the image", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:48:01.123Z" + }, + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 2, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:4001/withDuplicateBinaryFile/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + }, + { + "url": "http://127.0.0.1:4001/withDuplicateBinaryFile/testproj/overleaf-white-410.png", + "path": "overleaf-white-410-copy.png" + } + ] + }, + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:4001/withDuplicateBinaryFile/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/overleaf-white-410-copy.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/overleaf-white-410-copy.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/overleaf-white-410-copy.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullADuplicateBinaryFile/withDuplicateBinaryFile/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/state.json new file mode 100644 index 0000000000..8cde1852f0 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/state.json @@ -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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3862/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/base/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/state.json new file mode 100644 index 0000000000..e1e7c44989 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/state.json @@ -0,0 +1,72 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 2, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 2, + "comment": "i changed the image", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:48:01.123Z" + }, + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 2, + "srcs": [ + { + "content": "content\nadded more stuff\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3862/withModifiedBinaryFile/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + }, + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3862/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj/main.tex new file mode 100644 index 0000000000..933682f779 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj/main.tex @@ -0,0 +1,2 @@ +content +added more stuff diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..7fa339be7a Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedBinaryFile/withModifiedBinaryFile/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/state.json new file mode 100644 index 0000000000..ca256f8147 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/state.json @@ -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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3864/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/base/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/state.json new file mode 100644 index 0000000000..33156fcbb3 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/state.json @@ -0,0 +1,80 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 2, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 2, + "comment": "i added nested stuff", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:48:01.123Z" + }, + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 2, + "srcs": [ + { + "content": "content\nadded more stuff\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + }, + { + "content": "nest1", + "path": "nest1/nest1.tex" + }, + { + "content": "nest2", + "path": "nest1/nest2/nest2.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3864/withModifiedNestedFile/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + }, + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3864/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/main.tex new file mode 100644 index 0000000000..933682f779 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/main.tex @@ -0,0 +1,2 @@ +content +added more stuff diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/nest1/nest1.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/nest1/nest1.tex new file mode 100644 index 0000000000..487d7bb966 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/nest1/nest1.tex @@ -0,0 +1 @@ +nest1 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/nest1/nest2/nest2.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/nest1/nest2/nest2.tex new file mode 100644 index 0000000000..e3333784c2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/nest1/nest2/nest2.tex @@ -0,0 +1 @@ +nest2 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedNestedFile/withModifiedNestedFile/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/state.json new file mode 100644 index 0000000000..a1f26f571a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/state.json @@ -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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3859/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/base/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/state.json new file mode 100644 index 0000000000..838c704f59 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/state.json @@ -0,0 +1,72 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 2, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 2, + "comment": "i deleted test.tex", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:48:01.123Z" + }, + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 2, + "srcs": [ + { + "content": "content\nadded more stuff\n", + "path": "main.tex" + }, + { + "content": "This text is from another file. and modified", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3859/withModifiedTexFile/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + }, + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3859/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/testproj/main.tex new file mode 100644 index 0000000000..933682f779 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/testproj/main.tex @@ -0,0 +1,2 @@ +content +added more stuff diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/testproj/test.tex new file mode 100644 index 0000000000..1578584b86 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullAModifiedTexFile/withModifiedTexFile/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. and modified \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/state.json new file mode 100644 index 0000000000..d972e58345 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/state.json @@ -0,0 +1,54 @@ +[ + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + }, + { + "content": "nest1", + "path": "nest1/nest1.tex" + }, + { + "content": "nest2", + "path": "nest1/nest2/nest2.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3865/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/nest1/nest1.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/nest1/nest1.tex new file mode 100644 index 0000000000..487d7bb966 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/nest1/nest1.tex @@ -0,0 +1 @@ +nest1 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/nest1/nest2/nest2.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/nest1/nest2/nest2.tex new file mode 100644 index 0000000000..e3333784c2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/nest1/nest2/nest2.tex @@ -0,0 +1 @@ +nest2 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/base/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/state.json new file mode 100644 index 0000000000..683f1778f6 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/state.json @@ -0,0 +1,80 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 2, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 2, + "comment": "i deleted nested stuff", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:48:01.123Z" + }, + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 2, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3865/withDeletedNestedFiles/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + }, + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + }, + { + "content": "nest1", + "path": "nest1/nest1.tex" + }, + { + "content": "nest2", + "path": "nest1/nest2/nest2.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3865/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullDeletedNestedFiles/withDeletedNestedFiles/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/base/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/base/state.json new file mode 100644 index 0000000000..3a099a5fc0 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/base/state.json @@ -0,0 +1,41 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 1, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 1, + "comment": "init", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:47:01.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "*.txt", + "path": "sub/.gitignore" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/base/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/base/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/base/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/base/testproj/sub/.gitignore b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/base/testproj/sub/.gitignore new file mode 100644 index 0000000000..2211df63dd --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/base/testproj/sub/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/state.json new file mode 100644 index 0000000000..393f5a253a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/state.json @@ -0,0 +1,45 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 5, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 5, + "comment": "init", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:47:01.456Z" + } + ], + "getForVers": [ + { + "versionID": 5, + "srcs": [ + { + "content": "content\nupdated\n", + "path": "main.tex" + }, + { + "content": "*.txt", + "path": "sub/.gitignore" + }, + { + "content": "1", + "path": "sub/one.txt" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 5 + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/testproj/main.tex new file mode 100644 index 0000000000..3aa9d51a9f --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/testproj/main.tex @@ -0,0 +1,2 @@ +content +updated diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/testproj/sub/.gitignore b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/testproj/sub/.gitignore new file mode 100644 index 0000000000..2211df63dd --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/testproj/sub/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/testproj/sub/one.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/testproj/sub/one.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullIgnoredForceAddedFile/withUpdatedMainFile/testproj/sub/one.txt @@ -0,0 +1 @@ +1 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/state.json new file mode 100644 index 0000000000..ea08b73617 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/state.json @@ -0,0 +1,50 @@ +[ + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:4003/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + }, + { + "url": "http://127.0.0.1:4003/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410-copy.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/overleaf-white-410-copy.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/overleaf-white-410-copy.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/overleaf-white-410-copy.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/base/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/state.json new file mode 100644 index 0000000000..8270c358fe --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/state.json @@ -0,0 +1,80 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 2, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 2, + "comment": "i deleted the image", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:48:01.123Z" + }, + { + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 2, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:4003/withUpdatedBinaryFiles/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + }, + { + "url": "http://127.0.0.1:4003/withUpdatedBinaryFiles/testproj/overleaf-white-410.png", + "path": "overleaf-white-410-copy.png" + } + ] + }, + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:4003/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410.png" + }, + { + "url": "http://127.0.0.1:4003/base/testproj/overleaf-white-410.png", + "path": "overleaf-white-410-copy.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/overleaf-white-410-copy.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/overleaf-white-410-copy.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/overleaf-white-410-copy.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/overleaf-white-410.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPullUpdatedBinaryFiles/withUpdatedBinaryFiles/testproj/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/state.json new file mode 100644 index 0000000000..112ff2b9ff --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/state.json @@ -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:3866/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 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/testproj/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/testproj/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/testproj/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/testproj/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/testproj/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canPushFilesSuccessfully/state/testproj/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canServePushedFiles/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canServePushedFiles/state/state.json new file mode 100644 index 0000000000..3af45672e6 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canServePushedFiles/state/state.json @@ -0,0 +1,29 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 1, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "test1@example.com", + "name": "John+1" + }, + "getSavedVers": [], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 1 + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canServePushedFiles/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canServePushedFiles/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/canServePushedFiles/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneA4xxProject/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneA4xxProject/state/state.json new file mode 100644 index 0000000000..f8c76c1dec --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneA4xxProject/state/state.json @@ -0,0 +1,18 @@ +[ + { + "project": "gone", + "getDoc": { + "error": 410, + "versionID": 1, + "createdAt": "2018-02-05T15:30:00Z", + "email": "michael.walker@overleaf.com", + "name": "msw" + }, + "getSavedVers": [], + "getForVers": [], + "push": "success", + "postback": { + "type": "outOfDate" + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneAHasDotGitProject/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneAHasDotGitProject/state/state.json new file mode 100644 index 0000000000..23bf520b29 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneAHasDotGitProject/state/state.json @@ -0,0 +1,19 @@ +[ + { + "project": "conflict", + "getDoc": { + "error": 409, + "code": "projectHasDotGit", + "versionID": 1, + "createdAt": "2018-02-05T15:30:00Z", + "email": "michael.walker@overleaf.com", + "name": "msw" + }, + "getSavedVers": [], + "getForVers": [], + "push": "success", + "postback": { + "type": "outOfDate" + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneAMissingProject/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneAMissingProject/state/state.json new file mode 100644 index 0000000000..1db62b00b7 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneAMissingProject/state/state.json @@ -0,0 +1,18 @@ +[ + { + "project": "missing", + "getDoc": { + "error": 404, + "versionID": 1, + "createdAt": "2018-02-06T13:29:00Z", + "email": "michael.walker@overleaf.com", + "name": "msw" + }, + "getSavedVers": [], + "getForVers": [], + "push": "success", + "postback": { + "type": "outOfDate" + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneAProtectedProjectWithoutAuthentication/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneAProtectedProjectWithoutAuthentication/state/state.json new file mode 100644 index 0000000000..859bc623a7 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/cannotCloneAProtectedProjectWithoutAuthentication/state/state.json @@ -0,0 +1,173 @@ +[ + { + "project": "protected", + "getDoc": { + "error": 403, + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "contentchanged\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "outOfDate" + } + }, + { + "project": "invalidFiles", + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "changedñcontent\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "invalidFiles", + "errors": [ + { + "file": "file.invalid", + "state": "error" + }, + { + "file": "virus.exe", + "state": "disallowed" + }, + { + "file": "my image.jpg", + "state": "unclean_name", + "cleanFile": "my_image.jpg" + } + ] + } + }, + { + "project": "invalidProject", + "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:01Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "invalidProject", + "errors": [ + "No main.tex file exists." + ] + } + }, + { + "project": "error", + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "error" + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/state.json new file mode 100644 index 0000000000..6ae1457978 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/state.json @@ -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:3867/state/testproj/min_mean_wait_evm_7_eps_150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + } + ], + "push": "outOfDate", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/testproj/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/testproj/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/testproj/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/testproj/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/testproj/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnFirstStageOutOfDate/state/testproj/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/state.json new file mode 100644 index 0000000000..b1ce038fb6 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/state.json @@ -0,0 +1,65 @@ +[ + { + "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:3869/state/testproj/min_mean_wait_evm_7_eps_150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "invalidFiles", + "errors": [ + { + "file": "file1.invalid", + "state": "error" + }, + { + "file": "file2.exe", + "state": "disallowed" + }, + { + "file": "hello world.png", + "state": "unclean_name", + "cleanFile": "hello_world.png" + }, + { + "file": "an image.jpg", + "state": "unclean_name", + "cleanFile": "an_image.jpg" + } + ] + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/testproj/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/testproj/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/testproj/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/testproj/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/testproj/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidFiles/state/testproj/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/state.json new file mode 100644 index 0000000000..69af680aed --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/state.json @@ -0,0 +1,49 @@ +[ + { + "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:3870/state/testproj/min_mean_wait_evm_7_eps_150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "invalidProject", + "errors": [ + "project: no main file", + "The project would have no (editable) main .tex file." + ] + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/testproj/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/testproj/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/testproj/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/testproj/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/testproj/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnInvalidProject/state/testproj/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/state.json new file mode 100644 index 0000000000..cead6f6bbe --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/state.json @@ -0,0 +1,45 @@ +[ + { + "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:3868/state/testproj/min_mean_wait_evm_7_eps_150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "outOfDate" + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/testproj/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/testproj/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/testproj/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/testproj/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/testproj/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnSecondStageOutOfDate/state/testproj/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/state.json new file mode 100644 index 0000000000..4a2374f3f2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/state.json @@ -0,0 +1,45 @@ +[ + { + "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:3871/state/testproj/min_mean_wait_evm_7_eps_150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "error" + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/testproj/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/testproj/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/testproj/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/testproj/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/testproj/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushFailsOnUnexpectedError/state/testproj/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/state.json new file mode 100644 index 0000000000..d876a5492c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/state.json @@ -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 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/testproj/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/testproj/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/testproj/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/testproj/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/testproj/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSubmoduleFailsWithInvalidGitRepo/state/testproj/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/invalidState/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/invalidState/state.json new file mode 100644 index 0000000000..a885a9747e --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/invalidState/state.json @@ -0,0 +1,34 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 1, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "test@example.com", + "name": "Test User" + }, + "getSavedVers": [], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "invalidFiles", + "errors": [ + { + "file": "file1.exe", + "state": "disallowed" + } + ] + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/invalidState/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/invalidState/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/invalidState/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/validState/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/validState/state.json new file mode 100644 index 0000000000..fdad80ba96 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/validState/state.json @@ -0,0 +1,29 @@ +[ + { + "project": "testproj", + "getDoc": { + "versionID": 1, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "test@example.com", + "name": "Test User" + }, + "getSavedVers": [], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "content\n", + "path": "main.tex" + } + ], + "atts": [] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/validState/testproj/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/validState/testproj/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/pushSucceedsAfterRemovingInvalidFiles/validState/testproj/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/1234bbccddff/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/1234bbccddff/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/1234bbccddff/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/1234bbccddff/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/1234bbccddff/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/1234bbccddff/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/1234bbccddff/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/1234bbccddff/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/1234bbccddff/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/state.json new file mode 100644 index 0000000000..f273818712 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/rejectV1Repository/state/state.json @@ -0,0 +1,46 @@ +[ + { + "project": "1234bbccddff", + "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:3884/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 + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/state.json new file mode 100644 index 0000000000..72fc40fdda --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/state.json @@ -0,0 +1,39 @@ +[ + { + "project": "testproj2", + "getDoc": { + "versionID": 1, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "migratedFromId": "testprojthatdoesnotexist" + }, + "getSavedVers": [], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "two\n", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3882/state/testproj2/min_mean_wait_evm_7_eps_150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/testproj2/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/testproj2/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/testproj2/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/testproj2/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/testproj2/main.tex new file mode 100644 index 0000000000..f719efd430 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/testproj2/main.tex @@ -0,0 +1 @@ +two diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/testproj2/min_mean_wait_evm_7_eps_150dpi.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/testproj2/min_mean_wait_evm_7_eps_150dpi.png new file mode 100644 index 0000000000..74e1fcd990 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/skipMigrationWhenMigratedFromMissing/state/testproj2/min_mean_wait_evm_7_eps_150dpi.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/state.json new file mode 100644 index 0000000000..90fa025334 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/state.json @@ -0,0 +1,198 @@ +[ + { + "project": "1826rqgsdb", + "getDoc": { + "versionID": 243, + "createdAt": "2014-11-30T18:40:58.123Z", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1" + }, + "getSavedVers": [ + { + "versionID": 243, + "comment": "added more info on doc GET and error details", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-30T18:47:01.456Z" + }, + { + "versionID": 185, + "comment": "with more details on POST request", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-11T17:18:40.789Z" + }, + { + "versionID": 175, + "comment": "with updated PUT/POST request", + "email": "jdleesmiller+1@gmail.com", + "name": "John+1", + "createdAt": "2014-11-09T23:09:13.123Z" + }, + { + "versionID": 146, + "comment": "added PUT format", + "email": "jdleesmiller@gmail.com", + "name": "John Lees-Miller", + "createdAt": "2014-11-07T15:11:35.456Z" + }, + { + "versionID": 74, + "comment": "with example output", + "email": "jdleesmiller@gmail.com", + "name": "John Lees-Miller", + "createdAt": "2014-11-05T18:09:41.789Z" + }, + { + "versionID": 39, + "comment": "with more files", + "email": "jdleesmiller@gmail.com", + "name": "John Lees-Miller", + "createdAt": "2014-11-05T18:02:19.123Z" + }, + { + "versionID": 24, + "comment": "first draft", + "email": "jdleesmiller@gmail.com", + "name": "John Lees-Miller", + "createdAt": "2014-11-05T17:56:58.456Z" + } + ], + "getForVers": [ + { + "versionID": 243, + "srcs": [ + { + "content": "\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. This endpoint returns the latest version id, when the latest version was created (ISO8601), and the user that last edited the project (if any, otherwise null).\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => {\\n \\\"latestVerId\\\": 39,\\n \\\"latestVerAt\\\": \\\"2014-11-30T18:35:27Z\\\",\\n \\\"latestVerBy\\\": {\\n \\\"email\\\": \\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\": \\\"John Lees-Miller\\\"\\n }}\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/test.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\n\\\\subsection{Push a Project}\\n\\n\\\\begin{lstlisting}\\n# NB: JLM originally said PUT, but he now thinks POST is better\\n# NB: you must set a Content-Type: application/json header for this request\\n# in order to specify the data as JSON in the request body\\nPOST https://.../api/v0/docs/1826rqgsdb/snapshots\\nData:\\n{\\n latestVerId: integer,\\n files: [\\n {\\n name: string path (forward slashes, relative to root)\\n url: string (but only if the file is modified; else no url given)\\n }, ...\\n ]\\n postbackUrl: url to post result back to\\n}\\nResponse on success:\\n{\\n status: 202,\\n code: \\\"accepted\\\",\\n message: \\\"Accepted\\\"\\n}\\nResponse on out of date:\\n{\\n status: 409, # Conflict\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\n\\nPostback Data (METHOD POST):\\nOn success:\\n{\\n code: \\\"upToDate\\\",\\n latestVerId: integer\\n}\\nOn out of date:\\n{\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\nOn error with the files list (e.g. file extension not allowed):\\n{\\n code: \\\"invalidFiles\\\",\\n errors: [ {\\n file: the file name from the snapshot,\\n state: \\\"error\\\"|\\\"disallowed\\\"|\\\"unclean_name\\\"\\n }, ... ]\\n}\\nIf the file's error state is unclean_name, the error object will alsocontain a property cleanFile that contains the name of the file after it has been \\\"cleaned\\\" to meet our file naming requirements; for other file error states, this property is not present.\\nOn error with the project as a whole (e.g. over quota):\\n{\\n code: \\\"invalidProject\\\",\\n message: short string message for debugging\\n errors: [ array of zero or more string messages for the user ]\\n}\\nOn unexpected error (bug):\\n{\\n code: \\\"error\\\",\\n message: \\\"Unexpected Error\\\"\\n}\\n\\\\end{lstlisting}\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as one of your test projects. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/test}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [ + { + "url": "https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + }, + { + "versionID": 185, + "srcs": [ + { + "content": "\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => { latestVerId: 39 }\\nTODO will also include updatedAt time and user (if it was not anonymous)\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/test.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\n\\\\subsection{Push a Project}\\n\\n\\\\begin{lstlisting}\\n# NB: JLM originally said PUT, but he now thinks POST is better\\n# NB: you must set a Content-Type: application/json header for this request\\n# in order to specify the data as JSON in the request body\\nPOST https://.../api/v0/docs/1826rqgsdb/snapshots\\nData:\\n{\\n latestVerId: integer,\\n files: [\\n {\\n name: string path (forward slashes, relative to root)\\n url: string (but only if the file is modified; else no url given)\\n }, ...\\n ]\\n postbackUrl: url to post result back to\\n}\\nResponse on success:\\n{\\n status: 202,\\n code: \\\"accepted\\\",\\n message: \\\"Accepted\\\"\\n}\\nResponse on out of date:\\n{\\n status: 409, # Conflict\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\n\\nPostback Data (METHOD POST):\\nOn success:\\n{\\n code: \\\"upToDate\\\",\\n latestVerId: integer\\n}\\nOn out of date:\\n{\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\nOn error with the files list (e.g. file extension not allowed):\\n{\\n code: \\\"invalidFiles\\\",\\n errors: TODO\\n}\\nOn error with the project as a whole (e.g. over quota):\\n{\\n code: \\\"invalidProject\\\",\\n errors: TODO\\n}\\nOn unexpected error (bug):\\n{\\n code: \\\"error\\\",\\n message: \\\"Unexpected Error\\\"\\n}\\n\\\\end{lstlisting}\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as one of your test projects. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/test}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [ + { + "url": "https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + }, + { + "versionID": 175, + "srcs": [ + { + "content": "\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => { latestVerId: 39 }\\nTODO will also include updatedAt time and user (if it was not anonymous)\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/test.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\n\\\\subsection{Push a Project}\\n\\n\\\\begin{lstlisting}\\n# NB: JLM originally said PUT, but he now thinks POST is better\\n# NB: you must set a Content-Type: application/json header for this request\\n# in order to specify the data as JSON in the request body\\nPOST https://.../api/v0/docs/1826rqgsdb/snapshots\\nData:\\n{\\n latestVerId: integer,\\n files: [\\n {\\n name: string path (forward slashes, relative to root)\\n url: string (but only if the file is modified; else no url given)\\n }, ...\\n ]\\n postbackUrl: url to post result back to\\n}\\nResponse on success:\\n{\\n status: 202,\\n code: \\\"accepted\\\",\\n message: \\\"Accepted\\\"\\n}\\nResponse on out of date:\\n{\\n status: 409, # Conflict\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\n\\nPostback Data (METHOD POST):\\nOn success:\\n{\\n code: \\\"upToDate\\\",\\n latestVerId: integer\\n}\\nOn out of date:\\n{\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\nOn error:\\n{\\n code: \\\"invalidFile\\\",\\n TODO\\n}\\n\\\\end{lstlisting}\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as one of your test projects. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/test}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [ + { + "url": "https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + }, + { + "versionID": 146, + "srcs": [ + { + "content": "\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => { latestVerId: 39 }\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/test.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\n\\\\subsection{Push a Project}\\n\\n\\\\begin{lstlisting}\\nPUT https://.../api/v0/docs/1826rqgsdb/snapshots\\nData:\\n{\\n latestVerId: integer,\\n files: [\\n {\\n name: string path (forward slashes, relative to root)\\n url: string (but only if the file is modified; else no url given)\\n }, ...\\n ]\\n postbackUrl: url to post result back to\\n}\\nResponse on success:\\n{\\n status: 20x,\\n}\\nResponse on out of date:\\n{\\n status: 40x,\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\n\\nPostback Data (METHOD POST):\\nOn success:\\n{\\n code: \\\"upToDate\\\",\\n latestVerId: integer\\n}\\nOn out of date:\\n{\\n code: \\\"outOfDate\\\",\\n message: \\\"Out of Date\\\"\\n}\\nOn error:\\n{\\n code: \\\"invalidFile\\\",\\n TODO\\n}\\n\\\\end{lstlisting}\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as one of your test projects. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/test}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [ + { + "url": "https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + }, + { + "versionID": 74, + "srcs": [ + { + "content": "\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n# => { latestVerId: 39 }\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. Note that this query is not currently paginated.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n# => [\\n {\\\"versionId\\\":39,\\n \\\"comment\\\":\\\"with more files\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T18:02:19Z\\\"},\\n {\\\"versionId\\\":24,\\n \\\"comment\\\":\\\"first draft\\\",\\n \\\"user\\\":{\\n \\\"email\\\":\\\"jdleesmiller@gmail.com\\\",\\n \\\"name\\\":\\\"John Lees-Miller\\\"},\\n \\\"createdAt\\\":\\\"2014-11-05T17:56:58Z\\\"}]\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\nThe srcs array contains (content, file name) pairs; the atts array contains (URL, file name) pairs.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/39\\n# => {\\n \\\"srcs\\\":[\\n [\\\"This text is from another file.\\\",\\\"foo/bar/test.tex\\\"],\\n [\\\"\\\\\\\\documentclass[a4paper]{article}\\\\n...\\\",\\\"main.tex\\\"]],\\n \\\"atts\\\":[\\n [\\\"https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png\\\",\\\"min_mean_wait_evm_7_eps_150dpi.png\\\"]]}\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\nTODO still working on this part\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as a test project. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/test}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [ + { + "url": "https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + }, + { + "versionID": 39, + "srcs": [ + { + "content": "\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server, \\\\url{radiant-wind-3058.herokuapp.com}, but they're not on the production server yet.\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. To list saved versions for a doc:\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/saved_vers\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\n\\\\begin{lstlisting}\\nGET https://.../api/v0/docs/1826rqgsdb/snapshots/1\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\nTODO still working on this part\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as a test project. I've added an attachment and a file in a subfolder to make it a bit more interesting.\\n\\n\\\\input{foo/bar/test}\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", + "path": "main.tex" + }, + { + "content": "This text is from another file.", + "path": "foo/bar/test.tex" + } + ], + "atts": [ + { + "url": "https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + }, + { + "versionID": 24, + "srcs": [ + { + "content": "\\\\documentclass[a4paper]{article}\\n\\n\\\\usepackage[english]{babel}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{graphicx}\\n\\\\usepackage{fullpage}\\n\\\\usepackage{listings}\\n\\\\usepackage{courier}\\n\\\\usepackage{url}\\n\\n\\\\lstset{basicstyle=\\\\ttfamily,breaklines=true}\\n\\n\\\\begin{document}\\n\\\\title{API for the writeLaTeX-Git Bridge}\\n\\\\author{JLM}\\n\\\\date{\\\\today}\\n\\\\maketitle\\n\\n\\\\section{Fetching a Project from WriteLaTeX}\\n\\nThere are three API calls that will likely be of interest. You can run them against this server (radiant-wind-3058.herokuapp.com).\\n\\n\\\\subsection{Get Doc}\\n\\nA ``doc'' is our internal term for a ``project''. At present, this just returns the latest version number.\\n\\n\\\\begin{lstlisting}\\nGET https://radiant-wind.....com/api/v0/docs/1826rqgsdb\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Saved Vers}\\n\\nA ``saved ver'' is a version of a doc, saved by via the versions menu. To list saved versions for a doc:\\n\\n\\\\begin{lstlisting}\\nGET https://radiant-wind.....com/api/v0/docs/1826rqgsdb/saved_vers\\n\\\\end{lstlisting}\\n\\n\\\\subsection{Get Snapshot for Version}\\n\\nA snapshot contains the content of a project in the given version. You can safely request a snapshot of any version that is, or was at any point in the last 24 hours, (1) a saved version, or (2) the current version. (Older versions may or may not have been moved to cold storage.)\\n\\n\\\\begin{lstlisting}\\nGET https://radiant-wind.....com/api/v0/docs/1826rqgsdb/snapshots/1\\n\\\\end{lstlisting}\\n\\n\\\\section{Pushing a Project to WriteLaTeX}\\n\\nTODO still working on this part\\n\\n\\\\section{Test Data}\\n\\nYou can use this project as a test project. Here is an extra file to make it a bit more interesting.\\n\\n\\\\includegraphics[width=\\\\linewidth]{min_mean_wait_evm_7_eps_150dpi}\\n\\n\\\\end{document}", + "path": "main.tex" + } + ], + "atts": [ + { + "url": "https://writelatex-staging.s3.amazonaws.com/filepicker/1ENnu6zJSGyslI3DuNZD_min_mean_wait_evm_7.eps.150dpi.png", + "path": "min_mean_wait_evm_7_eps_150dpi.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 244 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/state.json b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/state.json new file mode 100644 index 0000000000..77d610d756 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/state.json @@ -0,0 +1,90 @@ +[ + { + "project": "testproj1", + "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.456Z" + } + ], + "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:3874/state/testproj1/overleaf-white-410.png", + "path": "overleaf-white-410.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + }, + { + "project": "testproj2", + "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.456Z" + } + ], + "getForVers": [ + { + "versionID": 1, + "srcs": [ + { + "content": "different content\n", + "path": "main.tex" + }, + { + "content": "a different one", + "path": "foo/bar/test.tex" + } + ], + "atts": [ + { + "url": "http://127.0.0.1:3874/state/testproj2/editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png", + "path": "editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png" + } + ] + } + ], + "push": "success", + "postback": { + "type": "success", + "versionID": 2 + } + } +] \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj1/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj1/foo/bar/test.tex new file mode 100644 index 0000000000..046794f19a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj1/foo/bar/test.tex @@ -0,0 +1 @@ +This text is from another file. \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj1/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj1/main.tex new file mode 100644 index 0000000000..d95f3ad14d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj1/main.tex @@ -0,0 +1 @@ +content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj1/overleaf-white-410.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj1/overleaf-white-410.png new file mode 100644 index 0000000000..6a23d10c15 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj1/overleaf-white-410.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj2/editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj2/editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png new file mode 100644 index 0000000000..7fa339be7a Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj2/editor-versions-a7e4de19d015c3e7477e3f7eaa6c418e.png differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj2/foo/bar/test.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj2/foo/bar/test.tex new file mode 100644 index 0000000000..1c05c0183f --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj2/foo/bar/test.tex @@ -0,0 +1 @@ +a different one \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj2/main.tex b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj2/main.tex new file mode 100644 index 0000000000..802d4bbd2e --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/WLGitBridgeIntegrationTest/wlgbCanSwapProjects/state/testproj2/main.tex @@ -0,0 +1 @@ +different content diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/.wlgb/wlgb.db b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/.wlgb/wlgb.db new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/idontexist/idontexist.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/idontexist/idontexist.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/COMMIT_EDITMSG b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/COMMIT_EDITMSG new file mode 100644 index 0000000000..c098216e78 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/COMMIT_EDITMSG @@ -0,0 +1 @@ +Main diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/config b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/config new file mode 100644 index 0000000000..6c9406b7d9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/description b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/applypatch-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/post-update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-applypatch.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-commit.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-push.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-rebase.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-rebase.sample new file mode 100755 index 0000000000..9773ed4cb2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/prepare-commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..f093a02ec4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/index b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/index new file mode 100644 index 0000000000..c9459c6fa5 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/index differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/info/exclude b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/logs/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/logs/HEAD new file mode 100644 index 0000000000..87a3c02d99 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 e5fc0d2678ec7b9bacf0bf514bac035fa371cb6e Winston Li 1471957665 +0100 commit (initial): Main diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/logs/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/logs/refs/heads/master new file mode 100644 index 0000000000..87a3c02d99 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 e5fc0d2678ec7b9bacf0bf514bac035fa371cb6e Winston Li 1471957665 +0100 commit (initial): Main diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/objects/8b/6f970d184c1e097e6e6bae9b0eb03fec7796bf b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/objects/8b/6f970d184c1e097e6e6bae9b0eb03fec7796bf new file mode 100644 index 0000000000..63b4550453 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/objects/8b/6f970d184c1e097e6e6bae9b0eb03fec7796bf differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/objects/e5/fc0d2678ec7b9bacf0bf514bac035fa371cb6e b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/objects/e5/fc0d2678ec7b9bacf0bf514bac035fa371cb6e new file mode 100644 index 0000000000..13bccfaac3 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/objects/e5/fc0d2678ec7b9bacf0bf514bac035fa371cb6e @@ -0,0 +1,2 @@ +xA +1 @Q=E4Zu$pZ#s}AO/yVec 3Hpx@BYx"fJ}g]m\{ pĜRG]ΌdB4& \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/objects/f2/7f21327e2f0f53e9d8afab217fedaeea6a1cee b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/objects/f2/7f21327e2f0f53e9d8afab217fedaeea6a1cee new file mode 100644 index 0000000000..d0cc2ae76e Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/objects/f2/7f21327e2f0f53e9d8afab217fedaeea6a1cee differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/refs/heads/master new file mode 100644 index 0000000000..d9abf312d9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj1/DOTgit/refs/heads/master @@ -0,0 +1 @@ +e5fc0d2678ec7b9bacf0bf514bac035fa371cb6e diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/COMMIT_EDITMSG b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/COMMIT_EDITMSG new file mode 100644 index 0000000000..c098216e78 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/COMMIT_EDITMSG @@ -0,0 +1 @@ +Main diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/config b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/config new file mode 100644 index 0000000000..6c9406b7d9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/description b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/applypatch-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/post-update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-applypatch.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-commit.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-push.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-rebase.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-rebase.sample new file mode 100755 index 0000000000..9773ed4cb2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/prepare-commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..f093a02ec4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/index b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/index new file mode 100644 index 0000000000..9756dde938 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/index differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/info/exclude b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/logs/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/logs/HEAD new file mode 100644 index 0000000000..2c05a99cac --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 6c12c073e5702530a9d06b83840d62f8a6621764 Winston Li 1471957694 +0100 commit (initial): Main diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/logs/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/logs/refs/heads/master new file mode 100644 index 0000000000..2c05a99cac --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 6c12c073e5702530a9d06b83840d62f8a6621764 Winston Li 1471957694 +0100 commit (initial): Main diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/objects/6c/12c073e5702530a9d06b83840d62f8a6621764 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/objects/6c/12c073e5702530a9d06b83840d62f8a6621764 new file mode 100644 index 0000000000..00a6b21dae --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/objects/6c/12c073e5702530a9d06b83840d62f8a6621764 @@ -0,0 +1,2 @@ +x= +1@abzAfH&! ::&x}AO`W溊qngrEϘr.km&FJ٤WOǛ!' (r єL۟\ _2 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/objects/7f/37654ebf6d0a19650abbcf5db3953b15001d1b b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/objects/7f/37654ebf6d0a19650abbcf5db3953b15001d1b new file mode 100644 index 0000000000..f77bc41b90 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/objects/7f/37654ebf6d0a19650abbcf5db3953b15001d1b differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/objects/d8/5308af36ff394df8bf063719b2aea26077aaea b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/objects/d8/5308af36ff394df8bf063719b2aea26077aaea new file mode 100644 index 0000000000..433ce0d1fc Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/objects/d8/5308af36ff394df8bf063719b2aea26077aaea differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/refs/heads/master new file mode 100644 index 0000000000..4b8bababa2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/FSGitRepoStoreTest/rootdir/proj2/DOTgit/refs/heads/master @@ -0,0 +1 @@ +6c12c073e5702530a9d06b83840d62f8a6621764 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/.gitignore b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/.gitignore new file mode 100644 index 0000000000..f5b2fc4665 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/.gitignore @@ -0,0 +1,2 @@ ++*.ignored + diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/COMMIT_EDITMSG b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/COMMIT_EDITMSG new file mode 100644 index 0000000000..810e445ac4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/COMMIT_EDITMSG @@ -0,0 +1 @@ +Commit bad gitignore diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/config b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/config new file mode 100644 index 0000000000..6c9406b7d9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/description b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/applypatch-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/post-update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-applypatch.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-commit.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-push.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-rebase.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-rebase.sample new file mode 100755 index 0000000000..9773ed4cb2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/prepare-commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..f093a02ec4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/index b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/index new file mode 100644 index 0000000000..8fb7e1ee88 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/index differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/info/exclude b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/logs/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/logs/HEAD new file mode 100644 index 0000000000..ac406aac3d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/logs/HEAD @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 4a64431a59519ff25eff2cd94a561081146059b7 Winston Li 1475933347 +0100 commit (initial): Initial commit +4a64431a59519ff25eff2cd94a561081146059b7 71ebe5d70c8634f7531cc09c1cad5dae951a9052 Winston Li 1475933399 +0100 commit: Add ignored file +71ebe5d70c8634f7531cc09c1cad5dae951a9052 fe5adb3985be3ef5e790eaf9b1b41f862d64dfc1 Winston Li 1475938853 +0100 commit: Commit bad gitignore diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/logs/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/logs/refs/heads/master new file mode 100644 index 0000000000..ac406aac3d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 4a64431a59519ff25eff2cd94a561081146059b7 Winston Li 1475933347 +0100 commit (initial): Initial commit +4a64431a59519ff25eff2cd94a561081146059b7 71ebe5d70c8634f7531cc09c1cad5dae951a9052 Winston Li 1475933399 +0100 commit: Add ignored file +71ebe5d70c8634f7531cc09c1cad5dae951a9052 fe5adb3985be3ef5e790eaf9b1b41f862d64dfc1 Winston Li 1475938853 +0100 commit: Commit bad gitignore diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/23/462c896684e004db23cfab16255e176400643a b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/23/462c896684e004db23cfab16255e176400643a new file mode 100644 index 0000000000..72a979be76 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/23/462c896684e004db23cfab16255e176400643a differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/39/2f03252185fe21cb8926676deb84b60a617ff9 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/39/2f03252185fe21cb8926676deb84b60a617ff9 new file mode 100644 index 0000000000..fddf852f9d Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/39/2f03252185fe21cb8926676deb84b60a617ff9 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/4a/64431a59519ff25eff2cd94a561081146059b7 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/4a/64431a59519ff25eff2cd94a561081146059b7 new file mode 100644 index 0000000000..f58821d7b4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/4a/64431a59519ff25eff2cd94a561081146059b7 @@ -0,0 +1,3 @@ +x +!>܃pt";i *l~>A,,w +6qk`!"OÔI'BZT-ϱKF+rl}8yFUͤԥpa 6> \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/55/45880406d44a71c1c179cb17a5819b59cbbc36 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/55/45880406d44a71c1c179cb17a5819b59cbbc36 new file mode 100644 index 0000000000..e7279a290b Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/55/45880406d44a71c1c179cb17a5819b59cbbc36 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/71/ebe5d70c8634f7531cc09c1cad5dae951a9052 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/71/ebe5d70c8634f7531cc09c1cad5dae951a9052 new file mode 100644 index 0000000000..5678b62739 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/71/ebe5d70c8634f7531cc09c1cad5dae951a9052 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 0000000000..7112238943 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/f5/b2fc4665b50e85c1b66594166f218f722a4af2 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/f5/b2fc4665b50e85c1b66594166f218f722a4af2 new file mode 100644 index 0000000000..17bca0306b Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/f5/b2fc4665b50e85c1b66594166f218f722a4af2 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/f9/8c2cd38c5fae9c349214f6835f5b4aaa622216 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/f9/8c2cd38c5fae9c349214f6835f5b4aaa622216 new file mode 100644 index 0000000000..e693a1b08c Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/f9/8c2cd38c5fae9c349214f6835f5b4aaa622216 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/fe/5adb3985be3ef5e790eaf9b1b41f862d64dfc1 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/fe/5adb3985be3ef5e790eaf9b1b41f862d64dfc1 new file mode 100644 index 0000000000..319683738c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/objects/fe/5adb3985be3ef5e790eaf9b1b41f862d64dfc1 @@ -0,0 +1 @@ +x;0D}푐도-=fmK$FPN1opAkKPb`B9Q+[\0XpDʉ'-yU2&/98cGedŔ0T%jA~o mkXã@YфRI)'5qN `ݨܖ,NF \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/refs/heads/master new file mode 100644 index 0000000000..5fb1c332c1 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/DOTgit/refs/heads/master @@ -0,0 +1 @@ +fe5adb3985be3ef5e790eaf9b1b41f862d64dfc1 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/file1.ignored b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/file1.ignored new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/file1.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/file1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/file2.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/badgitignore/file2.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/.gitignore b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/.gitignore new file mode 100644 index 0000000000..5545880406 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/.gitignore @@ -0,0 +1,2 @@ +*.ignored + diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/COMMIT_EDITMSG b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/COMMIT_EDITMSG new file mode 100644 index 0000000000..217e7d69b5 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/COMMIT_EDITMSG @@ -0,0 +1 @@ +Add ignored file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/config b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/config new file mode 100644 index 0000000000..6c9406b7d9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/description b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/applypatch-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/post-update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-applypatch.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-commit.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-push.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-rebase.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-rebase.sample new file mode 100755 index 0000000000..9773ed4cb2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/prepare-commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..f093a02ec4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/index b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/index new file mode 100644 index 0000000000..ac6d4fde4e Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/index differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/info/exclude b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/info/refs b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/info/refs new file mode 100644 index 0000000000..9c9037a66c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/info/refs @@ -0,0 +1 @@ +71ebe5d70c8634f7531cc09c1cad5dae951a9052 refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/logs/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/logs/HEAD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/logs/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/logs/refs/heads/master new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/incoming_4632440628786417060.pack b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/incoming_4632440628786417060.pack new file mode 100644 index 0000000000..c1a7bd5f16 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/incoming_4632440628786417060.pack @@ -0,0 +1 @@ +blah blah blah diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/incoming_4635689790689803605.pack b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/incoming_4635689790689803605.pack new file mode 100644 index 0000000000..6e5fe03fe0 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/incoming_4635689790689803605.pack @@ -0,0 +1 @@ +i am a pack diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/info/packs b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/info/packs new file mode 100644 index 0000000000..bbfa012663 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/info/packs @@ -0,0 +1,2 @@ +P pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.pack + diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.idx b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.idx new file mode 100644 index 0000000000..30d5e1365f Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.idx differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.pack b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.pack new file mode 100644 index 0000000000..d28e3bd61a Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.pack differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/packed-refs b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/packed-refs new file mode 100644 index 0000000000..102f77f783 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/DOTgit/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled +71ebe5d70c8634f7531cc09c1cad5dae951a9052 refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/file1.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/file1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/file2.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/incoming/file2.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/.gitignore b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/.gitignore new file mode 100644 index 0000000000..5545880406 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/.gitignore @@ -0,0 +1,2 @@ +*.ignored + diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/COMMIT_EDITMSG b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/COMMIT_EDITMSG new file mode 100644 index 0000000000..217e7d69b5 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/COMMIT_EDITMSG @@ -0,0 +1 @@ +Add ignored file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/config b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/config new file mode 100644 index 0000000000..6c9406b7d9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/description b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/applypatch-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/post-update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-applypatch.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-commit.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-push.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-rebase.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-rebase.sample new file mode 100755 index 0000000000..9773ed4cb2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/prepare-commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..f093a02ec4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/index b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/index new file mode 100644 index 0000000000..06891a69f8 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/index differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/info/exclude b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/logs/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/logs/HEAD new file mode 100644 index 0000000000..a82c29857c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 4a64431a59519ff25eff2cd94a561081146059b7 Winston Li 1475933347 +0100 commit (initial): Initial commit +4a64431a59519ff25eff2cd94a561081146059b7 71ebe5d70c8634f7531cc09c1cad5dae951a9052 Winston Li 1475933399 +0100 commit: Add ignored file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/logs/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/logs/refs/heads/master new file mode 100644 index 0000000000..a82c29857c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 4a64431a59519ff25eff2cd94a561081146059b7 Winston Li 1475933347 +0100 commit (initial): Initial commit +4a64431a59519ff25eff2cd94a561081146059b7 71ebe5d70c8634f7531cc09c1cad5dae951a9052 Winston Li 1475933399 +0100 commit: Add ignored file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/23/462c896684e004db23cfab16255e176400643a b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/23/462c896684e004db23cfab16255e176400643a new file mode 100644 index 0000000000..72a979be76 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/23/462c896684e004db23cfab16255e176400643a differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/39/2f03252185fe21cb8926676deb84b60a617ff9 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/39/2f03252185fe21cb8926676deb84b60a617ff9 new file mode 100644 index 0000000000..fddf852f9d Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/39/2f03252185fe21cb8926676deb84b60a617ff9 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/4a/64431a59519ff25eff2cd94a561081146059b7 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/4a/64431a59519ff25eff2cd94a561081146059b7 new file mode 100644 index 0000000000..f58821d7b4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/4a/64431a59519ff25eff2cd94a561081146059b7 @@ -0,0 +1,3 @@ +x +!>܃pt";i *l~>A,,w +6qk`!"OÔI'BZT-ϱKF+rl}8yFUͤԥpa 6> \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/55/45880406d44a71c1c179cb17a5819b59cbbc36 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/55/45880406d44a71c1c179cb17a5819b59cbbc36 new file mode 100644 index 0000000000..e7279a290b Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/55/45880406d44a71c1c179cb17a5819b59cbbc36 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/71/ebe5d70c8634f7531cc09c1cad5dae951a9052 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/71/ebe5d70c8634f7531cc09c1cad5dae951a9052 new file mode 100644 index 0000000000..5678b62739 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/71/ebe5d70c8634f7531cc09c1cad5dae951a9052 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 0000000000..7112238943 Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/refs/heads/master new file mode 100644 index 0000000000..60f255a417 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/DOTgit/refs/heads/master @@ -0,0 +1 @@ +71ebe5d70c8634f7531cc09c1cad5dae951a9052 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/file1.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/file1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/file2.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/repo/file2.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/.gitignore b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/.gitignore new file mode 100644 index 0000000000..5545880406 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/.gitignore @@ -0,0 +1,2 @@ +*.ignored + diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/COMMIT_EDITMSG b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/COMMIT_EDITMSG new file mode 100644 index 0000000000..217e7d69b5 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/COMMIT_EDITMSG @@ -0,0 +1 @@ +Add ignored file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/config b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/config new file mode 100644 index 0000000000..6c9406b7d9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/description b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/applypatch-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/post-update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-applypatch.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-commit.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-push.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-rebase.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-rebase.sample new file mode 100755 index 0000000000..9773ed4cb2 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/prepare-commit-msg.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..f093a02ec4 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/update.sample b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/index b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/index new file mode 100644 index 0000000000..ac6d4fde4e Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/index differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/info/exclude b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/info/refs b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/info/refs new file mode 100644 index 0000000000..9c9037a66c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/info/refs @@ -0,0 +1 @@ +71ebe5d70c8634f7531cc09c1cad5dae951a9052 refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/logs/HEAD b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/logs/HEAD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/logs/refs/heads/master b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/logs/refs/heads/master new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/objects/info/packs b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/objects/info/packs new file mode 100644 index 0000000000..bbfa012663 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/objects/info/packs @@ -0,0 +1,2 @@ +P pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.pack + diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.idx b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.idx new file mode 100644 index 0000000000..30d5e1365f Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.idx differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.pack b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.pack new file mode 100644 index 0000000000..d28e3bd61a Binary files /dev/null and b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/objects/pack/pack-71e0b18bc4675e30d48c891ae9bfc2487ec6e0bb.pack differ diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/packed-refs b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/packed-refs new file mode 100644 index 0000000000..102f77f783 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/DOTgit/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled +71ebe5d70c8634f7531cc09c1cad5dae951a9052 refs/heads/master diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/file1.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/file1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/file2.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/bridge/repo/GitProjectRepoTest/rootdir/without_incoming/file2.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq1/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq1/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq1/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq1/neq1.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq1/neq1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq2/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq2/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq2/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq2/neq2.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfFileNamesAreNotEqual/neq2/neq2.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfInnerDirectoryNamesAreNotEqual/neq1/neq1/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfInnerDirectoryNamesAreNotEqual/neq1/neq1/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfInnerDirectoryNamesAreNotEqual/neq1/neq1/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfInnerDirectoryNamesAreNotEqual/neq2/neq2/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfInnerDirectoryNamesAreNotEqual/neq2/neq2/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseIfInnerDirectoryNamesAreNotEqual/neq2/neq2/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenFilesAreNotEqualInBothDirectories/neq1/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenFilesAreNotEqualInBothDirectories/neq1/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenFilesAreNotEqualInBothDirectories/neq1/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenFilesAreNotEqualInBothDirectories/neq2/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenFilesAreNotEqualInBothDirectories/neq2/file.txt new file mode 100644 index 0000000000..81c545efeb --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenFilesAreNotEqualInBothDirectories/neq2/file.txt @@ -0,0 +1 @@ +1234 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq1/dir/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq1/dir/file.txt new file mode 100644 index 0000000000..274c0052dd --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq1/dir/file.txt @@ -0,0 +1 @@ +1234 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq1/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq1/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq1/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq2/dir/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq2/dir/file.txt new file mode 100644 index 0000000000..d800886d9c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq2/dir/file.txt @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq2/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq2/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsFalseWhenRecursiveFilesAreNotEqualInBothDirectories/neq2/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq1/dir/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq1/dir/file.txt new file mode 100644 index 0000000000..d800886d9c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq1/dir/file.txt @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq1/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq1/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq1/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq2/dir/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq2/dir/file.txt new file mode 100644 index 0000000000..d800886d9c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq2/dir/file.txt @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq2/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq2/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueEvenIfGitDirectoriesAreNotEqual/eq2/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenFilesAreEqualInBothDirectories/eq1/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenFilesAreEqualInBothDirectories/eq1/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenFilesAreEqualInBothDirectories/eq1/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenFilesAreEqualInBothDirectories/eq2/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenFilesAreEqualInBothDirectories/eq2/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenFilesAreEqualInBothDirectories/eq2/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq1/dir/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq1/dir/file.txt new file mode 100644 index 0000000000..d800886d9c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq1/dir/file.txt @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq1/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq1/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq1/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq2/dir/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq2/dir/file.txt new file mode 100644 index 0000000000..d800886d9c --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq2/dir/file.txt @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq2/file.txt b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq2/file.txt new file mode 100644 index 0000000000..e56e15bb7d --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/snapshot/servermock/util/FileUtilTest/returnsTrueWhenRecursiveFilesAreEqualInBothDirectories/eq2/file.txt @@ -0,0 +1 @@ +12345 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/dir_with_empty_file/empty b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/dir_with_empty_file/empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/file1 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/file1 new file mode 100644 index 0000000000..e2129701f1 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/file1 @@ -0,0 +1 @@ +file1 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/file2 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/file2 new file mode 100644 index 0000000000..6c493ff740 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/file2 @@ -0,0 +1 @@ +file2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/file1 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/file1 new file mode 100644 index 0000000000..5c441e6377 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/file1 @@ -0,0 +1 @@ +nest1/file1 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/file2 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/file2 new file mode 100644 index 0000000000..2b7361bb7e --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/file2 @@ -0,0 +1 @@ +nest1file2 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/file3 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/file3 new file mode 100644 index 0000000000..ac69189828 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/file3 @@ -0,0 +1 @@ +nest1/file3 diff --git a/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/nest2/file1 b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/nest2/file1 new file mode 100644 index 0000000000..5e92e63c44 --- /dev/null +++ b/services/git-bridge/src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir/nest1/nest2/file1 @@ -0,0 +1 @@ +nest1/nest2/file1 diff --git a/services/git-bridge/start.sh b/services/git-bridge/start.sh new file mode 100755 index 0000000000..1aa0a9314a --- /dev/null +++ b/services/git-bridge/start.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +/opt/envsubst < /envsubst_template.json > /conf/runtime.json + +VERSION=$(date +%y%m%d%H%M%S) + +if [ "x$GIT_BRIDGE_JVM_ARGS" == "x" ]; then + GIT_BRIDGE_JVM_ARGS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=50.0" +fi + +if [ "$ENABLE_PROFILE_AGENT" == "true" ]; then + GIT_BRIDGE_JVM_ARGS="-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=git-bridge,-cprof_service_version=${VERSION},-cprof_enable_heap_sampling=true ${GIT_BRIDGE_JVM_ARGS}" +fi + +if [ "$ENABLE_DEBUG_AGENT" == "true" ]; then + GIT_BRIDGE_JVM_ARGS="-agentpath:/opt/cdbg/cdbg_java_agent.so -Dcom.google.cdbg.module=git-bridge -Dcom.google.cdbg.version=$VERSION ${GIT_BRIDGE_JVM_ARGS}" +fi + +exec java $GIT_BRIDGE_JVM_ARGS -jar /git-bridge.jar /conf/runtime.json diff --git a/services/git-bridge/vendor/envsubst b/services/git-bridge/vendor/envsubst new file mode 100755 index 0000000000..f7ad8081d0 Binary files /dev/null and b/services/git-bridge/vendor/envsubst differ