{"action":"created","comment":{"url":"https://api.github.com/repos/hydroshare/hydroshare/pulls/comments/208641995","pull_request_review_id":144489295,"id":208641995,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIwODY0MTk5NQ==","diff_hunk":"@@ -0,0 +1,477 @@\n+import datetime\n+import json\n+import mimetypes\n+import os\n+# import random\n+from uuid import uuid4\n+\n+from django.conf import settings\n+from django.core.exceptions import PermissionDenied\n+from django.http import HttpResponse, FileResponse, HttpResponseRedirect\n+from rest_framework.decorators import api_view\n+\n+from django_irods import icommands\n+# from django_irods.storage import IrodsStorage\n+from hs_core.hydroshare import check_resource_type\n+from hs_core.hydroshare.hs_bagit import create_bag_files\n+from hs_core.hydroshare.resource import FILE_SIZE_LIMIT\n+from hs_core.signals import pre_download_file, pre_check_bag_flag\n+from hs_core.tasks import create_bag_by_irods, create_temp_zip, delete_zip\n+from hs_core.views.utils import authorize, ACTION_TO_AUTHORIZE\n+from . import models as m\n+from .icommands import Session, GLOBAL_SESSION\n+from hs_core.models import ResourceFile\n+\n+import logging\n+logger = logging.getLogger(__name__)\n+\n+\n+def download(request, path, rest_call=False, use_async=True, use_reverse_proxy=True,\n+             *args, **kwargs):\n+    \"\"\" perform a download request, either asynchronously or synchronously\n+\n+    :param request: the request object.\n+    :param path: the path of the thing to be downloaded.\n+    :param rest_call: True if calling from REST API\n+    :param use_async: True means to utilize asynchronous creation of objects to download.\n+    :param use_reverse_proxy: True means to utilize NGINX reverse proxy for streaming.\n+\n+    The following variables are computed:\n+\n+    * `path` is the public path of the thing to be downloaded.\n+    * `irods_path` is the location of `path` in irods.\n+    * `output_path` is the output path to be reported in the response object.\n+    * `irods_output_path` is the location of `output_path` in irods\n+\n+    and there are seven cases:\n+\n+    1. path points to a single file that is not a single-file-aggregation\n+       object in a composite resource.\n+    2. path points to a single file that is a single-file-aggregation\n+       object in a composite resource.\n+    3. path points to a metadata object that may need updating.\n+    4. path points to a bag that needs to be updated and then returned.\n+    5. path points to a folder that needs to be zipped.\n+    6. path points to a previously zipped file that was zipped asynchronously.\n+    7. path points to a .zip file that -- when the .zip is removed -- is a file that can be zipped\n+\n+    In cases 1, 3, 4, and 6 the output path is the input path.\n+    In cases 2, 5, and 7, the output path is created as a result of\n+    the operation of zipping the object and its metadata into a new zipfile.\n+    \"\"\"\n+    logger.debug(\"request path is {}\".format(path))\n+\n+    split_path_strs = path.split('/')\n+    while split_path_strs[-1] == '':\n+        split_path_strs.pop()\n+    path = u'/'.join(split_path_strs)  # no trailing slash\n+\n+    # initialize case variables\n+    is_bag_download = False\n+    is_zip_download = False\n+    is_zip_request = False\n+    is_sf_agg_file = False\n+\n+    if split_path_strs[0] == 'bags':\n+        is_bag_download = True\n+        # format is bags/{rid}.zip\n+        # logger.debug(\"fetching rid from bags field 1 = {}\".format(split_path_strs[1]))\n+        res_id = os.path.splitext(split_path_strs[1])[0]\n+    elif split_path_strs[0] == 'zips':\n+        is_zip_download = True\n+        # zips prefix means that we are following up on an asynchronous download request\n+        # format is zips/{date}/{zip-uuid}/{public-path}.zip where {public-path} contains the rid\n+        # logger.debug(\"fetching rid from zips field 3 = {}\".format(split_path_strs[3]))\n+        res_id = split_path_strs[3]\n+    else:  # regular download request\n+        # logger.debug(\"fetching rid from public path field 0 = {}\".format(split_path_strs[0]))\n+        res_id = split_path_strs[0]\n+\n+    logger.debug(\"resource id is {}\".format(res_id))\n+\n+    # now we have the resource Id and can authorize the request\n+    # if the resource does not exist in django, authorized will be false\n+    res, authorized, _ = authorize(request, res_id,\n+                                   needed_permission=ACTION_TO_AUTHORIZE.VIEW_RESOURCE,\n+                                   raises_exception=False)\n+    if not authorized:\n+        response = HttpResponse(status=401)\n+        content_msg = \"You do not have permission to download this resource!\"\n+        if rest_call:\n+            raise PermissionDenied(content_msg)\n+        else:\n+            response.content = \"<h1>\" + content_msg + \"</h1>\"\n+            return response\n+\n+    # default values are changed later as needed\n+    istorage = res.get_irods_storage()\n+    if res.is_federated:\n+        irods_path = os.path.join(res.resource_federation_path, path)\n+    else:\n+        irods_path = path\n+    # in many cases, path and output_path are the same.\n+    output_path = path\n+    irods_output_path = irods_path\n+\n+    # folder requests are automatically zipped\n+    if not is_bag_download and not is_zip_download:  # path points into resource: should I zip it?\n+        store_path = u'/'.join(split_path_strs[1:])  # data/contents/{path-to-something}\n+        if res.is_folder(store_path):  # automatically zip folders\n+            is_zip_request = True\n+            daily_date = datetime.datetime.today().strftime('%Y-%m-%d')\n+            output_path = \"zips/{}/{}/{}.zip\".format(daily_date, uuid4().hex, path)\n+            if res.is_federated:\n+                irods_output_path = os.path.join(res.resource_federation_path, output_path)\n+            else:\n+                irods_output_path = output_path\n+            logger.debug(\"automatically zipping folder {} to {}\".format(path, output_path))\n+        elif istorage.exists(irods_path):\n+            logger.debug(\"request for single file {}\".format(path))\n+        else:  # special case: single file compression request: add .zip to existing file\n+            root, ext = os.path.splitext(path)\n+            irods_root, ext = os.path.splitext(irods_path)\n+            if ext == '.zip' and istorage.exists(irods_root):\n+                # single-file zip request invoked by ending a path in .zip\n+                # (This also works for folders but is unnecessary)\n+                path = root  # strip .zip suffix from request\n+                irods_path = irods_root\n+                daily_date = datetime.datetime.today().strftime('%Y-%m-%d')\n+                output_path = \"zips/{}/{}/{}.zip\".format(daily_date, uuid4().hex, root)\n+                if res.is_federated:\n+                    irods_output_path = os.path.join(res.resource_federation_path, output_path)\n+                else:\n+                    irods_output_path = output_path\n+                # logger.debug(\"zipping file {} to {}\".format(root, output_path))\n+\n+                if \"data/contents/\" in path:  # not a metadata file\n+                    for f in ResourceFile.objects.filter(object_id=res.id):\n+                        if path == f.storage_path:\n+                            if f.has_logical_file and f.logical_file.is_single_file_aggregation:\n+                                is_sf_agg_file = True\n+                            break\n+                if not is_sf_agg_file:\n+                    logger.debug(\"zipping file {} to {}\".format(root, output_path))\n+                    is_zip_request = True\n+                else:\n+                    logger.debug(\"zipping single-file-aggregate {} to {}\".format(root, output_path))\n+\n+                # At this point, either is_zip_request or is_sf_agg_file is True.\n+                # NOTE: there is an existence check below for the final path\n+                # NOTE: metadata is automatically appended if the file is a single-file-aggregation\n+\n+    # logger.debug(\"before aggregation check, bag={}, zipd={} zipr={} sfagg={}\".format(\n+    #     str(is_bag_download),\n+    #     str(is_zip_download),\n+    #     str(is_zip_request),\n+    #     str(is_sf_agg_file)\n+    # ))\n+    # logger.debug(\"before aggregation check, path={}, opath={}\".format(\n+    #     path, output_path\n+    # ))\n+\n+    # at this point, we've modified the output_path and irods_output_path for three cases.\n+    # aggregation logic only applies if the download request isn't a bag, zipfile, or folder,\n+    # and the thing to be downloaded is a \"single file aggregation\" object.\n+    if not is_bag_download and not is_zip_download and not is_zip_request and \\\n+       res.resource_type == 'CompositeResource':\n+        if 'data/contents/' in path:  # not a metadata file\n+            for f in ResourceFile.objects.filter(object_id=res.id):\n+                if path == f.storage_path:\n+                    if f.has_logical_file and f.logical_file.is_single_file_aggregation:\n+                        is_sf_agg_file = True\n+                        daily_date = datetime.datetime.today().strftime('%Y-%m-%d')\n+                        output_path = \"zips/{}/{}/{}.zip\".format(daily_date, uuid4().hex, path)\n+                        if res.is_federated:\n+                            irods_output_path = os.path.join(res.resource_federation_path,\n+                                                             output_path)\n+                        else:\n+                            irods_output_path = output_path\n+                break\n+\n+    # After this point, we have valid path, irods_path, output_path, and irods_output_path\n+    # At most one of the following flags has also been set:\n+    # * is_bag_download: download a bag in format bags/{rid}.zip\n+    # * is_zip_download: download a zipfile in format zips/{date}/{random guid}/{path}.zip\n+    # * is_zip_request: path is a folder; zip before returning\n+    # * is_sf_agg_file: path is a single-file aggregation in Composite Resource, return a zip\n+    # if none of these are set, it's a normal download\n+\n+    # logger.debug(\" after aggregation check, bag={}, zipd={} zipr={} sfagg={}\".format(\n+    #     str(is_bag_download),\n+    #     str(is_zip_download),\n+    #     str(is_zip_request),\n+    #     str(is_sf_agg_file)\n+    # ))\n+    # logger.debug(\" after aggregation check, path={}, opath={}\".format(\n+    #     path, output_path\n+    # ))","path":"django_irods/views.py","position":207,"original_position":207,"commit_id":"03968c046559992eaaca0f8417c047ee110fc75e","original_commit_id":"03968c046559992eaaca0f8417c047ee110fc75e","user":{"login":"hyi","id":476302,"node_id":"MDQ6VXNlcjQ3NjMwMg==","avatar_url":"https://avatars2.githubusercontent.com/u/476302?v=4","gravatar_id":"","url":"https://api.github.com/users/hyi","html_url":"https://github.com/hyi","followers_url":"https://api.github.com/users/hyi/followers","following_url":"https://api.github.com/users/hyi/following{/other_user}","gists_url":"https://api.github.com/users/hyi/gists{/gist_id}","starred_url":"https://api.github.com/users/hyi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hyi/subscriptions","organizations_url":"https://api.github.com/users/hyi/orgs","repos_url":"https://api.github.com/users/hyi/repos","events_url":"https://api.github.com/users/hyi/events{/privacy}","received_events_url":"https://api.github.com/users/hyi/received_events","type":"User","site_admin":false},"body":"remove commented debug statements?","created_at":"2018-08-08T16:07:56Z","updated_at":"2018-08-08T16:57:33Z","html_url":"https://github.com/hydroshare/hydroshare/pull/2864#discussion_r208641995","pull_request_url":"https://api.github.com/repos/hydroshare/hydroshare/pulls/2864","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/hydroshare/hydroshare/pulls/comments/208641995"},"html":{"href":"https://github.com/hydroshare/hydroshare/pull/2864#discussion_r208641995"},"pull_request":{"href":"https://api.github.com/repos/hydroshare/hydroshare/pulls/2864"}}},"pull_request":{"url":"https://api.github.com/repos/hydroshare/hydroshare/pulls/2864","id":206839061,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA2ODM5MDYx","html_url":"https://github.com/hydroshare/hydroshare/pull/2864","diff_url":"https://github.com/hydroshare/hydroshare/pull/2864.diff","patch_url":"https://github.com/hydroshare/hydroshare/pull/2864.patch","issue_url":"https://api.github.com/repos/hydroshare/hydroshare/issues/2864","number":2864,"state":"open","locked":false,"title":"2502 fix user file","user":{"login":"alvacouch","id":3342292,"node_id":"MDQ6VXNlcjMzNDIyOTI=","avatar_url":"https://avatars1.githubusercontent.com/u/3342292?v=4","gravatar_id":"","url":"https://api.github.com/users/alvacouch","html_url":"https://github.com/alvacouch","followers_url":"https://api.github.com/users/alvacouch/followers","following_url":"https://api.github.com/users/alvacouch/following{/other_user}","gists_url":"https://api.github.com/users/alvacouch/gists{/gist_id}","starred_url":"https://api.github.com/users/alvacouch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alvacouch/subscriptions","organizations_url":"https://api.github.com/users/alvacouch/orgs","repos_url":"https://api.github.com/users/alvacouch/repos","events_url":"https://api.github.com/users/alvacouch/events{/privacy}","received_events_url":"https://api.github.com/users/alvacouch/received_events","type":"User","site_admin":false},"body":"<!--\r\n\r\nPlease read, and add your text at the bottom of this message.\r\n\r\nThanks for contributing code to HydroShare. In order to maintain code quality and expedite this process, please assist the development team by making sure the following is present in this pull request.\r\n\r\nFor more information, see https://docs.google.com/document/d/1dzxqlZW5fKNEyQSeKiSFq-SmS-VOPCva95XXkBjPExs\r\n\r\n-->\r\n\r\n### Pull Request Checklist: \r\n- [x] Positive Test Case Written by Dev\r\n\r\n<!-- Enter steps that a QA engineer, stakeholder, or user documentation writer would follow to test the positive or \"successful\" case of the functionality your code provides or fixes -->\r\n\r\n- [ ] Automated Testing\r\n\r\n<!-- Our Jenkins Instance is set up to automatically test every commit from a pull request. Code coverage must not decrease so new functionality or code paths added during a bug fix must have appropriate tests written. Every test must pass, including PEP8 code formatting tests. -->\r\n\r\n- [ ] Sufficient User and Developer Documentation\r\n\r\n<!-- Please email your positive test case lbrazil@cuahsi.org, who will make the decision regarding user documentation. -->\r\n\r\n- [ ] Passing Jenkins Build\r\n\r\n<!-- Our Jenkins Instance is set up to automatically test every commit from a pull request. Every test must pass, including PEP8 code formatting tests. -->\r\n\r\n- [ ] Peer Code review and approval\r\n    \r\n  \r\n<!-- This is the process by which a peer developer on the HydroShare team will read the changeset, provide feedback, and ultimately give a formal approval to the code before it passes PR status. -->\r\n\r\n### Positive Test Case\r\n1. use script \r\n``` ./hsctl managepy list_resources ```\r\nand its many arguments to locate existing resources with various attributes as follows: \r\n    * --storage=local, --storage=user, --storage=federated: storage model\r\n    * --access=public, --access=discoverable, --access=private: kind of access. \r\n    * --type=CompositeResource (etc) \r\n    * --has_subfolders: identifies resources with subfolders. \r\n\r\n2. For a local resource and a user resource: Try to zip and download a bag. \r\n3. For a local resource and a user resource with subfolders: Try to download a subfolder. \r\n4. For a local resource and a user resource: Try to zip a file. \r\n5. For a composite resource: try to download a file with single-file-aggregate metadata. (should get a zip). \r\n6. For a composite resource: try to download a file without single-file-aggregate metadata and force a zip with the .zip suffix. \r\n\r\n### Overview\r\n\r\nCorrect user file download in 2502-composite-resource-phase-2. This PR has a lot of components and fixes many, many shortcomings of the original solution: \r\n\r\n1. All URLs used for downloads are public URLs without iRODS information. \r\n2. User and federated resources are handled properly by adding the federation path when processing. \r\n3. There are three kinds of public requests: \r\n    * a bag download request. format `/bags/{resource_id}.zip`\r\n    * a zipfile request (for a previously created zipfile), format `/zips/{date}/{hash}/{resource id}/{path-to-zip}`\r\n    * a path download request (that is automatically zipped if it is a folder, and whose response contains the zipfile path), format `/resource/{resource id}/{path-to-target}`  \r\n4. special features: \r\n    * folders are always automatically zipped when used as targets\r\n    * adding `.zip` to the end of a file download request zips the file unconditionally. Otherwise the file is returned zipped if it has single-file-aggregate metadata, and unzipped if it doesn't.  \r\n5. other changes: \r\n    * `django_irods` is a directory, not a submodule. \r\n\r\nThere are a number of known issues: \r\n1. Large file handling does not change the banner about not being able to download large files. \r\n2. Many composite resource metadata files are missing from existing resources, even after the script to remove generic metadata. I am logging their absence as errors. \r\n3. Unit tests have mistakes in them; they look for the wrong paths. \r\n\r\nAs a summary of what this does: \r\n\r\n| action | old | new |\r\n|--------|------|------|\r\n| url of a zipped bag | /django_irods/download/bags/{resource id}.zip | /bags/{resource-id}.zip |\r\n| url of a zipped folder | /django_irods/download/zips/{date}/{resource-id}/{hash}/{path-to-target} | /zips/{date}/{hash}/{resource-id}/{path-to-target}.zip |\r\n| zipping a folder | /django_irods/downloads/zips/{resource id}/{path-to-target}.zip | regular path: /resource/{resource-id}/{path-to-target} |\r\n| zipping a file | no analogue | /resource/{resource id}/{path-to-target}.zip |\r\n\r\n","created_at":"2018-08-07T22:00:11Z","updated_at":"2018-08-08T16:57:33Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e26d87c6b62721715356483e543912d5cc775e08","assignee":null,"assignees":[],"requested_reviewers":[{"login":"aphelionz","id":106148,"node_id":"MDQ6VXNlcjEwNjE0OA==","avatar_url":"https://avatars1.githubusercontent.com/u/106148?v=4","gravatar_id":"","url":"https://api.github.com/users/aphelionz","html_url":"https://github.com/aphelionz","followers_url":"https://api.github.com/users/aphelionz/followers","following_url":"https://api.github.com/users/aphelionz/following{/other_user}","gists_url":"https://api.github.com/users/aphelionz/gists{/gist_id}","starred_url":"https://api.github.com/users/aphelionz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aphelionz/subscriptions","organizations_url":"https://api.github.com/users/aphelionz/orgs","repos_url":"https://api.github.com/users/aphelionz/repos","events_url":"https://api.github.com/users/aphelionz/events{/privacy}","received_events_url":"https://api.github.com/users/aphelionz/received_events","type":"User","site_admin":false},{"login":"sblack-usu","id":35820390,"node_id":"MDQ6VXNlcjM1ODIwMzkw","avatar_url":"https://avatars1.githubusercontent.com/u/35820390?v=4","gravatar_id":"","url":"https://api.github.com/users/sblack-usu","html_url":"https://github.com/sblack-usu","followers_url":"https://api.github.com/users/sblack-usu/followers","following_url":"https://api.github.com/users/sblack-usu/following{/other_user}","gists_url":"https://api.github.com/users/sblack-usu/gists{/gist_id}","starred_url":"https://api.github.com/users/sblack-usu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sblack-usu/subscriptions","organizations_url":"https://api.github.com/users/sblack-usu/orgs","repos_url":"https://api.github.com/users/sblack-usu/repos","events_url":"https://api.github.com/users/sblack-usu/events{/privacy}","received_events_url":"https://api.github.com/users/sblack-usu/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":762597729,"node_id":"MDU6TGFiZWw3NjI1OTc3Mjk=","url":"https://api.github.com/repos/hydroshare/hydroshare/labels/Composite","name":"Composite","color":"e6ed82","default":false},{"id":729633515,"node_id":"MDU6TGFiZWw3Mjk2MzM1MTU=","url":"https://api.github.com/repos/hydroshare/hydroshare/labels/File%20System","name":"File System","color":"c2e0c6","default":false},{"id":433017943,"node_id":"MDU6TGFiZWw0MzMwMTc5NDM=","url":"https://api.github.com/repos/hydroshare/hydroshare/labels/High%20Priority","name":"High Priority","color":"b60205","default":false},{"id":221586394,"node_id":"MDU6TGFiZWwyMjE1ODYzOTQ=","url":"https://api.github.com/repos/hydroshare/hydroshare/labels/Possible%20Hotfix","name":"Possible Hotfix","color":"0052cc","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/hydroshare/hydroshare/pulls/2864/commits","review_comments_url":"https://api.github.com/repos/hydroshare/hydroshare/pulls/2864/comments","review_comment_url":"https://api.github.com/repos/hydroshare/hydroshare/pulls/comments{/number}","comments_url":"https://api.github.com/repos/hydroshare/hydroshare/issues/2864/comments","statuses_url":"https://api.github.com/repos/hydroshare/hydroshare/statuses/03968c046559992eaaca0f8417c047ee110fc75e","head":{"label":"hydroshare:2502-fix-user-file","ref":"2502-fix-user-file","sha":"03968c046559992eaaca0f8417c047ee110fc75e","user":{"login":"hydroshare","id":3444493,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM0NDQ0OTM=","avatar_url":"https://avatars1.githubusercontent.com/u/3444493?v=4","gravatar_id":"","url":"https://api.github.com/users/hydroshare","html_url":"https://github.com/hydroshare","followers_url":"https://api.github.com/users/hydroshare/followers","following_url":"https://api.github.com/users/hydroshare/following{/other_user}","gists_url":"https://api.github.com/users/hydroshare/gists{/gist_id}","starred_url":"https://api.github.com/users/hydroshare/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hydroshare/subscriptions","organizations_url":"https://api.github.com/users/hydroshare/orgs","repos_url":"https://api.github.com/users/hydroshare/repos","events_url":"https://api.github.com/users/hydroshare/events{/privacy}","received_events_url":"https://api.github.com/users/hydroshare/received_events","type":"Organization","site_admin":false},"repo":{"id":24703136,"node_id":"MDEwOlJlcG9zaXRvcnkyNDcwMzEzNg==","name":"hydroshare","full_name":"hydroshare/hydroshare","owner":{"login":"hydroshare","id":3444493,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM0NDQ0OTM=","avatar_url":"https://avatars1.githubusercontent.com/u/3444493?v=4","gravatar_id":"","url":"https://api.github.com/users/hydroshare","html_url":"https://github.com/hydroshare","followers_url":"https://api.github.com/users/hydroshare/followers","following_url":"https://api.github.com/users/hydroshare/following{/other_user}","gists_url":"https://api.github.com/users/hydroshare/gists{/gist_id}","starred_url":"https://api.github.com/users/hydroshare/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hydroshare/subscriptions","organizations_url":"https://api.github.com/users/hydroshare/orgs","repos_url":"https://api.github.com/users/hydroshare/repos","events_url":"https://api.github.com/users/hydroshare/events{/privacy}","received_events_url":"https://api.github.com/users/hydroshare/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hydroshare/hydroshare","description":"HydroShare is a collaborative website for better access to data and models in the hydrologic sciences. ","fork":false,"url":"https://api.github.com/repos/hydroshare/hydroshare","forks_url":"https://api.github.com/repos/hydroshare/hydroshare/forks","keys_url":"https://api.github.com/repos/hydroshare/hydroshare/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hydroshare/hydroshare/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hydroshare/hydroshare/teams","hooks_url":"https://api.github.com/repos/hydroshare/hydroshare/hooks","issue_events_url":"https://api.github.com/repos/hydroshare/hydroshare/issues/events{/number}","events_url":"https://api.github.com/repos/hydroshare/hydroshare/events","assignees_url":"https://api.github.com/repos/hydroshare/hydroshare/assignees{/user}","branches_url":"https://api.github.com/repos/hydroshare/hydroshare/branches{/branch}","tags_url":"https://api.github.com/repos/hydroshare/hydroshare/tags","blobs_url":"https://api.github.com/repos/hydroshare/hydroshare/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hydroshare/hydroshare/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hydroshare/hydroshare/git/refs{/sha}","trees_url":"https://api.github.com/repos/hydroshare/hydroshare/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hydroshare/hydroshare/statuses/{sha}","languages_url":"https://api.github.com/repos/hydroshare/hydroshare/languages","stargazers_url":"https://api.github.com/repos/hydroshare/hydroshare/stargazers","contributors_url":"https://api.github.com/repos/hydroshare/hydroshare/contributors","subscribers_url":"https://api.github.com/repos/hydroshare/hydroshare/subscribers","subscription_url":"https://api.github.com/repos/hydroshare/hydroshare/subscription","commits_url":"https://api.github.com/repos/hydroshare/hydroshare/commits{/sha}","git_commits_url":"https://api.github.com/repos/hydroshare/hydroshare/git/commits{/sha}","comments_url":"https://api.github.com/repos/hydroshare/hydroshare/comments{/number}","issue_comment_url":"https://api.github.com/repos/hydroshare/hydroshare/issues/comments{/number}","contents_url":"https://api.github.com/repos/hydroshare/hydroshare/contents/{+path}","compare_url":"https://api.github.com/repos/hydroshare/hydroshare/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hydroshare/hydroshare/merges","archive_url":"https://api.github.com/repos/hydroshare/hydroshare/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hydroshare/hydroshare/downloads","issues_url":"https://api.github.com/repos/hydroshare/hydroshare/issues{/number}","pulls_url":"https://api.github.com/repos/hydroshare/hydroshare/pulls{/number}","milestones_url":"https://api.github.com/repos/hydroshare/hydroshare/milestones{/number}","notifications_url":"https://api.github.com/repos/hydroshare/hydroshare/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hydroshare/hydroshare/labels{/name}","releases_url":"https://api.github.com/repos/hydroshare/hydroshare/releases{/id}","deployments_url":"https://api.github.com/repos/hydroshare/hydroshare/deployments","created_at":"2014-10-02T02:19:41Z","updated_at":"2018-07-27T17:56:16Z","pushed_at":"2018-08-08T15:51:20Z","git_url":"git://github.com/hydroshare/hydroshare.git","ssh_url":"git@github.com:hydroshare/hydroshare.git","clone_url":"https://github.com/hydroshare/hydroshare.git","svn_url":"https://github.com/hydroshare/hydroshare","homepage":"https://www.hydroshare.org","size":226247,"stargazers_count":60,"watchers_count":60,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"archived":false,"open_issues_count":445,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":12,"open_issues":445,"watchers":60,"default_branch":"develop"}},"base":{"label":"hydroshare:2502-composite-resource-phase-2","ref":"2502-composite-resource-phase-2","sha":"714d2d3bfba7e396acb95afad30559e74a3a420a","user":{"login":"hydroshare","id":3444493,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM0NDQ0OTM=","avatar_url":"https://avatars1.githubusercontent.com/u/3444493?v=4","gravatar_id":"","url":"https://api.github.com/users/hydroshare","html_url":"https://github.com/hydroshare","followers_url":"https://api.github.com/users/hydroshare/followers","following_url":"https://api.github.com/users/hydroshare/following{/other_user}","gists_url":"https://api.github.com/users/hydroshare/gists{/gist_id}","starred_url":"https://api.github.com/users/hydroshare/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hydroshare/subscriptions","organizations_url":"https://api.github.com/users/hydroshare/orgs","repos_url":"https://api.github.com/users/hydroshare/repos","events_url":"https://api.github.com/users/hydroshare/events{/privacy}","received_events_url":"https://api.github.com/users/hydroshare/received_events","type":"Organization","site_admin":false},"repo":{"id":24703136,"node_id":"MDEwOlJlcG9zaXRvcnkyNDcwMzEzNg==","name":"hydroshare","full_name":"hydroshare/hydroshare","owner":{"login":"hydroshare","id":3444493,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM0NDQ0OTM=","avatar_url":"https://avatars1.githubusercontent.com/u/3444493?v=4","gravatar_id":"","url":"https://api.github.com/users/hydroshare","html_url":"https://github.com/hydroshare","followers_url":"https://api.github.com/users/hydroshare/followers","following_url":"https://api.github.com/users/hydroshare/following{/other_user}","gists_url":"https://api.github.com/users/hydroshare/gists{/gist_id}","starred_url":"https://api.github.com/users/hydroshare/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hydroshare/subscriptions","organizations_url":"https://api.github.com/users/hydroshare/orgs","repos_url":"https://api.github.com/users/hydroshare/repos","events_url":"https://api.github.com/users/hydroshare/events{/privacy}","received_events_url":"https://api.github.com/users/hydroshare/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hydroshare/hydroshare","description":"HydroShare is a collaborative website for better access to data and models in the hydrologic sciences. ","fork":false,"url":"https://api.github.com/repos/hydroshare/hydroshare","forks_url":"https://api.github.com/repos/hydroshare/hydroshare/forks","keys_url":"https://api.github.com/repos/hydroshare/hydroshare/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hydroshare/hydroshare/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hydroshare/hydroshare/teams","hooks_url":"https://api.github.com/repos/hydroshare/hydroshare/hooks","issue_events_url":"https://api.github.com/repos/hydroshare/hydroshare/issues/events{/number}","events_url":"https://api.github.com/repos/hydroshare/hydroshare/events","assignees_url":"https://api.github.com/repos/hydroshare/hydroshare/assignees{/user}","branches_url":"https://api.github.com/repos/hydroshare/hydroshare/branches{/branch}","tags_url":"https://api.github.com/repos/hydroshare/hydroshare/tags","blobs_url":"https://api.github.com/repos/hydroshare/hydroshare/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hydroshare/hydroshare/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hydroshare/hydroshare/git/refs{/sha}","trees_url":"https://api.github.com/repos/hydroshare/hydroshare/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hydroshare/hydroshare/statuses/{sha}","languages_url":"https://api.github.com/repos/hydroshare/hydroshare/languages","stargazers_url":"https://api.github.com/repos/hydroshare/hydroshare/stargazers","contributors_url":"https://api.github.com/repos/hydroshare/hydroshare/contributors","subscribers_url":"https://api.github.com/repos/hydroshare/hydroshare/subscribers","subscription_url":"https://api.github.com/repos/hydroshare/hydroshare/subscription","commits_url":"https://api.github.com/repos/hydroshare/hydroshare/commits{/sha}","git_commits_url":"https://api.github.com/repos/hydroshare/hydroshare/git/commits{/sha}","comments_url":"https://api.github.com/repos/hydroshare/hydroshare/comments{/number}","issue_comment_url":"https://api.github.com/repos/hydroshare/hydroshare/issues/comments{/number}","contents_url":"https://api.github.com/repos/hydroshare/hydroshare/contents/{+path}","compare_url":"https://api.github.com/repos/hydroshare/hydroshare/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hydroshare/hydroshare/merges","archive_url":"https://api.github.com/repos/hydroshare/hydroshare/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hydroshare/hydroshare/downloads","issues_url":"https://api.github.com/repos/hydroshare/hydroshare/issues{/number}","pulls_url":"https://api.github.com/repos/hydroshare/hydroshare/pulls{/number}","milestones_url":"https://api.github.com/repos/hydroshare/hydroshare/milestones{/number}","notifications_url":"https://api.github.com/repos/hydroshare/hydroshare/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hydroshare/hydroshare/labels{/name}","releases_url":"https://api.github.com/repos/hydroshare/hydroshare/releases{/id}","deployments_url":"https://api.github.com/repos/hydroshare/hydroshare/deployments","created_at":"2014-10-02T02:19:41Z","updated_at":"2018-07-27T17:56:16Z","pushed_at":"2018-08-08T15:51:20Z","git_url":"git://github.com/hydroshare/hydroshare.git","ssh_url":"git@github.com:hydroshare/hydroshare.git","clone_url":"https://github.com/hydroshare/hydroshare.git","svn_url":"https://github.com/hydroshare/hydroshare","homepage":"https://www.hydroshare.org","size":226247,"stargazers_count":60,"watchers_count":60,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"archived":false,"open_issues_count":445,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":12,"open_issues":445,"watchers":60,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/hydroshare/hydroshare/pulls/2864"},"html":{"href":"https://github.com/hydroshare/hydroshare/pull/2864"},"issue":{"href":"https://api.github.com/repos/hydroshare/hydroshare/issues/2864"},"comments":{"href":"https://api.github.com/repos/hydroshare/hydroshare/issues/2864/comments"},"review_comments":{"href":"https://api.github.com/repos/hydroshare/hydroshare/pulls/2864/comments"},"review_comment":{"href":"https://api.github.com/repos/hydroshare/hydroshare/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/hydroshare/hydroshare/pulls/2864/commits"},"statuses":{"href":"https://api.github.com/repos/hydroshare/hydroshare/statuses/03968c046559992eaaca0f8417c047ee110fc75e"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"hyi"}}