{"action":"created","comment":{"url":"https://api.github.com/repos/artefactual/archivematica/pulls/comments/176855318","pull_request_review_id":106633264,"id":176855318,"diff_hunk":"@@ -0,0 +1,244 @@\n+#!/usr/bin/env python2\n+from __future__ import print_function\n+import os\n+from pprint import pformat\n+import shutil\n+import sys\n+\n+import django\n+django.setup()\n+from django.conf import settings as mcpclient_settings\n+\n+# archivematicaCommon\n+from archivematicaFunctions import get_setting\n+from custom_handlers import get_script_logger\n+import databaseFunctions\n+from executeOrRunSubProcess import executeOrRun\n+\n+from main.models import File\n+\n+\n+class VerifyChecksumsError(Exception):\n+    \"\"\"Checksum verification has failed.\"\"\"\n+\n+\n+def extract_aip(aip_path, extract_path):\n+    os.makedirs(extract_path)\n+    command = \"atool --extract-to={} -V0 {}\".format(extract_path, aip_path)\n+    print('Running extraction command:', command)\n+    exit_code, _, _ = executeOrRun(\"command\", command, printing=True)\n+    if exit_code != 0:\n+        raise Exception(\"Error extracting AIP\")\n+\n+    aip_identifier, ext = os.path.splitext(os.path.basename(aip_path))\n+    if ext in ('.bz2', '.gz'):\n+        aip_identifier, _ = os.path.splitext(aip_identifier)\n+    return os.path.join(extract_path, aip_identifier)\n+\n+\n+def write_premis_event(sip_uuid, checksum_type, event_outcome,\n+                       event_outcome_detail_note):\n+    \"\"\"Write the AIP-level \"fixity check\" PREMIS event.\"\"\"\n+    try:\n+        databaseFunctions.insertIntoEvents(\n+            fileUUID=sip_uuid,\n+            eventType='fixity check',\n+            eventDetail='program=\"python, bag\"; module=\"hashlib.{}()\"'.format(\n+                checksum_type),\n+            eventOutcome=event_outcome,\n+            eventOutcomeDetailNote=event_outcome_detail_note\n+        )\n+    except Exception as err:\n+        print('Failed to write PREMIS event to database. Error: {error}'.format(\n+            error=err))\n+    else:\n+        return event_outcome_detail_note\n+\n+\n+def get_manifest_path(bag, sip_uuid, checksum_type):\n+    \"\"\"Raise exception if if the Bag manifest file is not a file.\"\"\"\n+    manifest_path = os.path.join(\n+        bag, 'manifest-{algo}.txt'.format(algo=checksum_type))\n+    if not os.path.isfile(manifest_path):\n+        event_outcome_detail_note = (\n+            'Unable to perform AIP-level fixity check on AIP {aip_uuid} because'\n+            ' unable to find a Bag manifest file at expected path'\n+            ' {manifest_path}.'.format(aip_uuid=sip_uuid,\n+                                       manifest_path=manifest_path))\n+        raise VerifyChecksumsError(\n+            write_premis_event(sip_uuid, checksum_type, 'Fail',\n+                               event_outcome_detail_note))\n+    return manifest_path\n+\n+\n+def parse_manifest(manifest_path, sip_uuid, checksum_type):\n+    \"\"\"Raise exception if the Bag manifest file cannot be parsed.\"\"\"\n+    with open(manifest_path) as filei:\n+        try:\n+            return {\n+                k.replace('data/', '', 1): v for k, v in\n+                dict(reversed(line.split()) for line in filei).items()}\n+        except Exception as err:\n+            event_outcome_detail_note = (\n+                'Unable to perform AIP-level fixity check on AIP {aip_uuid}'\n+                ' because unable to parse manifest file at path'\n+                ' {manifest_path}. Error:\\n{error}'.format(\n+                    aip_uuid=sip_uuid,\n+                    manifest_path=manifest_path,\n+                    error=err))\n+            raise VerifyChecksumsError(\n+                write_premis_event(sip_uuid, checksum_type, 'Fail',\n+                                   event_outcome_detail_note))\n+\n+\n+def assert_checksum_types_match(file_, sip_uuid, settings_checksum_type):\n+    \"\"\"Raise exception if checksum types (i.e., algorithms, e.g., 'sha256') of\n+    the file and the settings do not match.\n+    \"\"\"\n+    if file_.checksumtype != settings_checksum_type:\n+        event_outcome_detail_note = (\n+            'The checksum type of file {file_uuid} is'\n+            ' {file_checksum_type}; given the current application settings, we'\n+            ' expect it to {settings_checksum_type}'.format(\n+                file_uuid=file_.uuid,\n+                file_checksum_type=file_.checksumtype,\n+                settings_checksum_type=settings_checksum_type))\n+        raise VerifyChecksumsError(\n+            write_premis_event(sip_uuid, settings_checksum_type, 'Fail',\n+                               event_outcome_detail_note))\n+\n+\n+def get_expected_checksum(file_, sip_uuid, checksum_type, path2checksum,\n+                          file_path, manifest_path):\n+    \"\"\"Raise an exception if an expected checksum cannot be found in the\n+    Bag manifest.\n+    \"\"\"\n+    try:\n+        return path2checksum[file_path]\n+    except KeyError:\n+        event_outcome_detail_note = (\n+            'Unable to find expected path {expected_path} for file'\n+            ' {file_uuid} in the following mapping from file paths to'\n+            ' checksums that was extracted from Bag manifest file'\n+            ' {manifest_file}: {mapping}'.format(\n+                expected_path=file_path,\n+                file_uuid=file_.uuid,\n+                manifest_file=manifest_path,\n+                mapping=pformat(path2checksum)))\n+        raise VerifyChecksumsError(\n+            write_premis_event(sip_uuid, checksum_type, 'Fail',\n+                               event_outcome_detail_note))\n+\n+\n+def assert_checksums_match(file_, sip_uuid, checksum_type, expected_checksum):\n+    \"\"\"Raise an exception if checksums do not match.\"\"\"\n+    if file_.checksum != expected_checksum:\n+        event_outcome_detail_note = (\n+            'The checksum {db_checksum} for file {file_uuid} from the'\n+            ' database did not match the corresponding checksum from the'\n+            ' Bag manifest file {manifest_checksum}'.format(\n+                file_uuid=file_.uuid,\n+                db_checksum=file_.checksum,\n+                manifest_checksum=expected_checksum))\n+        raise VerifyChecksumsError(\n+            write_premis_event(sip_uuid, checksum_type, 'Fail',\n+                               event_outcome_detail_note))\n+\n+\n+def verify_checksums(bag, sip_uuid):\n+    \"\"\"Verify that the checksums generated at the beginning of transfer match\n+    those generated near the end of ingest by bag, i.e., \"Prepare AIP\"\n+    (bagit_v0.0).\n+    \"\"\"\n+    checksum_type = get_setting(\n+        'checksum_type', mcpclient_settings.DEFAULT_CHECKSUM_ALGORITHM)\n+    try:\n+        manifest_path = get_manifest_path(bag, sip_uuid, checksum_type)\n+        path2checksum = parse_manifest(manifest_path, sip_uuid, checksum_type)\n+        verification_count = 0\n+        for file_ in File.objects.filter(sip_id=sip_uuid):\n+            if not file_.currentlocation.startswith('%SIPDirectory%objects/'):\n+                continue\n+            file_path = file_.currentlocation.replace('%SIPDirectory%', '', 1)\n+            assert_checksum_types_match(file_, sip_uuid, checksum_type)\n+            expected_checksum = get_expected_checksum(\n+                file_, sip_uuid, checksum_type, path2checksum, file_path,\n+                manifest_path)\n+            assert_checksums_match(file_, sip_uuid, checksum_type,\n+                                   expected_checksum)\n+            verification_count += 1\n+    except VerifyChecksumsError as err:\n+        print(err)\n+        return 1\n+    event_outcome_detail_note = (\n+        'All {verification_count} checksums generated at start of transfer'\n+        ' match those generated by BagIt (bag).'.format(\n+            verification_count=verification_count))\n+    write_premis_event(sip_uuid, checksum_type, 'Pass',\n+                       event_outcome_detail_note)\n+    print(event_outcome_detail_note)\n+    return 0\n+\n+\n+def verify_aip():\n+    \"\"\"Verify the AIP was bagged correctly by extracting it and running\n+    verification on its contents. This is also where we verify the checksums\n+    now that the verifyPREMISChecksums_v0.0 (\"Verify checksums generated on\n+    ingest\") micro-service has been removed. It was removed because verifying\n+    checksums by calculating them in that MS and then having bagit calculate\n+    them here was redundant.\n+\n+    sys.argv[1] = UUID\n+      UUID of the SIP, which will become the UUID of the AIP\n+    sys.argv[2] = current location\n+      Full absolute path to the AIP's current location on the local filesystem\n+    \"\"\"\n+\n+    sip_uuid = sys.argv[1]  # %sip_uuid%\n+    aip_path = sys.argv[2]  # SIPDirectory%%sip_name%-%sip_uuid%.7z\n+\n+    temp_dir = mcpclient_settings.TEMP_DIRECTORY\n+\n+    is_uncompressed_aip = os.path.isdir(aip_path)\n+\n+    if is_uncompressed_aip:\n+        bag = aip_path\n+    else:\n+        try:\n+            extract_dir = os.path.join(temp_dir, sip_uuid)\n+            bag = extract_aip(aip_path, extract_dir)\n+        except Exception:\n+            print('Error extracting AIP at \"{}\"'.format(aip_path), file=sys.stderr)\n+            return 1\n+\n+    verification_commands = [\n+        '/usr/share/bagit/bin/bag verifyvalid \"{}\"'.format(bag),\n+        '/usr/share/bagit/bin/bag checkpayloadoxum \"{}\"'.format(bag),\n+        '/usr/share/bagit/bin/bag verifycomplete \"{}\"'.format(bag),\n+        '/usr/share/bagit/bin/bag verifypayloadmanifests \"{}\"'.format(bag),\n+        '/usr/share/bagit/bin/bag verifytagmanifests \"{}\"'.format(bag),\n+    ]\n+    return_code = 0\n+    for command in verification_commands:\n+        print(\"Running test: \", command)\n+        exit_code, _, _ = executeOrRun(\"command\", command, printing=True)\n+        if exit_code != 0:\n+            print(\"Failed test: \", command, file=sys.stderr)\n+            return_code = 1\n+\n+    if return_code == 0:\n+        return_code = verify_checksums(bag, sip_uuid)\n+    else:\n+        print('Not verifying checksums because other tests have already'\n+              ' failed.')\n+\n+    # cleanup\n+    if not is_uncompressed_aip:\n+        shutil.rmtree(extract_dir)","path":"src/MCPClient/lib/clientScripts/verify_AIP.py","position":237,"original_position":237,"commit_id":"defb42c067ee1118463939c3b22de7cf2aa3e20c","original_commit_id":"defb42c067ee1118463939c3b22de7cf2aa3e20c","user":{"login":"sevein","id":606459,"avatar_url":"https://avatars2.githubusercontent.com/u/606459?v=4","gravatar_id":"","url":"https://api.github.com/users/sevein","html_url":"https://github.com/sevein","followers_url":"https://api.github.com/users/sevein/followers","following_url":"https://api.github.com/users/sevein/following{/other_user}","gists_url":"https://api.github.com/users/sevein/gists{/gist_id}","starred_url":"https://api.github.com/users/sevein/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sevein/subscriptions","organizations_url":"https://api.github.com/users/sevein/orgs","repos_url":"https://api.github.com/users/sevein/repos","events_url":"https://api.github.com/users/sevein/events{/privacy}","received_events_url":"https://api.github.com/users/sevein/received_events","type":"User","site_admin":false},"body":"Do we want to make the script report an error if the clean up did not succeed? You may want to trap `OSError` if that's not the case.","created_at":"2018-03-23T20:29:50Z","updated_at":"2018-03-23T20:30:54Z","html_url":"https://github.com/artefactual/archivematica/pull/1012#discussion_r176855318","pull_request_url":"https://api.github.com/repos/artefactual/archivematica/pulls/1012","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/artefactual/archivematica/pulls/comments/176855318"},"html":{"href":"https://github.com/artefactual/archivematica/pull/1012#discussion_r176855318"},"pull_request":{"href":"https://api.github.com/repos/artefactual/archivematica/pulls/1012"}}},"pull_request":{"url":"https://api.github.com/repos/artefactual/archivematica/pulls/1012","id":177136670,"html_url":"https://github.com/artefactual/archivematica/pull/1012","diff_url":"https://github.com/artefactual/archivematica/pull/1012.diff","patch_url":"https://github.com/artefactual/archivematica/pull/1012.patch","issue_url":"https://api.github.com/repos/artefactual/archivematica/issues/1012","number":1012,"state":"open","locked":false,"title":"De-duplicate checksum verification","user":{"login":"jrwdunham","id":2504271,"avatar_url":"https://avatars1.githubusercontent.com/u/2504271?v=4","gravatar_id":"","url":"https://api.github.com/users/jrwdunham","html_url":"https://github.com/jrwdunham","followers_url":"https://api.github.com/users/jrwdunham/followers","following_url":"https://api.github.com/users/jrwdunham/following{/other_user}","gists_url":"https://api.github.com/users/jrwdunham/gists{/gist_id}","starred_url":"https://api.github.com/users/jrwdunham/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jrwdunham/subscriptions","organizations_url":"https://api.github.com/users/jrwdunham/orgs","repos_url":"https://api.github.com/users/jrwdunham/repos","events_url":"https://api.github.com/users/jrwdunham/events{/privacy}","received_events_url":"https://api.github.com/users/jrwdunham/received_events","type":"User","site_admin":false},"body":"Fixes #918 \r\n\r\nIn short, removes the once-per-file verifyPREMISChecksums_v0.0 micro-service and replaces it with once-per-SIP equivalent functionality in a new verifyAIP_v1.0 client script (which supersedes verifyAIP_v0.0). **Question:** I have deleted verifyAIP_v0.0 because other superseded client scripts appear to have been deleted. Should it be retained?\r\n\r\nThe new verifyAIP_v1.0 parses the BagIt manifest file for the checksums that `bag` has just calculated instead of needlessly re-calculating them.\r\n\r\nThe functional consequences of this change are as follows.\r\n\r\n1. AIP METS files will no longer have `fixity check` PREMIS events for each file.\r\n2. The AIP pointer file will now contain a single `fixity check` PREMIS event for the entire AIP:\r\n\r\n       <mets:mdWrap MDTYPE=\"PREMIS:EVENT\">\r\n         <mets:xmlData>\r\n           <premis:event xmlns:premis=\"info:lc/xmlns/premis-v2\" xsi:schemaLocation=\"info:lc/xmlns/premis-v2 http://www.loc.gov/standards/premis/v2/premis-v2-2.xsd\" version=\"2.2\">\r\n             <premis:eventIdentifier>\r\n               <premis:eventIdentifierType>UUID</premis:eventIdentifierType>\r\n               <premis:eventIdentifierValue>bdf5c772-1923-4241-a88b-1f949ab542c2</premis:eventIdentifierValue>\r\n             </premis:eventIdentifier>\r\n             <premis:eventType>fixity check</premis:eventType>\r\n             <premis:eventDateTime>2018-03-23T16:57:37.602859+00:00</premis:eventDateTime>\r\n             <premis:eventDetail>program=\"python, bag\"; module=\"hashlib.sha256()\"</premis:eventDetail>\r\n             <premis:eventOutcomeInformation>\r\n               <premis:eventOutcome>Pass</premis:eventOutcome>\r\n               <premis:eventOutcomeDetail>\r\n                 <premis:eventOutcomeDetailNote>\r\n                   All 3 checksums generated at start of transfer match those generated by BagIt (bag).\r\n                 </premis:eventOutcomeDetailNote>\r\n               </premis:eventOutcomeDetail>\r\n             </premis:eventOutcomeInformation>\r\n             <premis:linkingAgentIdentifier>\r\n               <premis:linkingAgentIdentifierType>preservation system</premis:linkingAgentIdentifierType>\r\n               <premis:linkingAgentIdentifierValue>Archivematica-1.7</premis:linkingAgentIdentifierValue>\r\n             </premis:linkingAgentIdentifier>\r\n             <premis:linkingAgentIdentifier>\r\n               <premis:linkingAgentIdentifierType>repository code</premis:linkingAgentIdentifierType>\r\n               <premis:linkingAgentIdentifierValue>test</premis:linkingAgentIdentifierValue>\r\n             </premis:linkingAgentIdentifier>\r\n           </premis:event>\r\n         </mets:xmlData>\r\n       </mets:mdWrap>\r\n\r\n3. AIPs without pointer files will have no record of the SIP/AIP-level fixity check. [This was determined to be an acceptable trade-off](https://github.com/artefactual/archivematica/issues/918#issuecomment-364611999) and should be remedied once we [begin creating pointer files for uncompressed AIPs] (artefactual/archivematica-storage-service#324), which is motivated by other considerations as well.","created_at":"2018-03-23T17:27:41Z","updated_at":"2018-03-23T20:30:54Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e21bdabafe1c9cbb51ca5cf8a31c9ce2259090e0","assignee":{"login":"jrwdunham","id":2504271,"avatar_url":"https://avatars1.githubusercontent.com/u/2504271?v=4","gravatar_id":"","url":"https://api.github.com/users/jrwdunham","html_url":"https://github.com/jrwdunham","followers_url":"https://api.github.com/users/jrwdunham/followers","following_url":"https://api.github.com/users/jrwdunham/following{/other_user}","gists_url":"https://api.github.com/users/jrwdunham/gists{/gist_id}","starred_url":"https://api.github.com/users/jrwdunham/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jrwdunham/subscriptions","organizations_url":"https://api.github.com/users/jrwdunham/orgs","repos_url":"https://api.github.com/users/jrwdunham/repos","events_url":"https://api.github.com/users/jrwdunham/events{/privacy}","received_events_url":"https://api.github.com/users/jrwdunham/received_events","type":"User","site_admin":false},"assignees":[{"login":"jrwdunham","id":2504271,"avatar_url":"https://avatars1.githubusercontent.com/u/2504271?v=4","gravatar_id":"","url":"https://api.github.com/users/jrwdunham","html_url":"https://github.com/jrwdunham","followers_url":"https://api.github.com/users/jrwdunham/followers","following_url":"https://api.github.com/users/jrwdunham/following{/other_user}","gists_url":"https://api.github.com/users/jrwdunham/gists{/gist_id}","starred_url":"https://api.github.com/users/jrwdunham/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jrwdunham/subscriptions","organizations_url":"https://api.github.com/users/jrwdunham/orgs","repos_url":"https://api.github.com/users/jrwdunham/repos","events_url":"https://api.github.com/users/jrwdunham/events{/privacy}","received_events_url":"https://api.github.com/users/jrwdunham/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":784147449,"url":"https://api.github.com/repos/artefactual/archivematica/labels/CUL:%20phase%202","name":"CUL: phase 2","color":"458bd1","default":false},{"id":723957976,"url":"https://api.github.com/repos/artefactual/archivematica/labels/Columbia%20University%20Library","name":"Columbia University Library","color":"76a6d4","default":false}],"milestone":{"url":"https://api.github.com/repos/artefactual/archivematica/milestones/8","html_url":"https://github.com/artefactual/archivematica/milestone/8","labels_url":"https://api.github.com/repos/artefactual/archivematica/milestones/8/labels","id":2430307,"number":8,"title":"1.8.0","description":"","creator":{"login":"sevein","id":606459,"avatar_url":"https://avatars2.githubusercontent.com/u/606459?v=4","gravatar_id":"","url":"https://api.github.com/users/sevein","html_url":"https://github.com/sevein","followers_url":"https://api.github.com/users/sevein/followers","following_url":"https://api.github.com/users/sevein/following{/other_user}","gists_url":"https://api.github.com/users/sevein/gists{/gist_id}","starred_url":"https://api.github.com/users/sevein/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sevein/subscriptions","organizations_url":"https://api.github.com/users/sevein/orgs","repos_url":"https://api.github.com/users/sevein/repos","events_url":"https://api.github.com/users/sevein/events{/privacy}","received_events_url":"https://api.github.com/users/sevein/received_events","type":"User","site_admin":false},"open_issues":29,"closed_issues":18,"state":"open","created_at":"2017-04-03T15:02:32Z","updated_at":"2018-03-23T17:28:11Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/artefactual/archivematica/pulls/1012/commits","review_comments_url":"https://api.github.com/repos/artefactual/archivematica/pulls/1012/comments","review_comment_url":"https://api.github.com/repos/artefactual/archivematica/pulls/comments{/number}","comments_url":"https://api.github.com/repos/artefactual/archivematica/issues/1012/comments","statuses_url":"https://api.github.com/repos/artefactual/archivematica/statuses/defb42c067ee1118463939c3b22de7cf2aa3e20c","head":{"label":"artefactual:dev/issue-918-deduplicate-checksum-verification","ref":"dev/issue-918-deduplicate-checksum-verification","sha":"defb42c067ee1118463939c3b22de7cf2aa3e20c","user":{"login":"artefactual","id":1163545,"avatar_url":"https://avatars1.githubusercontent.com/u/1163545?v=4","gravatar_id":"","url":"https://api.github.com/users/artefactual","html_url":"https://github.com/artefactual","followers_url":"https://api.github.com/users/artefactual/followers","following_url":"https://api.github.com/users/artefactual/following{/other_user}","gists_url":"https://api.github.com/users/artefactual/gists{/gist_id}","starred_url":"https://api.github.com/users/artefactual/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/artefactual/subscriptions","organizations_url":"https://api.github.com/users/artefactual/orgs","repos_url":"https://api.github.com/users/artefactual/repos","events_url":"https://api.github.com/users/artefactual/events{/privacy}","received_events_url":"https://api.github.com/users/artefactual/received_events","type":"Organization","site_admin":false},"repo":{"id":5811958,"name":"archivematica","full_name":"artefactual/archivematica","owner":{"login":"artefactual","id":1163545,"avatar_url":"https://avatars1.githubusercontent.com/u/1163545?v=4","gravatar_id":"","url":"https://api.github.com/users/artefactual","html_url":"https://github.com/artefactual","followers_url":"https://api.github.com/users/artefactual/followers","following_url":"https://api.github.com/users/artefactual/following{/other_user}","gists_url":"https://api.github.com/users/artefactual/gists{/gist_id}","starred_url":"https://api.github.com/users/artefactual/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/artefactual/subscriptions","organizations_url":"https://api.github.com/users/artefactual/orgs","repos_url":"https://api.github.com/users/artefactual/repos","events_url":"https://api.github.com/users/artefactual/events{/privacy}","received_events_url":"https://api.github.com/users/artefactual/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/artefactual/archivematica","description":"Free and open-source digital preservation system designed to maintain standards-based, long-term access to collections of digital objects.","fork":false,"url":"https://api.github.com/repos/artefactual/archivematica","forks_url":"https://api.github.com/repos/artefactual/archivematica/forks","keys_url":"https://api.github.com/repos/artefactual/archivematica/keys{/key_id}","collaborators_url":"https://api.github.com/repos/artefactual/archivematica/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/artefactual/archivematica/teams","hooks_url":"https://api.github.com/repos/artefactual/archivematica/hooks","issue_events_url":"https://api.github.com/repos/artefactual/archivematica/issues/events{/number}","events_url":"https://api.github.com/repos/artefactual/archivematica/events","assignees_url":"https://api.github.com/repos/artefactual/archivematica/assignees{/user}","branches_url":"https://api.github.com/repos/artefactual/archivematica/branches{/branch}","tags_url":"https://api.github.com/repos/artefactual/archivematica/tags","blobs_url":"https://api.github.com/repos/artefactual/archivematica/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/artefactual/archivematica/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/artefactual/archivematica/git/refs{/sha}","trees_url":"https://api.github.com/repos/artefactual/archivematica/git/trees{/sha}","statuses_url":"https://api.github.com/repos/artefactual/archivematica/statuses/{sha}","languages_url":"https://api.github.com/repos/artefactual/archivematica/languages","stargazers_url":"https://api.github.com/repos/artefactual/archivematica/stargazers","contributors_url":"https://api.github.com/repos/artefactual/archivematica/contributors","subscribers_url":"https://api.github.com/repos/artefactual/archivematica/subscribers","subscription_url":"https://api.github.com/repos/artefactual/archivematica/subscription","commits_url":"https://api.github.com/repos/artefactual/archivematica/commits{/sha}","git_commits_url":"https://api.github.com/repos/artefactual/archivematica/git/commits{/sha}","comments_url":"https://api.github.com/repos/artefactual/archivematica/comments{/number}","issue_comment_url":"https://api.github.com/repos/artefactual/archivematica/issues/comments{/number}","contents_url":"https://api.github.com/repos/artefactual/archivematica/contents/{+path}","compare_url":"https://api.github.com/repos/artefactual/archivematica/compare/{base}...{head}","merges_url":"https://api.github.com/repos/artefactual/archivematica/merges","archive_url":"https://api.github.com/repos/artefactual/archivematica/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/artefactual/archivematica/downloads","issues_url":"https://api.github.com/repos/artefactual/archivematica/issues{/number}","pulls_url":"https://api.github.com/repos/artefactual/archivematica/pulls{/number}","milestones_url":"https://api.github.com/repos/artefactual/archivematica/milestones{/number}","notifications_url":"https://api.github.com/repos/artefactual/archivematica/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/artefactual/archivematica/labels{/name}","releases_url":"https://api.github.com/repos/artefactual/archivematica/releases{/id}","deployments_url":"https://api.github.com/repos/artefactual/archivematica/deployments","created_at":"2012-09-14T16:43:38Z","updated_at":"2018-03-23T18:21:07Z","pushed_at":"2018-03-23T18:21:13Z","git_url":"git://github.com/artefactual/archivematica.git","ssh_url":"git@github.com:artefactual/archivematica.git","clone_url":"https://github.com/artefactual/archivematica.git","svn_url":"https://github.com/artefactual/archivematica","homepage":"http://www.archivematica.org","size":33354,"stargazers_count":130,"watchers_count":130,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":47,"mirror_url":null,"archived":false,"open_issues_count":108,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":47,"open_issues":108,"watchers":130,"default_branch":"stable/1.7.x"}},"base":{"label":"artefactual:qa/1.x","ref":"qa/1.x","sha":"93738b26cba5176fd6ee64dcd3104b4a0eb4b4fc","user":{"login":"artefactual","id":1163545,"avatar_url":"https://avatars1.githubusercontent.com/u/1163545?v=4","gravatar_id":"","url":"https://api.github.com/users/artefactual","html_url":"https://github.com/artefactual","followers_url":"https://api.github.com/users/artefactual/followers","following_url":"https://api.github.com/users/artefactual/following{/other_user}","gists_url":"https://api.github.com/users/artefactual/gists{/gist_id}","starred_url":"https://api.github.com/users/artefactual/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/artefactual/subscriptions","organizations_url":"https://api.github.com/users/artefactual/orgs","repos_url":"https://api.github.com/users/artefactual/repos","events_url":"https://api.github.com/users/artefactual/events{/privacy}","received_events_url":"https://api.github.com/users/artefactual/received_events","type":"Organization","site_admin":false},"repo":{"id":5811958,"name":"archivematica","full_name":"artefactual/archivematica","owner":{"login":"artefactual","id":1163545,"avatar_url":"https://avatars1.githubusercontent.com/u/1163545?v=4","gravatar_id":"","url":"https://api.github.com/users/artefactual","html_url":"https://github.com/artefactual","followers_url":"https://api.github.com/users/artefactual/followers","following_url":"https://api.github.com/users/artefactual/following{/other_user}","gists_url":"https://api.github.com/users/artefactual/gists{/gist_id}","starred_url":"https://api.github.com/users/artefactual/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/artefactual/subscriptions","organizations_url":"https://api.github.com/users/artefactual/orgs","repos_url":"https://api.github.com/users/artefactual/repos","events_url":"https://api.github.com/users/artefactual/events{/privacy}","received_events_url":"https://api.github.com/users/artefactual/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/artefactual/archivematica","description":"Free and open-source digital preservation system designed to maintain standards-based, long-term access to collections of digital objects.","fork":false,"url":"https://api.github.com/repos/artefactual/archivematica","forks_url":"https://api.github.com/repos/artefactual/archivematica/forks","keys_url":"https://api.github.com/repos/artefactual/archivematica/keys{/key_id}","collaborators_url":"https://api.github.com/repos/artefactual/archivematica/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/artefactual/archivematica/teams","hooks_url":"https://api.github.com/repos/artefactual/archivematica/hooks","issue_events_url":"https://api.github.com/repos/artefactual/archivematica/issues/events{/number}","events_url":"https://api.github.com/repos/artefactual/archivematica/events","assignees_url":"https://api.github.com/repos/artefactual/archivematica/assignees{/user}","branches_url":"https://api.github.com/repos/artefactual/archivematica/branches{/branch}","tags_url":"https://api.github.com/repos/artefactual/archivematica/tags","blobs_url":"https://api.github.com/repos/artefactual/archivematica/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/artefactual/archivematica/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/artefactual/archivematica/git/refs{/sha}","trees_url":"https://api.github.com/repos/artefactual/archivematica/git/trees{/sha}","statuses_url":"https://api.github.com/repos/artefactual/archivematica/statuses/{sha}","languages_url":"https://api.github.com/repos/artefactual/archivematica/languages","stargazers_url":"https://api.github.com/repos/artefactual/archivematica/stargazers","contributors_url":"https://api.github.com/repos/artefactual/archivematica/contributors","subscribers_url":"https://api.github.com/repos/artefactual/archivematica/subscribers","subscription_url":"https://api.github.com/repos/artefactual/archivematica/subscription","commits_url":"https://api.github.com/repos/artefactual/archivematica/commits{/sha}","git_commits_url":"https://api.github.com/repos/artefactual/archivematica/git/commits{/sha}","comments_url":"https://api.github.com/repos/artefactual/archivematica/comments{/number}","issue_comment_url":"https://api.github.com/repos/artefactual/archivematica/issues/comments{/number}","contents_url":"https://api.github.com/repos/artefactual/archivematica/contents/{+path}","compare_url":"https://api.github.com/repos/artefactual/archivematica/compare/{base}...{head}","merges_url":"https://api.github.com/repos/artefactual/archivematica/merges","archive_url":"https://api.github.com/repos/artefactual/archivematica/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/artefactual/archivematica/downloads","issues_url":"https://api.github.com/repos/artefactual/archivematica/issues{/number}","pulls_url":"https://api.github.com/repos/artefactual/archivematica/pulls{/number}","milestones_url":"https://api.github.com/repos/artefactual/archivematica/milestones{/number}","notifications_url":"https://api.github.com/repos/artefactual/archivematica/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/artefactual/archivematica/labels{/name}","releases_url":"https://api.github.com/repos/artefactual/archivematica/releases{/id}","deployments_url":"https://api.github.com/repos/artefactual/archivematica/deployments","created_at":"2012-09-14T16:43:38Z","updated_at":"2018-03-23T18:21:07Z","pushed_at":"2018-03-23T18:21:13Z","git_url":"git://github.com/artefactual/archivematica.git","ssh_url":"git@github.com:artefactual/archivematica.git","clone_url":"https://github.com/artefactual/archivematica.git","svn_url":"https://github.com/artefactual/archivematica","homepage":"http://www.archivematica.org","size":33354,"stargazers_count":130,"watchers_count":130,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":47,"mirror_url":null,"archived":false,"open_issues_count":108,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":47,"open_issues":108,"watchers":130,"default_branch":"stable/1.7.x"}},"_links":{"self":{"href":"https://api.github.com/repos/artefactual/archivematica/pulls/1012"},"html":{"href":"https://github.com/artefactual/archivematica/pull/1012"},"issue":{"href":"https://api.github.com/repos/artefactual/archivematica/issues/1012"},"comments":{"href":"https://api.github.com/repos/artefactual/archivematica/issues/1012/comments"},"review_comments":{"href":"https://api.github.com/repos/artefactual/archivematica/pulls/1012/comments"},"review_comment":{"href":"https://api.github.com/repos/artefactual/archivematica/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/artefactual/archivematica/pulls/1012/commits"},"statuses":{"href":"https://api.github.com/repos/artefactual/archivematica/statuses/defb42c067ee1118463939c3b22de7cf2aa3e20c"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"sevein"}}