diff --git a/plugins/55/gitbacked/.github/workflows/build_release.yml b/plugins/55/gitbacked/.github/workflows/build_release.yml new file mode 100644 index 0000000..15ee006 --- /dev/null +++ b/plugins/55/gitbacked/.github/workflows/build_release.yml @@ -0,0 +1,196 @@ +# ------------------------------------------------------------------------------------------ +# This is a workflow to release this project as a zipped installable artifact. +# Release version numbering and release notes generation is following standards defined by: +# +# https://semver.org +# https://keepachangelog.com +# https://common-changelog.org +# +# Note: Since DokuWiki is using version numbering in format YYYY-MM-DD we use this numbering +# format rather than a dotted numbering scheme. +# The git tag names have to use a 'v' as prefix to the DokuWiki version number. +# +# ------------------------------------------------------------------------------------------ +name: Build a release + +on: + # Triggers the workflow on push of a tag filtering the tag to meet + # semantic version numbering according to https://semver.org + # Here we use the DokuWiki conform version number pattern. + push: + tags: + ['v[0-9]+-[0-9]+-[0-9]+'] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + # Ensure that we run on tag references only + validate_github_reference: + name: Validate the tag reference + # The type of runner that the job will run on + runs-on: ubuntu-latest + # Validate tag + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + steps: + - run: | + echo "The selected git ref=${{ github.ref }} is NOT a valid release tag. Please select a valid release TAG as reference." + exit 1 + + # Create a release + release: + name: Release + # The type of runner that the job will run on + runs-on: ubuntu-latest + # Set job wide environment + env: + APP_NAME: dokuwiki-plugin-gitbacked + APP_INFO_FILE: plugin.info.txt + APP_INFO_FILE_VERSION_KEY: date + BUILD_DIR: build + ZIP_EXCLUSIONS: '*.git* .editorconfig /*.github/* /*build/* RELEASE_HEAD.md' + + steps: + # Log use case if triggered manually + - name: Log use case if triggered manually + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + echo "Workflow has been triggered manually" + + # Log use case if triggered by push + - name: Log use case if triggered by push + if: ${{ github.event_name == 'push' }} + run: | + echo "Workflow has been triggered by push to ${{ github.ref }}" + + # Check out this repo + - name: Checkout + uses: actions/checkout@v3 + + # Set version tags as global environment properties + - name: Prepare Version Tags + run: | + #echo "MAJOR_VERSION=$(echo ${GITHUB_REF/refs\/tags\//} | awk -F'-' '{print $1}')" >> $GITHUB_ENV + #echo "MINOR_VERSION=$(echo ${GITHUB_REF/refs\/tags\//} | awk -F'-' '{print $1"-"$2}')" >> $GITHUB_ENV + #echo "FULL_VERSION=$(echo ${GITHUB_REF/refs\/tags\//} | awk -F'-' '{print $1"-"$2"-"$3}')" >> $GITHUB_ENV + echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV + echo "APP_INFO_VERSION=$(sed -n -E 's/^${{ env.APP_INFO_FILE_VERSION_KEY }}[ \t]+([0-9-]+).*/\1/p' ${{ env.APP_INFO_FILE }})" >> $GITHUB_ENV + + # Validate app info version and set release name + - name: Validate app info version and set release name + run: | + if [ "${{ env.RELEASE_VERSION }}" != "${{ env.APP_INFO_VERSION }}" ]; then + echo "Mismatch of release version=${{ env.RELEASE_VERSION }} and application info version=${{ env.APP_INFO_VERSION }}!" >&2 + echo "Please review the value for key=${{ env.APP_INFO_FILE_VERSION_KEY }} in file ${{ env.APP_INFO_FILE }}." + exit 1 + fi + echo "RELEASE_NAME=Release ${{ env.APP_INFO_VERSION }}" >> $GITHUB_ENV + + - name: Validate CHANGELOG.md for this release version + # explanation of sed command: + # 1. select lines between SED_VERSION_BEGIN_PATTERN and SED_VERSION_END_PATTERN + # 2. invert this selection + # 3. delete it + # => only selection is remaining in stream + run: | + SED_VERSION_BEGIN_PATTERN="/^## \\[${{ env.RELEASE_VERSION }}\\]/" + SED_VERSION_END_PATTERN="/^## /" + echo "Pattern used for sed: ${SED_VERSION_BEGIN_PATTERN},${SED_VERSION_END_PATTERN} ! d" + # + # Extract the release notes for this RELEASE_VERSION including the line of the previous version: + # + RELEASE_NOTES_WITH_PREV_VERSION=$(sed -e "${SED_VERSION_BEGIN_PATTERN},${SED_VERSION_END_PATTERN} ! d" CHANGELOG.md) + echo ">>>>>> RELEASE_NOTES_WITH_PREV_VERSION - BEGIN >>>>>>" + echo "${RELEASE_NOTES_WITH_PREV_VERSION}" + echo "<<<<<< RELEASE_NOTES_WITH_PREV_VERSION - END <<<<<<" + # + # Format the release notes: + # + # 1. Remove last 2 lines: head -n 2 + # 2. Remove any empty line from the end: sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' + # (s. http://sed.sourceforge.net/sed1line.txt for reference) + # + #RELEASE_VERSION_NOTES=$(echo "$RELEASE_NOTES_WITH_PREV_VERSION" | head -n -2 | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}') + #echo "${RELEASE_VERSION_NOTES}" >> RELEASE.md + #printf "\n" >> RELEASE.md + # + # Extract previous release version: + # + # 1. Cut the last line only: tail -1 + # 2. Get the version from the enclosing [] brackets: awk -F "[][]" '{ print $2 }' + # + PREV_RELEASE_VERSION=$(echo "$RELEASE_NOTES_WITH_PREV_VERSION" | tail -1 | awk -F "[][]" '{ print $2 }') + if [ -z "$PREV_RELEASE_VERSION" ]; then + EXPECTED_COMPARE_URL="${{ github.server_url }}/${{ github.repository }}/releases/tag/v${{ env.RELEASE_VERSION }}" + else + EXPECTED_COMPARE_URL="${{ github.server_url }}/${{ github.repository }}/compare/v${PREV_RELEASE_VERSION}..v${{ env.RELEASE_VERSION }}" + fi + # Validate CHANGELOG.md content + IS_OK="true" + if ! grep -q "^## \\[${{ env.RELEASE_VERSION }}\\]" CHANGELOG.md; then + IS_OK="false" + echo "ERROR: CHANGELOG.md does not contain an entry for this release version of format: ## [${{ env.RELEASE_VERSION }}]" + fi + if ! grep -q "^\\[${{ env.RELEASE_VERSION }}\\]: ${EXPECTED_COMPARE_URL}" CHANGELOG.md; then + IS_OK="false" + echo "ERROR: CHANGELOG.md does not contain a line with a compare link of format: [${{ env.RELEASE_VERSION }}]: ${EXPECTED_COMPARE_URL}" + fi + if [ "$IS_OK" != "true" ]; then + echo "Please review CHANGELOG.md and update it for the content expected." + exit 1 + fi + + # Prepare release notes and build directory + - name: Prepare release notes and build directory + run: | + mkdir ${{ env.BUILD_DIR }} + #cp ./README.md ${{ env.BUILD_DIR }}/README.md + touch ${{ env.BUILD_DIR }}/README.md + cp ./CHANGELOG.md ${{ env.BUILD_DIR }}/CHANGELOG.md + cp ./.github/workflows/resources/RELEASE_HEAD.md ${{ env.BUILD_DIR }}/RELEASE_HEAD.md + + # Format the filename of this release + - name: Format release filename + id: format_release_filename + run: | + echo "FILE_NAME=${{ env.APP_NAME }}-${{ env.APP_INFO_VERSION }}.zip" >> $GITHUB_OUTPUT + + # Create archive file + - name: Build release archive + uses: GHCICD/zip-release@master + with: + type: 'zip' + filename: ${{ env.BUILD_DIR }}/${{ steps.format_release_filename.outputs.FILE_NAME }} + exclusions: ${{ env.ZIP_EXCLUSIONS }} + + # Create release notes by release-notes-from-changelog + - name: Create release notes by GHCICD/release-notes-from-changelog@v1 + uses: GHCICD/release-notes-from-changelog@v1 + with: + version: ${{ env.RELEASE_VERSION }} + working-directory: ${{ env.BUILD_DIR }} + + - name: Log RELEASE.md + run: | + echo ">>>>> build/RELEASE.md:" + cat ${{ env.BUILD_DIR }}/RELEASE.md + echo "<<<<<" +# echo ">>>>> build/CHANGELOG.md:" +# cat ${{ env.BUILD_DIR }}/CHANGELOG.md +# echo "<<<<<" + + # Create release with info from CHANGELOG.md + - name: Create GitHub release by ncipollo/release-action@v1 + id: create_release + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + name: ${{ env.RELEASE_NAME }} + tag: ${{ env.VERSION_TAG }} + bodyFile: ${{ env.BUILD_DIR }}/RELEASE.md + artifacts: ${{ env.BUILD_DIR }}/${{ steps.format_release_filename.outputs.FILE_NAME }} + artifactContentType: application/zip +# +# EOF +# \ No newline at end of file diff --git a/plugins/55/gitbacked/.github/workflows/docs/HowTo_MANAGE_RELEASES.md b/plugins/55/gitbacked/.github/workflows/docs/HowTo_MANAGE_RELEASES.md new file mode 100644 index 0000000..9734807 --- /dev/null +++ b/plugins/55/gitbacked/.github/workflows/docs/HowTo_MANAGE_RELEASES.md @@ -0,0 +1,19 @@ +# Manage Releases + +## Release version naming +- This plugin is provided as released DokuWiki installable ZIP packages with detailed release notes + via this repos [Release](https://github.com/woolfg/dokuwiki-plugin-gitbacked/releases) page. +- The name of a release is identical to the `date` property in `plugin.info.txt` of that release. + +## Pre-Requisites +- The release notes have to be maintained manually in [CHANGELOG.md](https://github.com/woolfg/dokuwiki-plugin-gitbacked/blob/master/CHANGELOG.md) - any details can be found in the comment section within this file. + +## Building a release +- Releases are built by the `build_release.yml` GitHub Action workflow of this project. +- A release build is triggered by applying a tag with name '**v**YYYY-MM-DD' to the corresponding most recent commit of this release. +- The release workflow is not triggered, if: + - The release tag is not of format `v[0-9]+-[0-9]+-[0-9]+` +- The release workflow is failing and no release will be created, if: + - The release version after the 'v'-prefix does not match the `date` property in file `plugin.info.txt` + - The `CHANGELOG.md` does not contain a line of format '# [YYYY-MM-DD]' matching the release version + - The `CHANGELOG.md` does not contain an appropriate compare link versus the previous release version at the end of the `CHANGELOG.md` file diff --git a/plugins/55/gitbacked/.github/workflows/resources/RELEASE_HEAD.md b/plugins/55/gitbacked/.github/workflows/resources/RELEASE_HEAD.md new file mode 100644 index 0000000..d1ebc06 --- /dev/null +++ b/plugins/55/gitbacked/.github/workflows/resources/RELEASE_HEAD.md @@ -0,0 +1,5 @@ +# dokuwiki-plugin-gitbacked + +gitbacked Plugin for DokuWiki - Store/Sync pages and media files in a git repository + +Release notes for this version: \ No newline at end of file diff --git a/plugins/55/gitbacked/CHANGELOG.md b/plugins/55/gitbacked/CHANGELOG.md new file mode 100644 index 0000000..9278ef4 --- /dev/null +++ b/plugins/55/gitbacked/CHANGELOG.md @@ -0,0 +1,185 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + + + + + +## [Unreleased] + +### Changed +- TBD + + +## [2023-05-07] + +### Fixed +- Deprecation warnings raised on `action/editcommit.php` - fixes [#86] + + +## [2023-03-07] + +### Changed +- Allow absolute path in `'repoPath'` and/or `'repoWorkDir'` - implements [#80] +- `'repoWorkDir'` is configured empty by default now +- `--work-tree` option is ommited, if `'repoWorkDir'` is empty - addressing [#79] + +### Fixed +- Cyrillic commit messages not being corrupted anymore - fixes [#82] + + +## [2022-02-06] + +### Changed +- Created LICENSE file and removed corresponding text from the README.md - implements [#67] +- Use DokuWiki's user name & email address as commit author - implements [#63], [#66] + - Updated default setting for `$conf['addParams']` to apply DokuWiki user name as commit author and DokuWiki user eMail as eMail. + - If DokuWiki user eMail is empty, then the eMail assigned to the commit will be empty as well. +- Updated README.md: + - Added a link to the referred COPYING license file originally hosted on the DokuWiki master branch to simplify a probable lookup. + - Issues linked on startpage, motivate people to contribute + +### Fixed +- Allow empty commits - fixes [#39] + + +## [2022-01-20] + +### Fixed +- Fix for compatibility to PHP versions <7.4 - was introduced by previous release - fixes [#69] + + +## [2021-03-19] + +### Added +- Extended to send error messages to a configurable eMail address - implements [#53] +- Added config `'emailAddressOnError'` +- Added config `'notifyByMailOnSuccess'` +- Added localizations for error messages +- Added eMail templates for mail notifications +- German translations added + + +## [2016-08-14] + +### Changed +- Updated last change date to current date - fix [#38] + +### Fixed +- Adjusted method signatures to match parent in action/editcommit.php +- Corrected method signature for php7-compatibility in action/editcommit.php + + +## [2015-10-03] + +### Added +- Allow name and mail user variables in addParams. +- Add an option for customizing git working tree +- Added setting ignorePaths to ignore specified paths in add/commit-process + +### Changed +- Use Markdown for the GitHub README. +- Update plugin date and URL, added Carsten Teibes as author +- Pull latest git php library (0.1.4) +- Allow to set the path to the git binary - implements [#8] +- Use relative path for Git.php and `$conf['tempdir']` for temp file. +- Coding compliance change: move handle_periodic_pull down, together with other "handle"s. + +### Fixed +- Fix passing additional arguments to git binary +- Fix lang typos. +- Coding compliance change, tabs to spaces, fix typos. +- dokuwiki Farm fix + + +## [2012-10-31] + +### Added +- Initial release + +### Comments +- The release name complies with the date property of plugin.info.txt +- The recent commit within this release is [2dbc1a5](https://github.com/woolfg/dokuwiki-plugin-gitbacked/commit/2dbc1a5564516b801dbda239b68152edb5be0303) of 13-Nov-2012 + + + +[Unreleased]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/compare/v2023-05-07..HEAD +[2023-05-07]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/compare/v2023-03-07..v2023-05-07 +[2023-03-07]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/compare/v2022-02-06..v2023-03-07 +[2022-02-06]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/compare/v2022-01-20..v2022-02-06 +[2022-01-20]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/compare/v2021-03-19..v2022-01-20 +[2021-03-19]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/compare/v2016-08-14..v2021-03-19 +[2016-08-14]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/compare/v2015-10-03..v2016-08-14 +[2015-10-03]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/compare/v2012-10-31..v2015-10-03 +[2012-10-31]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/releases/tag/v2012-10-31 +[#86]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/86 +[#82]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/82 +[#80]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/80 +[#79]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/79 +[#69]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/69 +[#67]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/67 +[#66]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/66 +[#63]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/63 +[#53]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/53 +[#39]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/39 +[#38]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/38 +[#8]: https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues/8 diff --git a/plugins/55/gitbacked/LICENSE b/plugins/55/gitbacked/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/plugins/55/gitbacked/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/plugins/55/gitbacked/README.md b/plugins/55/gitbacked/README.md new file mode 100644 index 0000000..03532f0 --- /dev/null +++ b/plugins/55/gitbacked/README.md @@ -0,0 +1,46 @@ +# gitbacked Plugin for DokuWiki + +## :green_heart: Contributions welcome :green_heart: + +You want to support Open Source, even if you are new to the game? +Feel free to grab an issue: + +- [Smaller issues, also well suited for newcomers](https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues?q=is%3Aissue+is%3Aopen+label%3Acontributionwelcome) +- [Feature requests and other cool ideas](https://github.com/woolfg/dokuwiki-plugin-gitbacked/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22) + +If you have encountered a problem, you have a good idea, or just have a question, please, create a new issue. + +## gitbacked Plugin for DokuWiki + +Store/Sync pages and media files in a git repository + +All documentation for this plugin can be found at +http://www.dokuwiki.org/plugin:gitbacked + +This plugin is provided as released DokuWiki installable ZIP packages with detailed [release notes](https://github.com/woolfg/dokuwiki-plugin-gitbacked/blob/master/CHANGELOG.md) +via this repos [Releases](https://github.com/woolfg/dokuwiki-plugin-gitbacked/releases). +Detailed technical information on how releases are built can be found at [HowTo manage releases](https://github.com/woolfg/dokuwiki-plugin-gitbacked/blob/master/.github/workflows/docs/HowTo_MANAGE_RELEASES.md). + +If you install this plugin manually, make sure that: +- you download a **released** `dokuwiki-plugin-gitbacked-YYYY-DD-MM.zip` file + from the [Release](https://github.com/woolfg/dokuwiki-plugin-gitbacked/releases) page. +- this file is to be unpacked in `/lib/plugins/gitbacked/` - if this folder + is called differently it will not work! + +Please refer to http://www.dokuwiki.org/plugins for additional info +on how to install plugins in DokuWiki. + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md) + +## Maintainers + +- [@mhoffrog (Markus Hoffrogge)](https://github.com/mhoffrog) +- [@woolfg (Wolfgang Gassler)](https://github.com/woolfg) + +## License + +This plugin is licensed under GPLv2, see [LICENSE](LICENSE). + +See the [COPYING](https://github.com/splitbrain/dokuwiki/blob/master/COPYING) file in your DokuWiki folder for details diff --git a/plugins/55/gitbacked/action/editcommit.php b/plugins/55/gitbacked/action/editcommit.php new file mode 100644 index 0000000..3f4c1b5 --- /dev/null +++ b/plugins/55/gitbacked/action/editcommit.php @@ -0,0 +1,343 @@ + + */ + +// must be run within Dokuwiki +if (!defined('DOKU_INC')) die(); + +if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); +if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); +if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); + +require_once dirname(__FILE__).'/../lib/Git.php'; +require_once dirname(__FILE__).'/../lib/GitBackedUtil.php'; + +class action_plugin_gitbacked_editcommit extends DokuWiki_Action_Plugin { + + function __construct() { + $this->temp_dir = GitBackedUtil::getTempDir(); + } + + public function register(Doku_Event_Handler $controller) { + + $controller->register_hook('IO_WIKIPAGE_WRITE', 'AFTER', $this, 'handle_io_wikipage_write'); + $controller->register_hook('MEDIA_UPLOAD_FINISH', 'AFTER', $this, 'handle_media_upload'); + $controller->register_hook('MEDIA_DELETE_FILE', 'AFTER', $this, 'handle_media_deletion'); + $controller->register_hook('DOKUWIKI_DONE', 'AFTER', $this, 'handle_periodic_pull'); + } + + private function initRepo() { + //get path to the repo root (by default DokuWiki's savedir) + $repoPath = GitBackedUtil::getEffectivePath($this->getConf('repoPath')); + $gitPath = trim($this->getConf('gitPath')); + if ($gitPath !== '') { + Git::set_bin($gitPath); + } + //init the repo and create a new one if it is not present + io_mkdir_p($repoPath); + $repo = new GitRepo($repoPath, $this, true, true); + //set git working directory (by default DokuWiki's savedir) + $repoWorkDir = $this->getConf('repoWorkDir'); + if (!empty($repoWorkDir)) { + $repoWorkDir = GitBackedUtil::getEffectivePath($repoWorkDir); + } + Git::set_bin(empty($repoWorkDir) ? Git::get_bin() : Git::get_bin().' --work-tree '.escapeshellarg($repoWorkDir)); + $params = str_replace( + array('%mail%','%user%'), + array($this->getAuthorMail(),$this->getAuthor()), + $this->getConf('addParams')); + if ($params) { + Git::set_bin(Git::get_bin().' '.$params); + } + return $repo; + } + + private function isIgnored($filePath) { + $ignore = false; + $ignorePaths = trim($this->getConf('ignorePaths')); + if ($ignorePaths !== '') { + $paths = explode(',',$ignorePaths); + foreach($paths as $path) { + if (strstr($filePath,$path)) { + $ignore = true; + } + } + } + return $ignore; + } + + private function commitFile($filePath,$message) { + if (!$this->isIgnored($filePath)) { + try { + $repo = $this->initRepo(); + + //add the changed file and set the commit message + $repo->add($filePath); + $repo->commit($message); + + //if the push after Commit option is set we push the active branch to origin + if ($this->getConf('pushAfterCommit')) { + $repo->push('origin',$repo->active_branch()); + } + } catch (Exception $e) { + if (!$this->isNotifyByEmailOnGitCommandError()) { + throw new Exception('Git committing or pushing failed: '.$e->getMessage(), 1, $e); + } + return; + } + } + } + + private function getAuthor() { + return $GLOBALS['USERINFO']['name']; + } + + private function getAuthorMail() { + return $GLOBALS['USERINFO']['mail']; + } + + public function handle_periodic_pull(Doku_Event &$event, $param) { + if ($this->getConf('periodicPull')) { + $lastPullFile = $this->temp_dir.'/lastpull.txt'; + //check if the lastPullFile exists + if (is_file($lastPullFile)) { + $lastPull = unserialize(file_get_contents($lastPullFile)); + } else { + $lastPull = 0; + } + //calculate time between pulls in seconds + $timeToWait = $this->getConf('periodicMinutes')*60; + $now = time(); + + //if it is time to run a pull request + if ($lastPull+$timeToWait < $now) { + try { + $repo = $this->initRepo(); + + //execute the pull request + $repo->pull('origin',$repo->active_branch()); + } catch (Exception $e) { + if (!$this->isNotifyByEmailOnGitCommandError()) { + throw new Exception('Git command failed to perform periodic pull: '.$e->getMessage(), 2, $e); + } + return; + } + + //save the current time to the file to track the last pull execution + file_put_contents($lastPullFile,serialize(time())); + } + } + } + + public function handle_media_deletion(Doku_Event &$event, $param) { + $mediaPath = $event->data['path']; + $mediaName = $event->data['name']; + + $message = str_replace( + array('%media%','%user%'), + array($mediaName,$this->getAuthor()), + $this->getConf('commitMediaMsgDel') + ); + + $this->commitFile($mediaPath,$message); + + } + + public function handle_media_upload(Doku_Event &$event, $param) { + + $mediaPath = $event->data[1]; + $mediaName = $event->data[2]; + + $message = str_replace( + array('%media%','%user%'), + array($mediaName,$this->getAuthor()), + $this->getConf('commitMediaMsg') + ); + + $this->commitFile($mediaPath,$message); + + } + + public function handle_io_wikipage_write(Doku_Event &$event, $param) { + + $rev = $event->data[3]; + + /* On update to an existing page this event is called twice, + * once for the transfer of the old version to the attic (rev will have a value) + * and once to write the new version of the page into the wiki (rev is false) + */ + if (!$rev) { + + $pagePath = $event->data[0][0]; + $pageName = $event->data[2]; + $pageContent = $event->data[0][1]; + + // get the summary directly from the form input + // as the metadata hasn't updated yet + $editSummary = $GLOBALS['INPUT']->str('summary'); + + // empty content indicates a page deletion + if ($pageContent == '') { + // get the commit text for deletions + $msgTemplate = $this->getConf('commitPageMsgDel'); + + // bad hack as DokuWiki deletes the file after this event + // thus, let's delete the file by ourselves, so git can recognize the deletion + // DokuWiki uses @unlink as well, so no error should be thrown if we delete it twice + @unlink($pagePath); + + } else { + //get the commit text for edits + $msgTemplate = $this->getConf('commitPageMsg'); + } + + $message = str_replace( + array('%page%','%summary%','%user%'), + array($pageName,$editSummary,$this->getAuthor()), + $msgTemplate + ); + + $this->commitFile($pagePath,$message); + + } + } + + // ====== Error notification helpers ====== + /** + * Notifies error on create_new + * + * @access public + * @param string repository path + * @param string reference path / remote reference + * @param string error message + * @return bool + */ + public function notify_create_new_error($repo_path, $reference, $error_message) { + $template_replacements = array( + 'GIT_REPO_PATH' => $repo_path, + 'GIT_REFERENCE' => (empty($reference) ? 'n/a' : $reference), + 'GIT_ERROR_MESSAGE' => $error_message + ); + return $this->notifyByMail('mail_create_new_error_subject', 'mail_create_new_error', $template_replacements); + } + + /** + * Notifies error on setting repo path + * + * @access public + * @param string repository path + * @param string error message + * @return bool + */ + public function notify_repo_path_error($repo_path, $error_message) { + $template_replacements = array( + 'GIT_REPO_PATH' => $repo_path, + 'GIT_ERROR_MESSAGE' => $error_message + ); + return $this->notifyByMail('mail_repo_path_error_subject', 'mail_repo_path_error', $template_replacements); + } + + /** + * Notifies error on git command + * + * @access public + * @param string repository path + * @param string current working dir + * @param string command line + * @param int exit code of command (status) + * @param string error message + * @return bool + */ + public function notify_command_error($repo_path, $cwd, $command, $status, $error_message) { + $template_replacements = array( + 'GIT_REPO_PATH' => $repo_path, + 'GIT_CWD' => $cwd, + 'GIT_COMMAND' => $command, + 'GIT_COMMAND_EXITCODE' => $status, + 'GIT_ERROR_MESSAGE' => $error_message + ); + return $this->notifyByMail('mail_command_error_subject', 'mail_command_error', $template_replacements); + } + + /** + * Notifies success on git command + * + * @access public + * @param string repository path + * @param string current working dir + * @param string command line + * @return bool + */ + public function notify_command_success($repo_path, $cwd, $command) { + if (!$this->getConf('notifyByMailOnSuccess')) { + return false; + } + $template_replacements = array( + 'GIT_REPO_PATH' => $repo_path, + 'GIT_CWD' => $cwd, + 'GIT_COMMAND' => $command + ); + return $this->notifyByMail('mail_command_success_subject', 'mail_command_success', $template_replacements); + } + + /** + * Send an eMail, if eMail address is configured + * + * @access public + * @param string lang id for the subject + * @param string lang id for the template(.txt) + * @param array array of replacements + * @return bool + */ + public function notifyByMail($subject_id, $template_id, $template_replacements) { + $ret = false; + //dbglog("GitBacked - notifyByMail: [subject_id=".$subject_id.", template_id=".$template_id.", template_replacements=".$template_replacements."]"); + if (!$this->isNotifyByEmailOnGitCommandError()) { + return $ret; + } + //$template_text = rawLocale($template_id); // this works for core artifacts only - not for plugins + $template_filename = $this->localFN($template_id); + $template_text = file_get_contents($template_filename); + $template_html = $this->render_text($template_text); + + $mailer = new \Mailer(); + $mailer->to($this->getEmailAddressOnErrorConfigured()); + //dbglog("GitBacked - lang check['".$subject_id."']: ".$this->getLang($subject_id)); + //dbglog("GitBacked - template text['".$template_id."']: ".$template_text); + //dbglog("GitBacked - template html['".$template_id."']: ".$template_html); + $mailer->subject($this->getLang($subject_id)); + $mailer->setBody($template_text, $template_replacements, null, $template_html); + $ret = $mailer->send(); + + return $ret; + } + + /** + * Check, if eMail is to be sent on a Git command error. + * + * @access public + * @return bool + */ + public function isNotifyByEmailOnGitCommandError() { + $emailAddressOnError = $this->getEmailAddressOnErrorConfigured(); + return !empty($emailAddressOnError); + } + + /** + * Get the eMail address configured for notifications. + * + * @access public + * @return string + */ + public function getEmailAddressOnErrorConfigured() { + $emailAddressOnError = trim($this->getConf('emailAddressOnError')); + return $emailAddressOnError; + } + +} + +// vim:ts=4:sw=4:et: diff --git a/plugins/55/gitbacked/conf/default.php b/plugins/55/gitbacked/conf/default.php new file mode 100644 index 0000000..b664b0f --- /dev/null +++ b/plugins/55/gitbacked/conf/default.php @@ -0,0 +1,21 @@ + + */ + +$conf['pushAfterCommit'] = 0; +$conf['periodicPull'] = 0; +$conf['periodicMinutes'] = 60; +$conf['commitPageMsg'] = 'Wiki page %page% changed with summary [%summary%] by %user%'; +$conf['commitPageMsgDel'] = 'Wiki page %page% deleted with reason [%summary%] by %user%'; +$conf['commitMediaMsg'] = 'Wiki media %media% uploaded by %user%'; +$conf['commitMediaMsgDel'] = 'Wiki media %media% deleted by %user%'; +$conf['repoPath'] = $GLOBALS['conf']['savedir']; +$conf['repoWorkDir'] = ''; +$conf['gitPath'] = ''; +$conf['addParams'] = '-c user.name="%user%" -c user.email="<%mail%>"'; +$conf['ignorePaths'] = ''; +$conf['emailAddressOnError'] = ''; +$conf['notifyByMailOnSuccess'] = 0; diff --git a/plugins/55/gitbacked/conf/metadata.php b/plugins/55/gitbacked/conf/metadata.php new file mode 100644 index 0000000..8ff5d3a --- /dev/null +++ b/plugins/55/gitbacked/conf/metadata.php @@ -0,0 +1,21 @@ + + */ + +$meta['pushAfterCommit'] = array('onoff'); +$meta['periodicPull'] = array('onoff'); +$meta['periodicMinutes'] = array('numeric'); +$meta['commitPageMsg'] = array('string'); +$meta['commitPageMsgDel'] = array('string'); +$meta['commitMediaMsg'] = array('string'); +$meta['commitMediaMsgDel'] = array('string'); +$meta['repoPath'] = array('string'); +$meta['repoWorkDir'] = array('string'); +$meta['gitPath'] = array('string'); +$meta['addParams'] = array('string'); +$meta['ignorePaths'] = array('string'); +$meta['emailAddressOnError'] = array('string'); +$meta['notifyByMailOnSuccess'] = array('onoff'); diff --git a/plugins/55/gitbacked/lang/de/lang.php b/plugins/55/gitbacked/lang/de/lang.php new file mode 100644 index 0000000..cda517a --- /dev/null +++ b/plugins/55/gitbacked/lang/de/lang.php @@ -0,0 +1,6 @@ +@GIT_REPO_PATH@ + + * **Aktuelles Arbeitsverzeichnis:** @GIT_CWD@ + + * **Befehl:** @GIT_COMMAND@ + + * **Exitcode:** @GIT_COMMAND_EXITCODE@ + + * **Fehler:** @GIT_ERROR_MESSAGE@ + + * **Anwender:** @NAME@ + + * **eMail:** @MAIL@ + + * **Browser:** @BROWSER@ + + * **Wiki:** @TITLE@ @DOKUWIKIURL@ diff --git a/plugins/55/gitbacked/lang/de/mail_command_success.txt b/plugins/55/gitbacked/lang/de/mail_command_success.txt new file mode 100644 index 0000000..65c02d2 --- /dev/null +++ b/plugins/55/gitbacked/lang/de/mail_command_success.txt @@ -0,0 +1,15 @@ +==== Der folgende Git Befehl wurde ausgeführt auf @DOKUWIKIURL@ ==== + + * **Repo Pfad:** @GIT_REPO_PATH@ + + * **Aktuelles Arbeitsverzeichnis:** @GIT_CWD@ + + * **Befehl:** @GIT_COMMAND@ + + * **Anwender:** @NAME@ + + * **eMail:** @MAIL@ + + * **Browser:** @BROWSER@ + + * **Wiki:** @TITLE@ @DOKUWIKIURL@ diff --git a/plugins/55/gitbacked/lang/de/mail_create_new_error.txt b/plugins/55/gitbacked/lang/de/mail_create_new_error.txt new file mode 100644 index 0000000..d8ae142 --- /dev/null +++ b/plugins/55/gitbacked/lang/de/mail_create_new_error.txt @@ -0,0 +1,15 @@ +==== FEHLER beim Anlegen eines neuen Git Repositories auf @DOKUWIKIURL@ ==== + + * **Repo Pfad:** @GIT_REPO_PATH@ + + * **Referenz:** @GIT_REFERENCE@ + + * **Fehler:** @GIT_ERROR_MESSAGE@ + + * **Anwender:** @NAME@ + + * **eMail:** @MAIL@ + + * **Browser:** @BROWSER@ + + * **Wiki:** @TITLE@ @DOKUWIKIURL@ diff --git a/plugins/55/gitbacked/lang/de/mail_repo_path_error.txt b/plugins/55/gitbacked/lang/de/mail_repo_path_error.txt new file mode 100644 index 0000000..36387e1 --- /dev/null +++ b/plugins/55/gitbacked/lang/de/mail_repo_path_error.txt @@ -0,0 +1,13 @@ +==== Ein FEHLER mit dem konfigurierten Git Repository Pfad ist aufgetreten auf @DOKUWIKIURL@ ==== + + * **Repo Pfad:** @GIT_REPO_PATH@ + + * **Fehler:** @GIT_ERROR_MESSAGE@ + + * **Anwender:** @NAME@ + + * **eMail:** @MAIL@ + + * **Browser:** @BROWSER@ + + * **Wiki:** @TITLE@ @DOKUWIKIURL@ diff --git a/plugins/55/gitbacked/lang/de/settings.php b/plugins/55/gitbacked/lang/de/settings.php new file mode 100644 index 0000000..a0b6c17 --- /dev/null +++ b/plugins/55/gitbacked/lang/de/settings.php @@ -0,0 +1,22 @@ + + */ + +$lang['pushAfterCommit'] = 'Push des aktiven Branch zum remote origin nach jedem commit'; +$lang['periodicPull'] = 'Pull des remote git Repositories alle "periodicMinutes", getriggert von einem http Page Request'; +$lang['periodicMinutes'] = 'Zeitraum (in Minuten) zwischen den periodischen pull requests'; +$lang['commitPageMsg'] = 'Commit Kommentar für Seitenänderungen (%user%,%summary%,%page% werden durch die tatsächlichen Werte ersetzt)'; +$lang['commitPageMsgDel'] = 'Commit Kommentar für gelöschte Seiten (%user%,%summary%,%page% werden durch die tatsächlichen Werte ersetzt)'; +$lang['commitMediaMsg'] = 'Commit Kommentar for media Dateien (%user%,%media% werden durch die tatsächlichen Werte ersetzt)'; +$lang['commitMediaMsgDel'] = 'Commit Kommentar für gelöschte media Dateien (%user%,%media% werden durch die tatsächlichen Werte ersetzt)'; +$lang['repoPath'] = 'Pfad des git repo (z.B. das savedir '.$GLOBALS['conf']['savedir'].')'; +$lang['repoWorkDir'] = 'Pfad des git working tree. Dieser muss die "pages" and "media" Verzeichnisse enthalten (z.B. das savedir '.$GLOBALS['conf']['savedir'].')'; +$lang['gitPath'] = 'Pfad zum git binary (Wenn leer, dann wird der Standard "/usr/bin/git" verwendet)'; +$lang['addParams'] = 'Zusätzliche git Parameter (diese werden dem git Kommando zugefügt) (%user% und %mail% werden durch die tatsächlichen Werte ersetzt)'; +$lang['ignorePaths'] = 'Pfade/Dateien die ignoriert werden und nicht von git archiviert werden sollen (durch Kommata getrennt)'; +$lang['emailAddressOnError'] = 'Wenn definiert, dann wird bei einem Fehler eine eMail an diese Adresse(n) gesendet, anstatt den aktuellen Endanwender mit einer Exception zu verunsichern. Mehrere Adressen können durch Kommata getrennt konfiguriert werden'; +$lang['notifyByMailOnSuccess'] = 'Wenn emailAddressOnError definiert ist, dann wird bei jedem Commit eine eMail gesendet. Diese Einstellung sollte nur zum Testen der eMail Benachrichtigung aktiviert werden'; diff --git a/plugins/55/gitbacked/lang/en/lang.php b/plugins/55/gitbacked/lang/en/lang.php new file mode 100644 index 0000000..6ea8620 --- /dev/null +++ b/plugins/55/gitbacked/lang/en/lang.php @@ -0,0 +1,6 @@ +@GIT_REPO_PATH@ + + * **Current working dir:** @GIT_CWD@ + + * **Command:** @GIT_COMMAND@ + + * **Exitcode:** @GIT_COMMAND_EXITCODE@ + + * **Error:** @GIT_ERROR_MESSAGE@ + + * **User:** @NAME@ + + * **eMail:** @MAIL@ + + * **Browser:** @BROWSER@ + + * **Wiki:** @TITLE@ @DOKUWIKIURL@ diff --git a/plugins/55/gitbacked/lang/en/mail_command_success.txt b/plugins/55/gitbacked/lang/en/mail_command_success.txt new file mode 100644 index 0000000..6c19a0c --- /dev/null +++ b/plugins/55/gitbacked/lang/en/mail_command_success.txt @@ -0,0 +1,15 @@ +==== The following Git command was performed on @DOKUWIKIURL@ ==== + + * **Repo path:** @GIT_REPO_PATH@ + + * **Current working dir:** @GIT_CWD@ + + * **Command:** @GIT_COMMAND@ + + * **User:** @NAME@ + + * **eMail:** @MAIL@ + + * **Browser:** @BROWSER@ + + * **Wiki:** @TITLE@ @DOKUWIKIURL@ diff --git a/plugins/55/gitbacked/lang/en/mail_create_new_error.txt b/plugins/55/gitbacked/lang/en/mail_create_new_error.txt new file mode 100644 index 0000000..52590f8 --- /dev/null +++ b/plugins/55/gitbacked/lang/en/mail_create_new_error.txt @@ -0,0 +1,15 @@ +==== The creation of a new Git repo FAILED on @DOKUWIKIURL@ ==== + + * **Repo path:** @GIT_REPO_PATH@ + + * **Reference:** @GIT_REFERENCE@ + + * **Error:** @GIT_ERROR_MESSAGE@ + + * **User:** @NAME@ + + * **eMail:** @MAIL@ + + * **Browser:** @BROWSER@ + + * **Wiki:** @TITLE@ @DOKUWIKIURL@ diff --git a/plugins/55/gitbacked/lang/en/mail_repo_path_error.txt b/plugins/55/gitbacked/lang/en/mail_repo_path_error.txt new file mode 100644 index 0000000..d44fecb --- /dev/null +++ b/plugins/55/gitbacked/lang/en/mail_repo_path_error.txt @@ -0,0 +1,13 @@ +==== An FAILURE with the Git repo path occurred on @DOKUWIKIURL@ ==== + + * **Repo path:** @GIT_REPO_PATH@ + + * **Error:** @GIT_ERROR_MESSAGE@ + + * **User:** @NAME@ + + * **eMail:** @MAIL@ + + * **Browser:** @BROWSER@ + + * **Wiki:** @TITLE@ @DOKUWIKIURL@ diff --git a/plugins/55/gitbacked/lang/en/settings.php b/plugins/55/gitbacked/lang/en/settings.php new file mode 100644 index 0000000..3701ec0 --- /dev/null +++ b/plugins/55/gitbacked/lang/en/settings.php @@ -0,0 +1,22 @@ + + */ + +$lang['pushAfterCommit'] = 'Push active branch to remote origin after every commit'; +$lang['periodicPull'] = 'Pull the remote git repository every "periodicMinutes" triggered by a http page request'; +$lang['periodicMinutes'] = 'Timespan (in minutes) between periodic pull requests'; +$lang['commitPageMsg'] = 'Commit message for page edits (%user%,%summary%,%page% are replaced by the corresponding values)'; +$lang['commitPageMsgDel'] = 'Commit message for deleted pages (%user%,%summary%,%page% are replaced by the corresponding values)'; +$lang['commitMediaMsg'] = 'Commit message for media files (%user%,%media% are replaced by the corresponding values)'; +$lang['commitMediaMsgDel'] = 'Commit message for deleted media files (%user%,%media% are replaced by the corresponding values)'; +$lang['repoPath'] = 'Path of the git repo(s) (e.g. the savedir '.$GLOBALS['conf']['savedir'].')'; +$lang['repoWorkDir'] = 'Path of the git working tree, must contain "pages" and "media" directories (e.g. the savedir '.$GLOBALS['conf']['savedir'].')'; +$lang['gitPath'] = 'Path to the git binary (if empty, the default "/usr/bin/git" will be used)'; +$lang['addParams'] = 'Additional git parameters (added to the git execution command) (%user% and %mail% are replaced by the corresponding values)'; +$lang['ignorePaths'] = 'Paths/files which are ignored and not added to git (comma-separated)'; +$lang['emailAddressOnError'] = 'If set, in case of a git error an eMail will be sent to this address rather than confusing the end user by the Exception raised. Multiple mail addresses can be configured comma separated'; +$lang['notifyByMailOnSuccess'] = 'If emailAddressOnError is defined, an eMail will be sent on any git commit. This is supposed to be used for eMail notification test purposes only'; diff --git a/plugins/55/gitbacked/lib/Git.php b/plugins/55/gitbacked/lib/Git.php new file mode 100644 index 0000000..3dca50a --- /dev/null +++ b/plugins/55/gitbacked/lib/Git.php @@ -0,0 +1,787 @@ += 7.4 only. + // protected ?\action_plugin_gitbacked_editcommit $plugin = null; + protected $plugin = null; + + /** + * Create a new git repository + * + * Accepts a creation path, and, optionally, a source path + * + * @access public + * @param string repository path + * @param \action_plugin_gitbacked_editcommit plugin + * @param string directory to source + * @param string reference path + * @return GitRepo or null in case of an error + */ + public static function &create_new($repo_path, \action_plugin_gitbacked_editcommit $plugin = null, $source = null, $remote_source = false, $reference = null) { + if (is_dir($repo_path) && file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) { + throw new Exception(self::handle_create_new_error($repo_path, $reference, '"'.$repo_path.'" is already a git repository', $plugin)); + } else { + $repo = new self($repo_path, $plugin, true, false); + if (is_string($source)) { + if ($remote_source) { + if (!is_dir($reference) || !is_dir($reference.'/.git')) { + throw new Exception(self::handle_create_new_error($repo_path, $reference, '"'.$reference.'" is not a git repository. Cannot use as reference.', $plugin)); + } else if (strlen($reference)) { + $reference = realpath($reference); + $reference = "--reference $reference"; + } + $repo->clone_remote($source, $reference); + } else { + $repo->clone_from($source); + } + } else { + $repo->run('init'); + } + return $repo; + } + } + + /** + * Constructor + * + * Accepts a repository path + * + * @access public + * @param string repository path + * @param \action_plugin_gitbacked_editcommit plugin + * @param bool create if not exists? + * @return void + */ + public function __construct($repo_path = null, \action_plugin_gitbacked_editcommit $plugin = null, $create_new = false, $_init = true) { + $this->plugin = $plugin; + if (is_string($repo_path)) { + $this->set_repo_path($repo_path, $create_new, $_init); + } + } + + /** + * Set the repository's path + * + * Accepts the repository path + * + * @access public + * @param string repository path + * @param bool create if not exists? + * @param bool initialize new Git repo if not exists? + * @return void + */ + public function set_repo_path($repo_path, $create_new = false, $_init = true) { + if (is_string($repo_path)) { + if ($new_path = realpath($repo_path)) { + $repo_path = $new_path; + if (is_dir($repo_path)) { + // Is this a work tree? + if (file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) { + $this->repo_path = $repo_path; + $this->bare = false; + // Is this a bare repo? + } else if (is_file($repo_path."/config")) { + $parse_ini = parse_ini_file($repo_path."/config"); + if ($parse_ini['bare']) { + $this->repo_path = $repo_path; + $this->bare = true; + } + } else { + if ($create_new) { + $this->repo_path = $repo_path; + if ($_init) { + $this->run('init'); + } + } else { + throw new Exception($this->handle_repo_path_error($repo_path, '"'.$repo_path.'" is not a git repository')); + } + } + } else { + throw new Exception($this->handle_repo_path_error($repo_path, '"'.$repo_path.'" is not a directory')); + } + } else { + if ($create_new) { + if ($parent = realpath(dirname($repo_path))) { + mkdir($repo_path); + $this->repo_path = $repo_path; + if ($_init) $this->run('init'); + } else { + throw new Exception($this->handle_repo_path_error($repo_path, 'cannot create repository in non-existent directory')); + } + } else { + throw new Exception($this->handle_repo_path_error($repo_path, '"'.$repo_path.'" does not exist')); + } + } + } + } + + /** + * Get the path to the git repo directory (eg. the ".git" directory) + * + * @access public + * @return string + */ + public function git_directory_path() { + return ($this->bare) ? $this->repo_path : $this->repo_path."/.git"; + } + + /** + * Tests if git is installed + * + * @access public + * @return bool + */ + public function test_git() { + $descriptorspec = array( + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w'), + ); + $pipes = array(); + $resource = proc_open(Git::get_bin(), $descriptorspec, $pipes); + + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + foreach ($pipes as $pipe) { + fclose($pipe); + } + + $status = trim(proc_close($resource)); + return ($status != 127); + } + + /** + * Run a command in the git repository + * + * Accepts a shell command to run + * + * @access protected + * @param string command to run + * @return string or null in case of an error + */ + protected function run_command($command) { + //dbglog("Git->run_command(command=[".$command."])"); + $descriptorspec = array( + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w'), + ); + $pipes = array(); + /* Depending on the value of variables_order, $_ENV may be empty. + * In that case, we have to explicitly set the new variables with + * putenv, and call proc_open with env=null to inherit the reset + * of the system. + * + * This is kind of crappy because we cannot easily restore just those + * variables afterwards. + * + * If $_ENV is not empty, then we can just copy it and be done with it. + */ + if(count($_ENV) === 0) { + $env = NULL; + foreach($this->envopts as $k => $v) { + putenv(sprintf("%s=%s",$k,$v)); + } + } else { + $env = array_merge($_ENV, $this->envopts); + } + $cwd = $this->repo_path; + //dbglog("GitBacked - cwd: [".$cwd."]"); + $resource = proc_open($command, $descriptorspec, $pipes, $cwd, $env); + + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + foreach ($pipes as $pipe) { + fclose($pipe); + } + + $status = trim(proc_close($resource)); + //dbglog("GitBacked: run_command status: ".$status); + if ($status) { + //dbglog("GitBacked - stderr: [".$stderr."]"); + // Remove a probable password from the Git URL, if the URL is contained in the error message + $error_message = preg_replace($this::REGEX_GIT_URL_FILTER_PWD, $this::REGEX_GIT_URL_FILTER_PWD_REPLACE_PATTERN, $stderr); + //dbglog("GitBacked - error_message: [".$error_message."]"); + throw new Exception($this->handle_command_error($this->repo_path, $cwd, $command, $status, $error_message)); + } else { + $this->handle_command_success($this->repo_path, $cwd, $command); + } + + return $stdout; + } + + /** + * Run a git command in the git repository + * + * Accepts a git command to run + * + * @access public + * @param string command to run + * @return string + */ + public function run($command) { + return $this->run_command(Git::get_bin()." ".$command); + } + + /** + * Handles error on create_new + * + * @access protected + * @param string repository path + * @param string error message + * @return string error message + */ + protected static function handle_create_new_error($repo_path, $reference, $error_message, $plugin) { + if ($plugin instanceof \action_plugin_gitbacked_editcommit) { + $plugin->notify_create_new_error($repo_path, $reference, $error_message); + } + return $error_message; + } + + /** + * Handles error on setting the repo path + * + * @access protected + * @param string repository path + * @param string error message + * @return string error message + */ + protected function handle_repo_path_error($repo_path, $error_message) { + if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { + $this->plugin->notify_repo_path_error($repo_path, $error_message); + } + return $error_message; + } + + /** + * Handles error on git command + * + * @access protected + * @param string repository path + * @param string current working dir + * @param string command line + * @param int exit code of command (status) + * @param string error message + * @return string error message + */ + protected function handle_command_error($repo_path, $cwd, $command, $status, $error_message) { + if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { + $this->plugin->notify_command_error($repo_path, $cwd, $command, $status, $error_message); + } + return $error_message; + } + + /** + * Handles success on git command + * + * @access protected + * @param string repository path + * @param string current working dir + * @param string command line + * @return void + */ + protected function handle_command_success($repo_path, $cwd, $command) { + if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { + $this->plugin->notify_command_success($repo_path, $cwd, $command); + } + } + + /** + * Runs a 'git status' call + * + * Accept a convert to HTML bool + * + * @access public + * @param bool return string with
+ * @return string + */ + public function status($html = false) { + $msg = $this->run("status"); + if ($html == true) { + $msg = str_replace("\n", "
", $msg); + } + return $msg; + } + + /** + * Runs a `git add` call + * + * Accepts a list of files to add + * + * @access public + * @param mixed files to add + * @return string + */ + public function add($files = "*") { + if (is_array($files)) { + $files = '"'.implode('" "', $files).'"'; + } + return $this->run("add $files -v"); + } + + /** + * Runs a `git rm` call + * + * Accepts a list of files to remove + * + * @access public + * @param mixed files to remove + * @param Boolean use the --cached flag? + * @return string + */ + public function rm($files = "*", $cached = false) { + if (is_array($files)) { + $files = '"'.implode('" "', $files).'"'; + } + return $this->run("rm ".($cached ? '--cached ' : '').$files); + } + + + /** + * Runs a `git commit` call + * + * Accepts a commit message string + * + * @access public + * @param string commit message + * @param boolean should all files be committed automatically (-a flag) + * @return string + */ + public function commit($message = "", $commit_all = true) { + $flags = $commit_all ? '-av' : '-v'; + $msgfile = GitBackedUtil::createMessageFile($message); + try { + return $this->run("commit --allow-empty ".$flags." --file=".$msgfile); + } finally { + unlink($msgfile); + } + } + + /** + * Runs a `git clone` call to clone the current repository + * into a different directory + * + * Accepts a target directory + * + * @access public + * @param string target directory + * @return string + */ + public function clone_to($target) { + return $this->run("clone --local ".$this->repo_path." $target"); + } + + /** + * Runs a `git clone` call to clone a different repository + * into the current repository + * + * Accepts a source directory + * + * @access public + * @param string source directory + * @return string + */ + public function clone_from($source) { + return $this->run("clone --local $source ".$this->repo_path); + } + + /** + * Runs a `git clone` call to clone a remote repository + * into the current repository + * + * Accepts a source url + * + * @access public + * @param string source url + * @param string reference path + * @return string + */ + public function clone_remote($source, $reference) { + return $this->run("clone $reference $source ".$this->repo_path); + } + + /** + * Runs a `git clean` call + * + * Accepts a remove directories flag + * + * @access public + * @param bool delete directories? + * @param bool force clean? + * @return string + */ + public function clean($dirs = false, $force = false) { + return $this->run("clean".(($force) ? " -f" : "").(($dirs) ? " -d" : "")); + } + + /** + * Runs a `git branch` call + * + * Accepts a name for the branch + * + * @access public + * @param string branch name + * @return string + */ + public function create_branch($branch) { + return $this->run("branch $branch"); + } + + /** + * Runs a `git branch -[d|D]` call + * + * Accepts a name for the branch + * + * @access public + * @param string branch name + * @return string + */ + public function delete_branch($branch, $force = false) { + return $this->run("branch ".(($force) ? '-D' : '-d')." $branch"); + } + + /** + * Runs a `git branch` call + * + * @access public + * @param bool keep asterisk mark on active branch + * @return array + */ + public function list_branches($keep_asterisk = false) { + $branchArray = explode("\n", $this->run("branch")); + foreach($branchArray as $i => &$branch) { + $branch = trim($branch); + if (! $keep_asterisk) { + $branch = str_replace("* ", "", $branch); + } + if ($branch == "") { + unset($branchArray[$i]); + } + } + return $branchArray; + } + + /** + * Lists remote branches (using `git branch -r`). + * + * Also strips out the HEAD reference (e.g. "origin/HEAD -> origin/master"). + * + * @access public + * @return array + */ + public function list_remote_branches() { + $branchArray = explode("\n", $this->run("branch -r")); + foreach($branchArray as $i => &$branch) { + $branch = trim($branch); + if ($branch == "" || strpos($branch, 'HEAD -> ') !== false) { + unset($branchArray[$i]); + } + } + return $branchArray; + } + + /** + * Returns name of active branch + * + * @access public + * @param bool keep asterisk mark on branch name + * @return string + */ + public function active_branch($keep_asterisk = false) { + $branchArray = $this->list_branches(true); + $active_branch = preg_grep("/^\*/", $branchArray); + reset($active_branch); + if ($keep_asterisk) { + return current($active_branch); + } else { + return str_replace("* ", "", current($active_branch)); + } + } + + /** + * Runs a `git checkout` call + * + * Accepts a name for the branch + * + * @access public + * @param string branch name + * @return string + */ + public function checkout($branch) { + return $this->run("checkout $branch"); + } + + + /** + * Runs a `git merge` call + * + * Accepts a name for the branch to be merged + * + * @access public + * @param string $branch + * @return string + */ + public function merge($branch) { + return $this->run("merge $branch --no-ff"); + } + + + /** + * Runs a git fetch on the current branch + * + * @access public + * @return string + */ + public function fetch() { + return $this->run("fetch"); + } + + /** + * Add a new tag on the current position + * + * Accepts the name for the tag and the message + * + * @param string $tag + * @param string $message + * @return string + */ + public function add_tag($tag, $message = null) { + if ($message === null) { + $message = $tag; + } + $msgfile = GitBackedUtil::createMessageFile($message); + try { + return $this->run("tag -a $tag --file=".$msgfile); + } finally { + unlink($msgfile); + } + } + + /** + * List all the available repository tags. + * + * Optionally, accept a shell wildcard pattern and return only tags matching it. + * + * @access public + * @param string $pattern Shell wildcard pattern to match tags against. + * @return array Available repository tags. + */ + public function list_tags($pattern = null) { + $tagArray = explode("\n", $this->run("tag -l $pattern")); + foreach ($tagArray as $i => &$tag) { + $tag = trim($tag); + if ($tag == '') { + unset($tagArray[$i]); + } + } + + return $tagArray; + } + + /** + * Push specific branch to a remote + * + * Accepts the name of the remote and local branch + * + * @param string $remote + * @param string $branch + * @return string + */ + public function push($remote, $branch) { + return $this->run("push --tags $remote $branch"); + } + + /** + * Pull specific branch from remote + * + * Accepts the name of the remote and local branch + * + * @param string $remote + * @param string $branch + * @return string + */ + public function pull($remote, $branch) { + return $this->run("pull $remote $branch"); + } + + /** + * List log entries. + * + * @param strgin $format + * @return string + */ + public function log($format = null) { + if ($format === null) + return $this->run('log'); + else + return $this->run('log --pretty=format:"' . $format . '"'); + } + + /** + * Sets the project description. + * + * @param string $new + */ + public function set_description($new) { + $path = $this->git_directory_path(); + file_put_contents($path."/description", $new); + } + + /** + * Gets the project description. + * + * @return string + */ + public function get_description() { + $path = $this->git_directory_path(); + return file_get_contents($path."/description"); + } + + /** + * Sets custom environment options for calling Git + * + * @param string key + * @param string value + */ + public function setenv($key, $value) { + $this->envopts[$key] = $value; + } + +} + +/* End of file */ diff --git a/plugins/55/gitbacked/lib/GitBackedUtil.php b/plugins/55/gitbacked/lib/GitBackedUtil.php new file mode 100644 index 0000000..ec5b8e9 --- /dev/null +++ b/plugins/55/gitbacked/lib/GitBackedUtil.php @@ -0,0 +1,121 @@ +