add groovy-git

This commit is contained in:
Jörg Prante 2021-11-09 10:53:57 +01:00
parent 57dc419fe3
commit 0cd3177137
131 changed files with 8529 additions and 0 deletions

View file

@ -9,3 +9,5 @@ mail.version = 1.6.2
sshd.version = 2.6.0.0
log4j.version = 2.14.0
junit4.version = 4.13
jgit.version = 5.13.0.202109080827-r
spock.version = 1.2-groovy-2.5

202
groovy-git/LICENSE.txt Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

14
groovy-git/NOTICE.txt Normal file
View file

@ -0,0 +1,14 @@
This work is a fork of Andrew J. Oberstar's grgit project as of 6-Oct-2018
https://github.com/ajoberstar/grgit
Copyight (C) 2013 Andrew J. Oberstar
License: Apache 2.0
This implementation is a fork of the Andrew J. Oberstar's grgit project with a reset of the
version numbering scheme.
It was ensured to build and run under Java 11 and Gradle 5.
Windows implementation and documentation has been removed.
The package structure and documentation have been adjusted;
all references to original authors and versions have been removed for clarity and consistency.

20
groovy-git/README.adoc Normal file
View file

@ -0,0 +1,20 @@
# Groovy Git
NOTE: This implementation is a fork of the Andrew J. Oberstar's https://github.com/ajoberstar/grgit[grgit] project with a reset of the
version numbering scheme.
It was ensured to build and run under Java 11 and Gradle 5.
Windows implementation details and documentation has been removed.
The package structure and documentation have been adjusted;
all references to original authors and versions have been removed for clarity and consistency.
https://eclipse.org/jgit/[JGit] provides a powerful Java API for interacting with Git repositories. However,
in a Groovy context, it feels very cumbersome, making it harder to express the operations you want to perform
without being surrounded by a lot of cruft.
Groovy Git is a wrapper over JGit that provides a fluent API for interacting with Git repositories in Groovy-based
tooling. Features that require more user interaction (such as resolving merge conflicts) are intentionally excluded.
It also provides a Gradle plugin to easily get a Groovy Git instance.
Groovy Git is available from Maven or the Gradle Plugin Portal.

8
groovy-git/build.gradle Normal file
View file

@ -0,0 +1,8 @@
apply from: rootProject.file('gradle/compile/groovy.gradle')
dependencies {
api "org.eclipse.jgit:org.eclipse.jgit:${project.property('jgit.version')}"
testImplementation("org.spockframework:spock-core:${project.property('spock.version')}") {
exclude group: 'org.codehaus.groovy', module: 'groovy-all'
}
}

View file

@ -0,0 +1,60 @@
= add
== Name
add - Add file contents to the index
== Synopsis
[source, groovy]
----
git.add(patterns: ['<path>', ...], update: <boolean>)
----
[source, groovy]
----
git.add {
patterns = ['<path>', ...]
update = <boolean>
}
----
== Description
This command updates the index using the current content found in the working tree, to prepare the content staged for the next commit. It typically adds the current content of existing paths as a whole, but with some options it can also be used to remove paths that do not exist in the working tree anymore.
The "index" holds a snapshot of the content of the working tree, and it is this snapshot that is taken as the contents of the next commit. Thus after making any changes to the working tree, and before running the commit command, you must use the add command to add any new or modified files to the index.
This command can be performed multiple times before a commit. It only adds the content of the specified file(s) at the time the add command is run; if you want subsequent changes included in the next commit, then you must run `add` again to add the new content to the index.
The status command can be used to obtain a summary of which files have changes that are staged for the next commit.
Please see commit for alternative ways to add content to a commit.
== Options
patterns:: (`Set<String>`, default `[]`) Files to add content from. A leading directory name (e.g. `dir` to add `dir/file1` and `dir/file2`) can be given to update the index to match the current state of the directory as a whole (e.g. specifying `dir` will record not just a file `dir/file1` modified in the working tree, a file `dir/file2` added to the working tree, but also a file `dir/file3` removed from the working tree.
update:: (`boolean`, default `false`) Update the index just where it already has an entry matching `patterns`. This removes as well as modifies index entries to match the working tree, but adds no new files.
+
If no `pathspec` is given when `update` option is used, all tracked files in the entire working tree are updated.
== Examples
To add specific files or directories to the path. Wildcards are not supported.
[source, groovy]
----
git.add(patterns: ['1.txt', 'some/dir'])
----
To add changes to all currently tracked files.
[source, groovy]
----
git.add(update: true)
----
== See Also
- link:https://git-scm.com/docs/git-add[git-add]

View file

@ -0,0 +1,41 @@
= apply
== Name
apply - Apply a patch to files.
== Synopsis
[source, groovy]
----
git.apply(patch: <patch file)
----
[source, groovy]
----
git.apply {
patch = <patch file>
}
----
== Description
Reads the supplied diff output (i.e. "a patch") and applies it to files in the repository.
This command applies the patch but does not create a commit.
== Options
patch:: (`Object`, default: `null`) The file to read the patch from. This can be a `java.io.File`, `java.nio.file.Path`, or a `String`.
== Examples
[source, groovy]
----
git.apply(patch: '/some/file.patch')
----
== See Also
- link:https://git-scm.com/docs/git-apply[git-apply]

View file

@ -0,0 +1,74 @@
= authentication
== Description
Groovy Git communicates with remote repositories in one of three ways:
. HTTP(S) protocol with basic auth credentials (i.e. username/password).
+
TIP: Where possible, use of HTTPS urls is preferred over the SSH options. It has far simpler behavior than the SSH options.
. SSH protocol using `ssh` directly. This approach should work as long as you can push/pull on your machine.
+
TIP: You should be using an SSH agent to have your key loaded before Groovy Git runs.
== HTTP BasicAuth Credentials
These are presented in precedence order (direct parameter in code, system properties, environment variables).
=== Parameter to Groovy Git operations
Some Groovy Git operations, such as link:clone.html[clone] allow you to provide credentials programmatically.
[source, groovy]
----
import org.xbib.groovy.git.Git
import org.xbib.groovy.git.Credentials
def git = Git.clone(dir: '...', url: '...', credentials: new Credentials(username, password))
----
=== System Properties
org.xbib.groovy.git.auth.username:: Username to provide when basic authentication credentials are needed.
Username can be specified without a password (e.g. using a GitHub auth token as the user and providing no password).
org.xbib.groovy.git.auth.password:: Password to provide when basic authentication credentials are needed.
=== Environment Variables
GIT_USER:: Username to provide when basic authentication credentials are needed.
Username can be specified without a password (e.g. using a GitHub auth token as the user and providing no password).
GIT_PASS:: Password to provide when basic authentication credentials are needed.
== SSH
If the `GIT_SSH` environment variable is set, that command will be used.
If not, Git will scan your `PATH` for an `ssh`command.
Any keys your `ssh` or `plink` can natively pick up will be used. This should include keys in default locations
(e.g. `~/`) and any compatible SSH agent or OS keychain.
[CAUTION]
====
Groovy Git cannot provide credentials to the system `ssh` command.
If your key is password protected and not loaded in an agent, authentication _will_ fail **or** hang indefinitely.
====
== Examples
This is a non-exhaustive list of examples of how to configure authentication in common scenarios.
=== Using a GitHub auth token with HTTPS URLs
Set the environment variable `GIT_USER` to your authentication token from GitHub.
=== Using a Username and Password with HTTPS URLs
Set the system properties:
----
groovy -Dorg.xbib.groovy.git.auth.username=someone -Dorg.xbib.groovy.git.auth.password=mysecretpassword myscript.groovy
----
=== Using ssh-agent
Make sure your ssh-agent is started and your key is loaded. Then just run your application or script.

View file

@ -0,0 +1,186 @@
= branch
== Name
branch - List, create, or delete branches
== Synopsis
[source, groovy]
----
git.branch.current()
----
[source, groovy]
----
git.branch.list()
git.branch.list(mode: <mode>, contains: <commit>)
----
[source, groovy]
----
git.branch.add(name: <name>, startPoint: <startPoint>, mode: <mode>)
----
[source, groovy]
----
git.branch.change(name: <name>, startPoint: <startPoint>, mode: <mode>)
----
[source, groovy]
----
git.branch.remove(names: [<name>, ...], force: <boolean>)
----
== Description
`git.branch.current()`:: Returns the current branch.
`git.branch.list()`:: Returns a list of branches. The branches returned can be filtered using `mode` and `contains`.
`git.branch.add(name: <name>, startPoint: <startPoint>, mode: <mode>)`:: Creates a new branch named `<name>` pointing at `<startPoint>`, possibly tracking a remote depending on `<mode>`. Returns the created branch.
`git.branch.change(name: <name>, startPoint: <startPoint>, mode: <mode>)`:: Modify an existing branch named `<name>` pointing at `<startPoint>`, possibly tracking a remote depending on `<mode>`. Returns the modified branch.
`git.branch.remove(names: [<name>, ...], force: <boolean>)`:: Removes one or more branches. Returns a `List<String>` of branch names removed.
== Options
=== list
mode:: (`String`, default `local`) Must be one of `local`, `remote`, `all`.
`local`:::: Only list local branches (i.e. those under `refs/heads`)
`remote`:::: Only list remote branches (i.e. those under `refs/remotes`)
`all`:::: List all branches
contains:: (`Object`, default `null`) Only list branches which contain the specified commit. (`Object`, default `null`) Start the new branch at this commit. For a more complete list of acceptable inputs, see link:resolve.html[resolve] (specifically the `toRevisionString` method).
=== add or change
name:: (`String`, default `null`) Name of the branch
startPoint:: (`Object`, default `null`) Start the new branch at this commit. For a more complete list of acceptable inputs, see link:resolve.html[resolve] (specifically the `toRevisionString` method).
mode:: (`String`, default `null`) Must be one of `'track'` or `'no-track'` or `null`
`track`:::: When creating a new branch, set up `branch.<name>.remote` and `branch.<name>.merge` configuration entries to mark the start-point branch as "upstream" from the new branch. It directs link:pull.html[pull] without arguments to pull from the upstream when the new branch is checked out.
+
This behavior is the default when the start point is a remote-tracking branch. Set the `branch.autoSetupMerge` configuration variable to `false` if you want link:checkout.html[checkout] and `branch` to always behave as if `no-track` were given. Set it to `always` if you want this behavior when the start-point is either a local or remote-tracking branch.
`no-track`:::: Do not set up "upstream" configuration, even if the branch.autoSetupMerge configuration variable is true.
=== remove
names:: (`List<Object>`, default `[]`) Names of the branches. For a more complete list of acceptable inputs, see link:resolve.html[resolve] (specifically the `toBranchName` method).
force:: (`boolean`, default `false`) Set to `true` to allow deleting branches that are not merged into another branch already.
== Examples
To add a branch starting at the current HEAD.
[source, groovy]
----
git.branch.add(name: 'new-branch')
----
To add a branch starting at, but not tracking, a local start point.
[source, groovy]
----
git.branch.add(name: 'new-branch', startPoint: 'local-branch')
git.branch.add(name: 'new-branch', startPoint: 'local-branch', mode: BranchAddOp.Mode.NO_TRACK)
----
To add a branch starting at and tracking a local start point.
[source, groovy]
----
git.branch.add(name: 'new-branch', startPoint: 'local-branch', mode: BranchAddOp.Mode.TRACK)
----
To add a branch starting from and tracking a remote branch.
[source, groovy]
----
git.branch.add(name: 'new-branch', startPoint: 'origin/remote-branch')
git.branch.add(name: 'new-branch', startPoint: 'origin/remote-branch', mode: BranchAddOp.Mode.TRACK)
----
To add a branch starting from, but not tracking, a remote branch.
[source, groovy]
----
git.branch.add(name: 'new-branch', startPoint: 'origin/remote-branch', mode: BranchAddOp.Mode.NO_TRACK)
----
To change the branch to start at, but not track, a local start point.
[source, groovy]
----
git.branch.change(name: 'existing-branch', startPoint: 'local-branch')
git.branch.change(name: 'existing-branch', startPoint: 'local-branch', mode: BranchChangeOp.Mode.NO_TRACK)
----
To change the branch to start at and track a local start point.
[source, groovy]
----
git.branch.change(name: 'existing-branch', startPoint: 'local-branch', mode: BranchChangeOp.Mode.TRACK)
----
To change the branch to start from and track a remote start point.
[source, groovy]
----
git.branch.change(name: 'existing-branch', startPoint: 'origin/remote-branch')
git.branch.change(name: 'existing-branch', startPoint: 'origin/remote-branch', mode: BranchChangeOp.Mode.TRACK)
----
To change the branch to start from, but not track, a remote start point.
[source, groovy]
----
git.branch.change(name: 'existing-branch', startPoint: 'origin/remote-branch', mode: BranchChangeOp.Mode.NO_TRACK)
----
Remove branches that have been merged.
[source, groovy]
----
def removedBranches = git.branch.remove(names: ['the-branch'])
def removedBranches = git.branch.remove(names: ['the-branch', 'other-branch'], force: false)
----
Remove branches, even if they haven't been merged.
[source, groovy]
----
def removedBranches = git.branch.remove(names: ['the-branch'], force: true)
----
To list local branches only.
[source, groovy]
----
def branches = git.branch.list()
def branches = git.branch.list(mode: BranchListOp.Mode.LOCAL)
----
To list remote branches only.
[source, groovy]
----
def branches = git.branch.list(mode: BranchListOp.Mode.REMOTE)
----
To list all branches.
[source, groovy]
----
def branches = git.branch.list(mode: BranchListOp.Mode.ALL)
----
To list all branches contains specified commit
[source, groovy]
----
def branches = git.branch.list(contains: %Commit hash or tag name%)
----
== See Also
- link:https://git-scm.com/docs/git-branch[git-branch]
- link:push.html[push]
- link:pull.html[pull]

View file

@ -0,0 +1,90 @@
= checkout
== Name
checkout - Switch branches
== Synopsis
[source, groovy]
----
git.checkout(branch: <branch>)
git.checkout(branch: <branch>, createBranch: true, orphan: <boolean>, startPoint: <startPoint>)
----
[source, groovy]
----
git.checkout {
branch = <branch>
}
git.checkout {
branch = <branch>
createBranch = true
orphan = <boolean>
startPoint = <startPoint>
}
----
== Description
Updates files in the working tree to match the version in the index or the specified tree. If no paths are given, git checkout will also update HEAD to set the specified branch as the current branch.
`git.checkout(branch: <branch>)`:: To prepare for working on `<branch>`, switch to it by updating the index and the files in the working tree, and by pointing `HEAD` at the branch. Local modifications to the files in the working tree are kept, so that they can be committed to the `<branch>`.
`git.checkout(branch: <new_branch>, createBranch: true, startPoint: <startPoint>, orphan: <boolean>)`:: Causes a new branch to be created as if link:branch.html[branch] were called and then checked out.
== Options
branch:: (`Object`, default `null`) Name of new branch to create or branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that branch is checked out. Otherwise, if it refers to a valid commit, your HEAD becomes "detached" and you are no longer on any branch (see below for details). For a more complete list of acceptable inputs, see link:resolve.html[resolve] (specifically the `toBranchName` method).
startPoint:: (`Object`, default `null`) Start the new branch at this commit. For a more complete list of acceptable inputs, see link:resolve.html[git-resolve] (specifically the `toRevisionString` method).
createBranch:: (`boolean`, default `false`) Create a new branch named `<branch>` and start it at `<startPoint>`.
orphan:: (`boolean`, default `false`) Create a new orphan branch, named `branch`, started from `startPoint` and switch to it. The first commit made on this new branch will have no parents and it will be the root of a new history totally disconnected from all the other branches and commits.
+
The index and the working tree are adjusted as if you had previously run `git.checkout(branch: <startPoint>)`. This allows you to start a new history that records a set of paths similar to `startPoint` by easily running `git.commit(message: <msg>, all: true)`.
+
This can be useful when you want to publish the tree from a commit without exposing its full history. You might want to do this to publish an open source branch of a project whose current tree is "clean", but whose full history contains proprietary or otherwise encumbered bits of code.
+
If you want to start a disconnected history that records a set of paths that is totally different from the one of `startPoint`, then you should clear the index and the working tree right after creating the orphan branch by running `git.remove(patterns: ['.'])` from the top level of the working tree. Afterwards you will be ready to prepare your new files, repopulating the working tree, by copying them from elsewhere, extracting a tarball, etc.
== Examples
To checkout an existing branch.
[source, groovy]
----
git.checkout(branch: 'existing-branch')
git.checkout(branch: 'existing-branch', createBranch: false)
----
To checkout a new branch starting at, but not tracking, the current HEAD.
[source, groovy]
----
git.checkout(branch: 'new-branch', createBranch: true)
----
To checkout a new branch starting at, but not tracking, a start point.
[source, groovy]
----
git.checkout(branch: 'new-branch', startPoint: 'any-branch', createBranch: true)
----
To checkout a new orphan branch starting at, but not tracking, the current HEAD.
[source, groovy]
----
git.checkout(branch: 'new-branch', orphan: true)
----
To checkout a new orphan branch starting at, but not tracking, a start point.
[source, groovy]
----
git.checkout(branch: 'new-branch', startPoint: 'any-branch', orphan: true)
----
== See Also
- link:https://git-scm.com/docs/git-checkout[git-checkout]
- link:reset.html[reset]

View file

@ -0,0 +1,85 @@
= clean
== Name
clean - Remove untracked files from the working tree
== Synopsis
[source, groovy]
----
git.clean()
----
[source, groovy]
----
git.clean(paths: ['<path>', ...], directories: <boolean>, dryRun: <boolean>, ignore: <boolean>)
----
[source, groovy]
----
git.clean {
paths = ['<path>', ...]
directories = <boolean>
dryRun = <boolean>
ignore = <boolean>
}
----
== Description
Cleans the working tree by removing files that are not under version control.
Normally, only files uknown to Git are removed, but if `ignore` is `false`, ignored files are also removed. This can, for example, be useful to remove all build products.
If any optional `paths` are given, only those paths are affected.
Returns a `Set<String>` of the paths that were deleted.
== Options
directories:: (`boolean`, default `false`) Remove untracked directories in addition to untracked files.
dryRun:: (`boolean`, default `false`) Dont actually remove anything, just show what would be done.
ignore:: (`boolean`, default `true`) Dont use the standard ignore rules read from .gitignore (per directory) and $GIT_DIR/info/exclude. This allows removing all untracked files, including build products. This can be used (possibly in conjunction with git reset) to create a pristine working directory to test a clean build.
paths:: (`Set<String>`, default `null`) Only remove files in the given paths.
== Examples
To clean all untracked files, but not ignored ones or untracked directories.
[source, groovy]
----
def cleanedPaths = git.clean()
----
To clean all untracked files and directories.
[source, groovy]
----
def cleanedPaths = git.clean(directories: true)
----
To clean all untracked files, including ignored ones.
[source, groovy]
----
def cleanedPaths = git.clean(ignore: false)
----
To only return files that would be cleaned.
[source, groovy]
----
def cleanedPaths = git.clean(dryRun: true)
----
To clean specific untracked files.
[source, groovy]
----
def cleanedPaths = git.clean(paths: ['specific/file.txt'])
----
== See Also
- link:https://git-scm.com/docs/git-clean[git-clean]

View file

@ -0,0 +1,52 @@
= clone
== Name
clone - Clone a repository into a new directory
== Synopsis
[source, groovy]
----
git.clone(dir: <path>, uri: <path or uri>, remote: <name>, bare: <boolean>,
checkout: <boolean>, refToCheckout: <name>, credentials: <credentials>)
----
[source, groovy]
----
git.clone {
dir = <path>
uri = <path or uri>
remote = <name>
bare = <boolean>
checkout = <boolean>
refToCheckout = <name>
credentials = <credentials>
}
----
== Description
Clones a repository into a newly created directory, creates remote-tracking branches for each branch in the cloned repository, and creates and checks out an initial branch that is forked from the cloned repositorys currently active branch.
After the clone, a plain link:fetch.html[fetch] without arguments will update all the remote-tracking branches, and a link:pull.html[pull] without arguments will in addition merge the remote master branch into the current master branch.
This default configuration is achieved by creating references to the remote branch heads under `refs/remotes/origin` and by initializing `remote.origin.url` and `remote.origin.fetch` configuration variables.
Returns a Groovy Git instance.
== Options
dir:: (`Object`, default `null`) The directory the repository should be cloned into. Can be a `File`, `Path`, or `String`.
uri:: (`String`, default `null`) The URI to the repository to be cloned.
remote:: (`String`, default `origin`) Instead of using the remote name `origin` to keep track of the upstream repository, use `<name>`.
bare:: (`boolean`, default `false`) Create a bare repository.
checkout:: (`boolean`, default `true`) Set to `false` to skip checking out a `HEAD`.
refToCheckout:: (`String`, default `null`) Instead of pointing the newly created `HEAD` to the branch pointed to by the cloned repositorys `HEAD`, point to `<name>` branch instead. In a non-bare repository, this is the branch that will be checked out. This can also take tags and detaches the `HEAD` at that commit in the resulting repository.
credentials:: (`Credentials`, default `null`) An instance of Credentials containing username/password to be used in operations that require authentication. See link:authentication.html[authentication] for preferred ways to configure this.
== Examples
== See Also
- link:https://git-scm.com/docs/git-clone[git-clone]

View file

@ -0,0 +1,85 @@
= commit
== Name
commit - Record changes to the repository
== Synopsis
[source, groovy]
----
git.commit(message: <message>, paths: [<path>, ...], all: <boolean>, amend: <boolean>,
reflogComment: <message>, committer: <person>, author: <person>)
----
[source, groovy]
----
git.commit {
message = <message>
paths = [<path>, ...]
all = <boolean>
amend = <boolean>
reflogComment = <message>
committer = <person>
author = <person>
}
----
== Description
Stores the current contents of the index in a new commit along with a log message from the user describing the changes.
The content to be added can be specified in several ways:
. by using link:add.html[add] to incrementally "add" changes to the index before using the commit command (Note: even modified files must be "added");
. by using link:remove.html[remove] to remove files from the working tree and the index, again before using the commit command;
. by listing `paths` as arguments to the `commit` command, in which case the commit will ignore changes staged in the index, and instead record the current content of the listed files (which must already be known to Git);
. by using the `all` switch with the commit command to automatically "add" changes from all known files (i.e. all files that are already listed in the index) and to automatically "rm" files in the index that have been removed from the working tree, and then perform the actual commit;
If you make a commit and then find a mistake immediately after that, you can recover from it with link:reset.html[reset].
Returns a Commit representing the new `HEAD`.
== Options
message:: (`String`, default `null`) Use the given <msg> as the commit message.
paths:: (`Set<String>`, default `[]`) When files are given on the command line, the command commits the contents of the named files, without recording the changes already staged. The contents of these files are also staged for the next commit on top of what have been staged before.
all:: (`boolean`, default `false`) Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected.
amend:: (`boolean`, default `false`) Replace the tip of the current branch by creating a new commit. The recorded tree is prepared as usual, and the message from the original commit is used as the starting point, instead of an empty message, when no other message is specified from the command line. The new commit has the same parents and author as the current one.
reflogComment:: (`String`, default `null`) Use a different comment in the reflog for this commit.
committer:: (`Person`, default `null`) Override the committer recorded in the commit. This must be a Person.
author:: (`Person`, default `null`) Override the author recorded in the commit. This must be a Person.
== Examples
To commit all staged changes.
[source, groovy]
----
def commit = git.commit(message: 'something about the change')
----
To commit changes to all previously tracked files.
[source, groovy]
----
def commit = git.commit(message: 'something about the change', all: true)
----
To amend the previous commit.
[source, groovy]
----
def commit = git.commit(message: 'something about the change', amend: true)
----
To commit changes authored by another person.
[source, groovy]
----
def commit = git.commit(message: 'something about the change', author: new Person('Bruce Wayne', 'bruce.wayne@wayneindustries.com'))
----
== See Also
- link:https://git-scm.com/docs/git-commit[git-commit]

View file

@ -0,0 +1,67 @@
= describe
== Name
describe - Describe a commit using the most recent tag reachable from it
== Synopsis
[source, groovy]
----
git.describe()
----
[source, groovy]
----
git.describe(commit: <commit>, longDescr: <boolean>, tags: <boolean>, match: [<string>])
----
[source, groovy]
----
git.describe {
commit = <commit>
longDescr = <boolean>
tags = <boolean>
match = [<string>]
}
----
== Description
The command finds the most recent tag that is reachable from a commit. If the tag points to the commit, then only the tag is shown. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.
Describe only shows annotated tags. For more information about creating annotated tags see the `annotate` option to link:tag.html[tag].
== Options
commit:: (`Object`, default `null`) Commit-ish object names to describe. Defaults to HEAD if omitted. For a more complete list of ways to spell commit names, see link:resolve.html[resolve] (specifically the `toCommit` method).
longDescr:: (`boolean`, default `false`) Always output the long format (the tag, the number of commits and the abbreviated commit name) even when it matches a tag. This is useful when you want to see parts of the commit object name in "describe" output, even when the commit in question happens to be a tagged version. Instead of just emitting the tag name, it will describe such a commit as v1.2-0-gdeadbee (0th commit since tag v1.2 that points at object deadbee….).
tags:: (`boolean`, default `false`) Instead of using only the annotated tags, use any tag found in `refs/tags` namespace. This option enables matching a lightweight (non-annotated) tag.
match:: (`List<String>`, default `[]`) Only consider tags matching the given glob(7) pattern, excluding the "refs/tags/" prefix. This can be used to avoid leaking private tags from the repository. If multiple patterns are given they will be accumulated, and tags matching any of the patterns will be considered.
== Examples
Describe the current `HEAD`.
[source, groovy]
----
git.describe() == '1.0.0'
----
Find the most recent tag that is reachable from a different commit.
[source, groovy]
----
git.describe(commit: 'other-branch') == '2.0.0-rc.1-7-g91fda36'
----
Always output the long format (the tag, the number of commits and the abbreviated commit name) even when it matches a tag.
[source, groovy]
----
git.describe(longDescr: true) == '2.0.0-rc.1-7-g91fda36'
----
== See Also
- link:https://git-scm.com/docs/git-describe[git-describe]

View file

@ -0,0 +1,58 @@
= fetch
== Name
fetch - Download objects and refs from another repository
== Synopsis
[source, groovy]
----
git.fetch()
----
[source, groovy]
----
git.fetch(remote: '<name or uri>', refSpecs: [<refspec>, ...], prune: <boolean>, tagMode: <mode>)
----
[source, groovy]
----
git.fetch {
remote = '<name or uri>'
refspecs = [<refspec>, ...]
prune = <boolean>
tagMode = <mode>
}
----
== Description
Fetch branches and/or tags (collectively, "refs") from one or more other repositories, along with the objects necessary to complete their histories. Remote-tracking branches are updated (see the description of <refspec> below for ways to control this behavior).
By default, any tag that points into the histories being fetched is also fetched; the effect is to fetch tags that point at branches that you are interested in. This default behavior can be changed by using a `tagMode` of `'all'` or `'none'' or by configuring `remote.<name>.tagOpt`. By using a refspec that fetches tags explicitly, you can fetch tags that do not point into branches you are interested in as well.
When no remote is specified, by default the origin remote will be used, unless theres an upstream branch configured for the current branch.
== Options
remote:: (`String`, default `null`) The "remote" repository that is source of a fetch operation. This parameter can be either a URL or the name of a remote.
refspecs:: (`List<String>`, default `[]`) Specifies which refs to fetch and which local refs to update. When no <refspec>s are provided, the refs to fetch are read from `remote.<repository>.fetch` variables instead.
+
The format of a <refspec> parameter is an optional plus +, followed by the source ref <src>, followed by a colon :, followed by the destination ref <dst>. The colon can be omitted when <dst> is empty.
+
The remote ref that matches <src> is fetched, and if <dst> is not empty string, the local ref that matches it is fast-forwarded using <src>. If the optional plus + is used, the local ref is updated even if it does not result in a fast-forward update.
prune:: (`boolean`, default `false`) Before fetching, remove any remote-tracking references that no longer exist on the remote. Tags are not subject to pruning if they are fetched only because of the default tag auto-following or due to a `tagMode` option. However, if tags are fetched due to an explicit refspec, then they are also subject to pruning.
tagMode:: (`String`, default `auto`) Must be one of `'auto'`, `'all'`, `'none'`.
+
`'auto'` - tags that point at objects that are downloaded from the remote repository are fetched and stored locally.
+
`'none'` - disables automatic tag following.
+
`'all'` - Fetch all tags from the remote (i.e., fetch remote tags `refs/tags/*` into local tags with the same name), in addition to whatever else would otherwise be fetched.
== Examples
== See Also
- link:https://git-scm.com/docs/git-fetch[git-fetch]

View file

@ -0,0 +1,69 @@
= gradle
## Applying the Plugin
Generally, you should only apply the plugin to the root project of your build.
```groovy
plugins {
id 'org.xbib.gradle.plugin.git' version '<version>'
}
```
## What does the plugin do?
The `org.xbib.gradle.plugin.git` plugin adds a `git` property to your build,
which is an instance of Groovy Git opened to the repository visible from your project's root dir.
This will check the project directory and its parents for a `.git` directory.
If no repository is found, the `git` property is `null`.
```groovy
version = "1.0.0-${git.head().abbreviatedId}"
task tagRelease {
description = 'Tags the current head with the project\'s version.'
doLast {
git.tag.add {
name = version
message = "Release of ${version}"
}
}
}
task pushToOrigin {
description = 'Pushes current branch\'s committed changes to origin repo.'
doLast {
git.push()
}
}
```
For details on the available operations, see the link:reference.html[reference]. Examples are provided there.
## Just getting the library
If you don't want to interact with the project's repository, but still want to
```groovy
plugins {
id 'org.xbib.gradle.plugin.git' version '<version>' apply false
}
```
Then you can import Groovy Git and continue from there:
```groovy
import org.xbib.gradle.plugin.git.Git
task cloneSomeRepo {
doLast {
def git = Git.clone(dir: "$buildDir/my-repo", uri: "https://github.com/jprante/groovy-git.git")
println git.describe()
}
}
```
## Authentication
If you will be doing a clone, fetch, push, or pull, review the link:authentication.html[authentication] page for details
on how to configure credentials for these commands.

View file

@ -0,0 +1,22 @@
= head
== Name
head - Gets the current `HEAD` commit.
== Synopsis
[source, groovy]
----
git.head()
----
== Description
Returns the commit that the current `HEAD` points to.
== Options
== Examples
== See Also

View file

@ -0,0 +1,159 @@
= groovy-git
== What is Groovy Git?
A library providing a wrapper around link:https://eclipse.org/jgit/[Eclipse's JGit] for more fluent usage from Groovy.
This allows you to interact easily with link:https://git-scm.com[Git] repositories from general applications
using Groovy or from Gradle builds.
== Where do I get Groovy Git?
To use Groovy Git as a library, add a dependency on it.
[source, groovy]
.build.gradle
----
dependencies {
compile 'org.xbib:groovy-git:<version>'
}
----
[source, xml]
.pom.xml
----
<!-- make sure you've specified JCenter in your repositories -->
<dependencies>
<dependency>
<group>org.xbib</group>
<artifactId>groovy-git</artifactId>
<version>...</version>
</dependency>
</dependencies>
----
To use Groovy Git in a Gradle build, see the documentation for link:gradle.html[org.xbib.gradle.plugin.git].
== How do I use Groovy Git?
First you need to get an instance of Groovy Git, by either link:init.html[initializing] a new repo or
link:open.html[opening] or link:clone.html[cloning] an existing repo.
Once you have a Groovy Git instance, you can use the available operations to inspect or modify the repo.
Each operation has 3 variants, depending on your preference:
[source, groovy]
----
// no arg variant, only applies to operations that don't require input
git.log()
// map argument variant
git.log(includes: ['master', 'other-branch'], excludes: ['old-stuff'], skipCommits: 5)
// closure argument variant
git.log {
range('1.0.0', '1.5.1')
maxCommits = 2
}
----
Look in the link:reference.html[reference] documentation for information on each available operation,
it's options, and example usage. Additionally, see the link:authentication.html[authentication] documentation
if you plan to interact with a remote repository.
== Example Usage
In an example like this, using the HTTP protocol, you'll likely need to specify basic auth credentials.
This can be done via system properties or environment variables. SSH access is also supported.
See the link:-authentication.html[authentication] documentation.
[source]
.Environment Variables
----
GIT_USER=somebody
GIT_PASS=myauthtoken
----
[source, groovy]
.gitSample.groovy
----
// get an instance
def git = Git.clone(dir: 'test-repo', uri: 'https://github.com/jprante/groovy-git.git')
// make some changes
new File('test-repo/file.txt') << 'making some changes'
git.add(patterns: ['test-repo/file.txt'])
// make a commit
git.commit(message: 'Adding a new file')
// view the commits
git.log {
range('origin/master', 'master')
}.each { commit ->
println "${commit.id} ${commit.shortMessage}"
}
// push to the remote
git.push()
// cleanup after yourself
git.close()
----
include::add.adoc[]
include::apply.adoc[]
include::authentication.adoc[]
include::branch.adoc[]
include::checkout.adoc[]
include::clean.adoc[]
include::clone.adoc[]
include::commit.adoc[]
include::describe.adoc[]
include::fetch.adoc[]
include::gradle.adoc[]
include::head.adoc[]
include::init.adoc[]
include::isAncestorOf.adoc[]
include::log.adoc[]
include::lsremote.adoc[]
include::merge.adoc[]
include::open.adoc[]
include::pull.adoc[]
include::push.adoc[]
include::remote.adoc[]
include::remove.adoc[]
include::reset.adoc[]
include::resolve.adoc[]
include::revert.adoc[]
include::show.adoc[]
include::status.adoc[]
include::tag.adoc[]

View file

@ -0,0 +1,37 @@
= init
== Name
init - Create an empty Git repository
== Synopsis
[source, groovy]
----
git.init(dir: <path>, bare: <boolean>)
----
[source, groovy]
----
git.init {
dir = <path>
bare = <boolean>
}
----
== Description
This command creates an empty Git repository - basically a `.git` directory with subdirectories for `objects`, `refs/heads`, `refs/tags`, and template files. An initial `HEAD` file that references the HEAD of the master branch is also created.
Returns a Groovy Git instance.
== Options
dir:: (`Object`, default `null`) The directory the repository should be initialized in. Can be a `File`, `Path`, or `String`.
bare:: (`boolean`, default `false`) Create a bare repository.
== Examples
== See Also
- link:https://git-scm.com/docs/git-init[git-init]

View file

@ -0,0 +1,30 @@
= isAncestorOf
== Name
isAncestorOf - Tell if a commit is an ancestor of another.
== Synopsis
[source, groovy]
----
git.isAncestorOf(<base commit>, <tip commit>)
----
== Description
Given a base commit and a tip commit, return `true` if the base commit can be reached by walking back from the tip.
== Options
1. (`Object`) base commit. For a more complete list of objects you can pass in, see link:resolve.html[resolve] (specifically the `toCommit` method).
1. (`Object`) tip commit. For a more complete list of objects you can pass in, see link:resolve.html[resolve] (specifically the `toCommit` method).
== Examples
[source, groovy]
----
git.isAncestorOf('v1.2.3', 'master')
----
== See Also

View file

@ -0,0 +1,71 @@
= log
== Name
log - Show commit logs
== Synopsis
[source, groovy]
----
git.log()
----
[source, groovy]
----
git.log(includes: [<revstr>, ...], excludes: [<revstr>, ...], paths: [<path>, ...],
skipCommits: <int>, maxCommits: <int>)
----
[source, groovy]
----
git.log {
includes = [<revstr>, ...]
excludes = [<revstr>, ...]
paths = [<path>, ...]
skipCommits = <int>
maxCommits = <int>
range(<revstr since>, <revstr until>)
}
----
== Description
Shows the commit logs.
List commits that are reachable by following the parent links from the given `includes` commit(s), but exclude commits that are reachable from the one(s) given with the `excludes` option. The output is given in reverse chronological order.
You can think of this as a set operation. Commits given in `includes` form a set of commits that are reachable from any of them, and then commits reachable from any of the ones given with `excludes` are subtracted from that set. The remaining commits are what comes out in the result. Various other options and paths parameters can be used to further limit the result.
Thus, the following command means "list all the commits which are reachable from foo or bar, but not from baz".
[source, groovy]
----
git.log(includes: ['foo', 'bar'], excludes: ['baz'])
----
A special notation (in the Closure syntax only) `range(<commit1>, <commit2>)` can be used as a short-hand for `excludes: [<commit2>], includes: [<commit1>]`. For example, either of the following may be used interchangeably:
[source, groovy]
----
git.log(includes: ['HEAD'], excludes: ['origin'])
git.log {
range('origin', 'HEAD')
}
----
Returns a `List<Commit>` with the commits matching the criteria given.
== Options
includes:: (`List<Object>`, default `[]`) Commit-ish object names to include the history of in the output. For a more complete list of ways to spell commit names, see link:resolve.html[resolve] (specifically the `toCommit` method).
excludes:: (`List<Object>`, default `[]`) Commit-ish object names to exclude the history of in the output. For a more complete list of ways to spell commit names, see link:resolve.html[resolve] (specifically the `toCommit` method).
skipCommits:: (`int`, default `-1`) Skip `skipCommits` commits before starting to show the commit output. A negative value ignores this option.
maxCommits:: (`int`, default `-1`) Limit the number of commits to output. A negative value ignores this option.
paths:: (`List<String>`, defaul: `[]`) Commits modifying the given paths are selected. Omitting this will include all reachable commits.
== Examples
== See Also
- link:https://git-scm.com/docs/git-log[git-log]

View file

@ -0,0 +1,58 @@
= lsremote
== Name
lsremote - List references in a remote repository
== Synopsis
[source, groovy]
----
git.lsremote()
----
[source, groovy]
----
git.lsremote(heads: <boolean>, tags: <boolean>, remote: '<name or uri>')
----
[source, groovy]
----
git.lsremote {
heads = <boolean>
tags = <boolean>
remote = '<name or uri>'
}
----
== Description
Returns a `Map<Ref, String>` containing references available in the remote, and the object ID they currently point to.
== Options
heads:: (`boolean`, default `false`) Limit to `refs/heads`. This is not mutually exclusive with `tags`; when given both, references stored in both places are returned.
tags:: (`boolean`, default `false`) Limit to `refs/tags`. This is not mutually exclusive with `heads`; when given both, references stored in both places are returned.
remote:: (`String`, default `'origin'`) The name of the remote or the URI of the repository to list.
== Examples
[source, groovy]
.Code
----
git.lsremote(tags: true).each { ref, id ->
println "${id} ${ref.fullName}"
}
----
.Output
----
d6602ec5194c87b0fc87103ca4d67251c76f233a refs/tags/v0.99
f25a265a342aed6041ab0cc484224d9ca54b6f41 refs/tags/v0.99.1
7ceca275d047c90c0c7d5afb13ab97efdf51bd6e refs/tags/v0.99.3
c5db5456ae3b0873fc659c19fafdde22313cc441 refs/tags/v0.99.2
----
== See Also
- link:https://git-scm.com/docs/git-ls-remote[git-ls-remote]

View file

@ -0,0 +1,60 @@
= merge
== Name
merge - Join two or more development histories together
== Synopsis
[source, groovy]
----
git.merge(head: <revstr>, mode: <mode>, message: <message>)
----
[source, groovy]
----
git.merge {
head = <revstr>
mode = <mode>
message = <message>
}
----
== Description
Incorporates changes from the named commits (since the time their histories diverged from the current branch) into the current branch. This command is used by link:pull.html[pull] to incorporate changes from another repository and can be used by hand to merge changes from one branch into another.
Assume the following history exists and the current branch is "master":
----
A---B---C topic
/
D---E---F---G master
----
Then `git.merge(head: 'topic')` will replay the changes made on the topic branch since it diverged from master (i.e., `E`) until its current commit `C` on top of master, and record the result in a new commit along with the names of the two parent commits and a log message from the user describing the changes.
----
A---B---C topic
/ \
D---E---F---G---H master
----
This is a simplified version of merge. If any conflict occurs the merge will throw an exception. The conflicting files can be identified with link:status.html[status].
== Options
head:: (`Object`, default `null`) Commit, usually another branch head, to merge into our branch. For a more complete list of acceptable inputs, see link:resolve.html[resolve] (specifically the `toRevisionString` method).
mode:: (`String`, default `default`) Must be one of `default`, `only-ff`, `create-commit`, `squash`, `no-commit`.
`default`:::: When the merge resolves as a fast-forward, only update the branch pointer, without creating a merge commit.
`only-ff`:::: Refuse to merge and fail with an exception unless the current `HEAD` is already up-to-date or the merge can be resolved as a fast-forward.
`create-commit`:::: Create a merge commit even when the merge resolves as a fast-forward.
`squash`:::: Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit, move the `HEAD`, or record `$GIT_DIR/MERGE_HEAD` (to cause the next git commit command to create a merge commit). This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus).
`no-commit`:::: Perform the merge but pretend the merge failed and do not autocommit, to give the user a chance to inspect and further tweak the merge result before committing.
message:: (`String`, default `null`) Use the given <msg> as the merge commit message.
== Examples
== See Also
- link:https://git-scm.com/docs/git-merge[git-merge]

View file

@ -0,0 +1,42 @@
= open
== Name
open - Open an existing Git repository
== Synopsis
[source, groovy]
----
git.open()
----
[source, groovy]
----
git.open(dir: <path>, currentDir: <path>, credentials: <credentials>)
----
[source, groovy]
----
git.open {
dir = <path>
currentDir = <path>
credentials = <credentals>
}
----
== Description
This command opens an existing Git repository. If both `dir` and `currentDir` are `null`, acts as if the `currentDir` is the JVM's working directory.
Returns a Groovy Git instance.
== Options
dir:: (`Object`, default `null`) The directory the repository is in. Can be a `File`, `Path`, or `String`.
currentDir:: (`Object`, default `null`) The directory to start searching for the repository from. Can be a `File`, `Path`, or `String`.
credentials:: (`Credentials`, default `null`) An instance of Credentials containing username/password to be used in operations that require authentication.
See link:authentication.html[authentication] for preferred ways to configure this.
== Examples

View file

@ -0,0 +1,64 @@
= pull
== Name
pull - Fetch from and integrate with another repository or a local branch
== Synopsis
[source, groovy]
----
git.pull()
----
[source, groovy]
----
git.pull(remote: '<name or uri>', branch: '<name>', rebase: <boolean>)
----
[source, groovy]
----
git.pull {
remote = '<name or uri>'
branch = '<name>'
rebase = <boolean>
}
----
== Description
Incorporates changes from a remote repository into the current branch. In its default mode, git pull is shorthand for fetch followed by merge.
More precisely, pull runs fetch with the given parameters and calls merge to merge the retrieved branch heads into the current branch. With `rebase`, it runs rebase instead of merge.
Default values for `remote` and `branch` are read from the "remote" and "merge" configuration for the current branch.
Assume the following history exists and the current branch is "master":
----
A---B---C master on origin
/
D---E---F---G master
^
origin/master in your repository
----
Then "git pull" will fetch and replay the changes from the remote master branch since it diverged from the local master (i.e., E) until its current commit `C` on top of master and record the result in a new commit along with the names of the two parent commits and a log message from the user describing the changes.
----
A---B---C origin/master
/ \
D---E---F---G---H master
----
== Options
remote:: (`String`, default `null`) The "remote" repository that is source of a pull operation. This parameter can be either a URL or the name of a remote.
branch:: (`String`, default `null`) The remote branch to pull.
rebase:: (`boolean`, default `false`) When true, rebase the current branch on top of the upstream branch after fetching. If there is a remote-tracking branch corresponding to the upstream branch and the upstream branch was rebased since last fetched, the rebase uses that information to avoid rebasing non-local changes.
== Examples
== See Also
- link:https://git-scm.com/docs/git-pull[git-pull]

View file

@ -0,0 +1,63 @@
= push
== Name
git-push - Update remote refs along with associated objects
== Synopsis
[source, groovy]
----
git.push()
----
[source, groovy]
----
git.push(remote: '<name or uri>', refsOrSpecs: [<ref or refspec>, ...],
all: <boolean>, tags: <boolean>, force: <boolean>, dryRun: <boolean>)
----
[source, groovy]
----
git.push {
remote = '<name or uri>'
refsOrSpecs = [<ref or refspec>, ...]
all = <boolean>
tags = <boolean>
force = <boolean>
dryRun = <boolean>
}
----
== Description
Updates remote refs using local refs, while sending objects necessary to complete the given refs.
When the `remote` is not specified, `branch.*.remote` configuration for the current branch is consulted to determine where to push. If the configuration is missing, it defaults to `origin`.
When the `refsOrSpecs` or `all` or `tags` options are not specified, the command finds the default `<refspec>` by consulting `remote.*.push` configuration, and if it is not found, honors `push.default` configuration to decide what to push (See link:https://git-scm.com/docs/git-config[git-config] for the meaning of `push.default`).
== Options
remote:: (`String`, default `null`) The "remote" repository that is destination of a push operation. This parameter can be either a URL or the name of a remote.
refsOrSpecs:: (`List<String>`, default `[]`) Specify what destination ref to update with what source object. The format of a <refspec> parameter is an optional plus +, followed by the source object <src>, followed by a colon :, followed by the destination ref <dst>.
+
The <src> is often the name of the branch you would want to push, but it can be any arbitrary "SHA-1 expression", such as `master~4` or `HEAD` (see link:https://git-scm.com/docs/gitrevisions[gitrevisions]).
+
The <dst> tells which ref on the remote side is updated with this push. Arbitrary expressions cannot be used here, an actual ref must be named. If `git.push(remote: '<repositor>')` without any `refsOrSpecs` argument is set to update some ref at the destination with <src> with `remote.<repository>.push` configuration variable, :<dst> part can be omitted—such a push will update a ref that <src> normally updates without any <refspec> on the command line. Otherwise, missing :<dst> means to update the same ref as the <src>.
+
The object referenced by <src> is used to update the <dst> reference on the remote side. By default this is only allowed if <dst> is not a tag (annotated or lightweight), and then only if it can fast-forward <dst>. By having the optional leading +, you can tell Git to update the <dst> ref even if it is not allowed by default (e.g., it is not a fast-forward.) This does not attempt to merge <src> into <dst>.
+
Pushing an empty <src> allows you to delete the <dst> ref from the remote repository.
+
The special refspec : (or +: to allow non-fast-forward updates) directs Git to push "matching" branches: for every branch that exists on the local side, the remote side is updated if a branch of the same name already exists on the remote side.
all:: (`boolean`, default `false`) Push all branches (i.e. refs under `refs/heads/`); cannot be used with other `refsOrSpecs`.
tags:: (`boolean`, default `false`) All refs under refs/tags are pushed, in addition to refspecs explicitly listed in `refsOrSpecs`.
force:: (`boolean`, default `false`) Usually, the command refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it. Note that `force` applies to all the refs that are pushed, hence using it with `push.default` set to `matching` or with multiple push destinations configured with `remote.*.push` may overwrite refs other than the current branch (including local refs that are strictly behind their remote counterpart). To force a push to only one branch, use a + in front of the refspec to push (e.g git push origin +master to force a push to the master branch). See the <refspec>... section above for details.
dryRun:: (`boolean`, default `false`) Do everything except actually send the updates.
== Examples
== See Also
- link:https://git-scm.com/docs/git-push[git-push]

View file

@ -0,0 +1,55 @@
= remote
== Name
remote - Manage set of tracked repositories
== Synopsis
[source, groovy]
----
git.remote.list()
----
[source, groovy]
----
git.remote.add(name: <name>, url: <url>, pushUrl: <url>,
fetchRefSpecs: [<refspec>, ...], pushRefSpecs: [<refspecs>, ...],
mirror: <boolean>)
----
[source, groovy]
----
git.remote.add {
name = <name>
url = <url>
pushUrl = <url>
fetchRefSpecs = [<refspec>, ...]
pushRefSpecs = [<refspecs>, ...]
mirror = <boolean>
}
----
== Description
Manage the set of repositories ("remotes") whose branches you track.
`list()` returns a `List<Remote>` (Remote) of all remotes configured for the repo.
`add()` returns the newly configured Remote
== Options
=== add
name:: (`String`, default `null`) Name of the new remote
url:: (`String`, default `null`) URL to fetch the remote repository from.
pushUrl:: (`String`, default `null`) URL to push to for this remote. If omitted, `url` is used for push.
fetchRefSpecs:: (`List<String>`, default `[+refs/heads/*:refs/remotes/<name>/*]`) default refspecs to use when fetching from this remote.
pushRefSpecs:: (`List<String>`, default `[]`) default refspecs to use when pushing to this remote.
mirror:: (`boolean`, default `false`) If `true` makes push always behave as if `mirror` is specified.
== Examples
== See Also
- link:https://git-scm.com/docs/git-remote[git-remote]

View file

@ -0,0 +1,49 @@
= remove
== Name
remove - Remove files from the working tree and from the index
== Synopsis
[source, groovy]
----
git.remove(patterns: ['<path>', ...], cached: <boolean>)
----
[source, groovy]
----
git.remove {
patterns = ['<path>', ...]
cached = <boolean>
}
----
== Description
Remove files from the index, or from the working tree and the index. `remove` will not remove a file from just your working directory. The files being removed have to be identical to the tip of the branch, and no updates to their contents can be staged in the index. When `cached` is given, the staged content has to match either the tip of the branch or the file on disk, allowing the file to be removed from just the index.
== Options
patterns:: (`Set<String>`, default `[]`) Files to remove. A leading directory name (e.g. `dir` to remove `dir/file1` and `dir/file2`) can be given to remove all files in the directory, and recursively all sub-directories.
cached:: (`boolean`, default `false`) Use this option to unstage and remove paths only from the index. Working tree files, whether modified or not, will be left alone.
== Examples
Remove specific file or directory from both the index and working tree.
[source, groovy]
----
git.remove(patterns: ['1.txt', 'some/dir'])
----
Remove specific file or directory from the index, but leave the in the working tree.
[source, groovy]
----
git.remove(patterns: ['1.txt', 'some/dir'], cached: true)
----
== See Also
- link:https://git-scm.com/docs/git-rm[git-rm]

View file

@ -0,0 +1,88 @@
= reset
== Name
reset - Reset current HEAD to the specified state
== Synopsis
[source, groovy]
----
git.reset(commit: <commit>, paths: [<path>, ...])
git.reset(mode: <mode>, commit: <commit>)
----
[source, groovy]
----
git.reset {
commit = <commit>
paths = [<path>, ...]
}
git.reset {
mode = <mode>
commit = <commit>
}
----
== Description
In the first form, copy entries from `commit` to the index. In the second form, set the current branch head (HEAD) to `commit`, optionally modifying index and working tree to match. The `commit` defaults to `HEAD` in all forms.
`git.reset(commit: <commit>, paths: [<path>, ...])`::
+
This form resets the index entries for all `<paths>` to their state at `<commit>`. (It does not affect the working tree or the current branch.)
+
This means that `git.reset(paths: [<paths>])` is the opposite of `git.add(patterns: [<paths>])`.
+
After running `reset` to update the index entry, you can use checkout to check the contents out of the index to the working tree. Alternatively, using checkout and specifying a commit, you can copy the contents of a path out of a commit to the index and to the working tree in one go.
`git.reset(mode: <mode>, commit: <commit>)`::
+
This form resets the current branch head to `<commit>` and possibly updates the index (resetting it to the tree of `<commit>`) and the working tree depending on <mode>. If `mode` is omitted, defaults to `mixed`. The <mode> must be one of the following:
+
`soft`:::: Does not touch the index file or the working tree at all (but resets the head to `<commit>`, just like all modes do). This leaves all your changed files `staged`, as `git.status()` would put it.
`mixed`:::: Resets the index but not the working tree (i.e., the changed files are preserved but not marked for commit) and reports what has not been updated. This is the default action.
`hard`:::: Resets the index and working tree. Any changes to tracked files in the working tree since `<commit>` are discarded.
If you want to undo a commit other than the latest on a branch, revert is your friend.
== Options
commit:: (`Object`, default `null`) Commit to reset to. For a more complete list of ways to spell commit names, see resolve (specifically the `toCommit` method).
paths:: (`Set<String>`, default `[]`) Paths to reset.
mode:: (`String`, default `mixed`) Must be one of `hard`, `mixed`, `soft`.
== Examples
Reset the HEAD to a different commit.
[source, groovy]
----
git.reset(commit: 'HEAD~1', mode: 'soft')
----
Reset the HEAD, index, and working tree to a different commit.
[source, groovy]
----
git.reset(commit: 'other-branch', mode: 'hard')
----
Reset the HEAD and index to a different commit.
[source, groovy]
----
git.reset(commit: 'HEAD~2')
git.reset(commit: 'HEAD~2', mode: 'mixed')
----
Reset the index for specific paths back to the HEAD
[source, groovy]
----
git.reset(paths: ['some/file.txt'])
----
== See Also
- link:https://git-scm.com/docs/git-reset[git-reset]

View file

@ -0,0 +1,66 @@
= resolve
== Name
git-resolve - Resolves objects to various types
== Synopsis
[source, groovy]
----
git.resolve.toObjectId(<object>)
----
[source, groovy]
----
git.resolve.toCommit(<object>)
----
[source, groovy]
----
git.resolve.toBranch(<object>)
----
[source, groovy]
----
git.resolve.toBranchName(<object>)
----
[source, groovy]
----
git.resolve.toTag(<object>)
----
[source, groovy]
----
git.resolve.toTagName(<object>)
----
[source, groovy]
----
git.resolve.toRevisionString(<object>)
----
== Description
Various methods to resolve objects to types needed by Groovy Git operations. These are used to normalize the input, allowing the caller more flexibility in providing the data they have rather than having to convert it ahead of time.
== Options
toObjectId:: Accepts Commit, Tag, Branch, Ref
toCommit:: Accepts Commit, Tag, Branch, String, GString
toBranch:: Accepts Branch, String, GString
toBranchName:: Accepts Branch, String, GString
toTag:: Accepts Tag, String, GString
toTagName:: Accepts Tag, String, GString
toRevisionString:: Accepts Commit, Tag, Branch, String, GString
== Examples
== See Also

View file

@ -0,0 +1,43 @@
= revert
== Name
revert - Revert some existing commits
== Synopsis
[source, groovy]
----
git.revert(commits: [<commit>, ...])
----
[source, groovy]
----
git.revert {
commits = ['<commit>', ...]
}
----
== Description
Given one or more existing commits, revert the changes that the related patches introduce, and record some new commits that record them. This requires your working tree to be clean (no modifications from the HEAD commit).
Note: git revert is used to record some new commits to reverse the effect of some earlier commits (often only a faulty one). If you want to throw away all uncommitted changes in your working directory, you should see reset, particularly the `hard` option. Take care with these alternatives as they will discard uncommitted changes in your working directory.
Returns a Commit representing the new `HEAD`.
== Options
commits:: (`List<Object>`, default: `[]]`) Commits to revert. For a more complete list of ways to spell commit names, see resolve (specifically the `toCommit` method).
== Examples
[source, groovy]
----
git.revert(commits: ['1234567', '1234568'])
----
== See Also
- link:https://git-scm.com/docs/git-revert[git-revert]

View file

@ -0,0 +1,40 @@
= show
== Name
show - Show a commit
== Synopsis
[source, groovy]
----
git.show()
----
[source, groovy]
----
git.show(commit: <revstr>)
----
[source, groovy]
----
git.show {
commit = <revstr>
}
----
== Description
Shows a commit including it's message and diff.
Returns a CommitDiff for the given commit.
== Options
commit:: (`Object`, default: `HEAD`) A revstring-ish object naming the commit to be shown. Commit-ish object names to include the history of in the output. For a more complete list of ways to specify a revstring, see resolve (specifically the `toRevisionString` method).
== Examples
== See Also
- link:https://git-scm.com/docs/git-show[git-show]

View file

@ -0,0 +1,28 @@
= status
== Name
status - Show the working tree status
== Synopsis
[source, groovy]
----
git.status()
----
== Description
Displays paths that have differences between the index file and the current `HEAD` commit, paths that have differences between the working tree and the index file, and paths in the working tree that are not tracked by Git (and are not ignored by gitignore). The first are what you would commit by running `commit`; the second and third are what you could commit by running `add` before running `commit`.
Returns a Status instance detailing the paths that differ.
== Options
_None_
== Examples
== See Also
- link:https://git-scm.com/docs/git-status[git-status]

View file

@ -0,0 +1,96 @@
= tag
== Name
tag - Create, list, or delete tag object
== Synopsis
[source, groovy]
----
git.tag.list()
----
[source, groovy]
----
git.tag.add(name: <name>, pointsTo: <commit>, force: <boolean>,
annotate: <boolean>, message: <msg>, tagger: <person>)
----
[source, groovy]
----
git.tag.remove(names: [<name>, ...])
----
== Description
`git.tag.list()`:: Returns a list of tags (Tag).
`git.tag.add(name: <name>, pointsTo: <commit>, force: <boolean>, annotate: <boolean>, message: <msg>, tagger: <person>)`:: Creates a new tag named `<name>` pointing at `<pointsTo>`.
Returns the created Tag.
`git.tag.remove(names: [<name>, ...])`:: Removes one or more tages. Returns a `List<String>` of tag names removed.
== Options
=== add
name:: (`String`, default `null`) Name of the tag
message:: (`String`, default `null`) Use the given <msg> as the commit message.
tagger:: (`Person`, default `null`) Override the tagger recorded in the tag. This must be a Person.
annotate:: (`boolean`, default `true`) Make an unsigned, annotated tag object
force:: (`boolean`, default `false`) Replace an existing tag with the given name (instead of failing)
pointsTo:: (`Object`, default `null`) Point new tag at this commit. For a more complete list of acceptable inputs, see resolve (specifically the `toRevisionString` method).
=== remove
names:: (`List<Object>`, default `[]`) Names of the tags. For a more complete list of acceptable inputs, see resolve (specifically the `toTagName` method).
== Examples
To list all tags.
[source, groovy]
----
def tags = git.tag.list()
----
Add an annotated tag.
[source, groovy]
----
git.tag.add(name: 'new-tag')
git.tag.add(name: 'new-tag', message: 'Some message')
git.tag.add(name: 'new-tag', annotate: true)
----
Add an unannotated tag.
[source, groovy]
----
git.tag.add(name: 'new-tag', annotate: false)
----
Add a tag starting at a specific commit.
[source, groovy]
----
git.tag.add(name: 'new-tag', pointsTo: 'other-branch')
----
Overwrite an existing tag.
[source, groovy]
----
git.tag.add(name: 'existing-tag', force: true)
----
Remove tags.
[source, groovy]
----
def removedTags = git.tag.remove(names: ['the-tag'])
----
== See Also
- link:https://git-scm.com/docs/git-tag[git-tag]

View file

@ -0,0 +1,30 @@
package org.xbib.groovy.git
import groovy.transform.Immutable
import org.eclipse.jgit.lib.Repository
/**
* A branch.
*/
@Immutable
class Branch {
/**
* The fully qualified name of this branch.
*/
String fullName
/**
* This branch's upstream branch. {@code null} if this branch isn't
* tracking an upstream.
*/
Branch trackingBranch
/**
* The simple name of the branch.
* @return the simple name
*/
String getName() {
return Repository.shortenRefName(fullName)
}
}

View file

@ -0,0 +1,24 @@
package org.xbib.groovy.git
import groovy.transform.Immutable
/**
* The tracking status of a branch.
*/
@Immutable
class BranchStatus {
/**
* The branch this object is for.
*/
Branch branch
/**
* The number of commits this branch is ahead of its upstream.
*/
int aheadCount
/**
* The number of commits this branch is behind its upstream.
*/
int behindCount
}

View file

@ -0,0 +1,51 @@
package org.xbib.groovy.git
import groovy.transform.Immutable
import java.time.ZonedDateTime
/**
* A commit.
*/
@Immutable(knownImmutableClasses=[ZonedDateTime])
class Commit {
/**
* The full hash of the commit.
*/
String id
/**
* The abbreviated hash of the commit.
*/
String abbreviatedId
/**
* Hashes of any parent commits.
*/
List<String> parentIds
/**
* The author of the changes in the commit.
*/
Person author
/**
* The committer of the changes in the commit.
*/
Person committer
/**
* The time the commit was created with the time zone of the committer, if available.
*/
ZonedDateTime dateTime
/**
* The full commit message.
*/
String fullMessage
/**
* The shortened commit message.
*/
String shortMessage
}

View file

@ -0,0 +1,28 @@
package org.xbib.groovy.git
import groovy.transform.Immutable
import groovy.transform.ToString
@Immutable
@ToString(includeNames=true)
class CommitDiff {
Commit commit
Set<String> added = []
Set<String> copied = []
Set<String> modified = []
Set<String> removed = []
Set<String> renamed = []
/**
* Gets all changed files.
* @return all changed files
*/
Set<String> getAllChanges() {
return added + copied + modified + removed + renamed
}
}

View file

@ -0,0 +1,9 @@
package org.xbib.groovy.git
import org.xbib.groovy.git.internal.AnnotateAtRuntime
@FunctionalInterface
@AnnotateAtRuntime(annotations = "org.gradle.api.HasImplicitReceiver")
interface Configurable<T> {
void configure(T t)
}

View file

@ -0,0 +1,35 @@
package org.xbib.groovy.git
import groovy.transform.Canonical
/**
* Credentials to use for remote operations.
*/
@Canonical
class Credentials {
final String username
final String password
Credentials() {
this(null, null)
}
Credentials(String username, String password) {
this.username = username
this.password = password
}
String getUsername() {
return username ?: ''
}
String getPassword() {
return password ?: ''
}
boolean isPopulated() {
return username != null
}
}

View file

@ -0,0 +1,182 @@
package org.xbib.groovy.git
import org.xbib.groovy.git.internal.WithOperations
import org.xbib.groovy.git.operation.AddOp
import org.xbib.groovy.git.operation.ApplyOp
import org.xbib.groovy.git.operation.CheckoutOp
import org.xbib.groovy.git.operation.CleanOp
import org.xbib.groovy.git.operation.CloneOp
import org.xbib.groovy.git.operation.CommitOp
import org.xbib.groovy.git.operation.DescribeOp
import org.xbib.groovy.git.operation.FetchOp
import org.xbib.groovy.git.operation.InitOp
import org.xbib.groovy.git.operation.LogOp
import org.xbib.groovy.git.operation.LsRemoteOp
import org.xbib.groovy.git.operation.MergeOp
import org.xbib.groovy.git.operation.OpenOp
import org.xbib.groovy.git.operation.PullOp
import org.xbib.groovy.git.operation.PushOp
import org.xbib.groovy.git.operation.ResetOp
import org.xbib.groovy.git.operation.RevertOp
import org.xbib.groovy.git.operation.RmOp
import org.xbib.groovy.git.operation.ShowOp
import org.xbib.groovy.git.operation.StatusOp
import org.xbib.groovy.git.service.BranchService
import org.xbib.groovy.git.service.RemoteService
import org.xbib.groovy.git.service.ResolveService
import org.xbib.groovy.git.service.TagService
import org.xbib.groovy.git.util.GitUtil
/**
* Provides support for performing operations on and getting information about
* a Git repository.
*
* <p>A Git instance can be obtained via 3 methods.</p>
*
* <ul>
* <li>
* <p>{@link org.xbib.groovy.git.operation.OpenOp Open} an existing repository.</p>
* <pre>def git = Git.open(dir: 'path/to/my/repo')</pre>
* </li>
* <li>
* <p>{@link org.xbib.groovy.git.operation.InitOp Initialize} a new repository.</p>
* <pre>def git = Git.init(dir: 'path/to/my/repo')</pre>
* </li>
* <li>
* <p>{@link org.xbib.groovy.git.operation.CloneOp Clone} an existing repository.</p>
* <pre>def git = Git.clone(dir: 'path/to/my/repo', uri: 'git@github.com:jprante/groovy-git.git')</pre>
* </li>
* </ul>
*
* <p>
* Once obtained, operations can be called with two syntaxes.
* </p>
*
* <ul>
* <li>
* <p>Map syntax. Any public property on the {@code *Op} class can be provided as a Map entry.</p>
* <pre>git.commit(message: 'Committing my code.', amend: true)</pre>
* </li>
* <li>
* <p>Closure syntax. Any public property or method on the {@code *Op} class can be used.</p>
* <pre>
* git.log {
* range 'master', 'my-new-branch'
* maxCommits = 5
* }
* </pre>
* </li>
* </ul>
*
* <p>
* Details of each operation's properties and methods are available on the
* doc page for the class. The following operations are supported directly on a
* Git instance.
* </p>
*
* <ul>
* <li>{@link org.xbib.groovy.git.operation.AddOp add}</li>
* <li>{@link org.xbib.groovy.git.operation.ApplyOp apply}</li>
* <li>{@link org.xbib.groovy.git.operation.CheckoutOp checkout}</li>
* <li>{@link org.xbib.groovy.git.operation.CleanOp clean}</li>
* <li>{@link org.xbib.groovy.git.operation.CommitOp commit}</li>
* <li>{@link org.xbib.groovy.git.operation.DescribeOp describe}</li>
* <li>{@link org.xbib.groovy.git.operation.FetchOp fetch}</li>
* <li>{@link org.xbib.groovy.git.operation.LogOp log}</li>
* <li>{@link org.xbib.groovy.git.operation.LsRemoteOp lsremote}</li>
* <li>{@link org.xbib.groovy.git.operation.MergeOp merge}</li>
* <li>{@link org.xbib.groovy.git.operation.PullOp pull}</li>
* <li>{@link org.xbib.groovy.git.operation.PushOp push}</li>
* <li>{@link org.xbib.groovy.git.operation.RmOp remove}</li>
* <li>{@link org.xbib.groovy.git.operation.ResetOp reset}</li>
* <li>{@link org.xbib.groovy.git.operation.RevertOp revert}</li>
* <li>{@link org.xbib.groovy.git.operation.ShowOp show}</li>
* <li>{@link org.xbib.groovy.git.operation.StatusOp status}</li>
* </ul>
*
* <p>
* And the following operations are supported statically on the Git class.
* </p>
*
* <ul>
* <li>{@link org.xbib.groovy.git.operation.CloneOp clone}</li>
* <li>{@link org.xbib.groovy.git.operation.InitOp init}</li>
* <li>{@link org.xbib.groovy.git.operation.OpenOp open}</li>
* </ul>
*
* <p>
* Further operations are available on the following services.
* </p>
*
* <ul>
* <li>{@link org.xbib.groovy.git.service.BranchService branch}</li>
* <li>{@link org.xbib.groovy.git.service.RemoteService remote}</li>
* <li>{@link org.xbib.groovy.git.service.ResolveService resolve}</li>
* <li>{@link org.xbib.groovy.git.service.TagService tag}</li>
* </ul>
*/
@WithOperations(staticOperations=[InitOp, CloneOp, OpenOp], instanceOperations=[CleanOp,
StatusOp, AddOp, RmOp, ResetOp, ApplyOp, PullOp, PushOp, FetchOp, LsRemoteOp,
CheckoutOp, LogOp, CommitOp, RevertOp, MergeOp, DescribeOp, ShowOp])
class Git implements AutoCloseable {
/**
* The repository opened by this object.
*/
final Repository repository
/**
* Supports operations on branches.
*/
final BranchService branch
/**
* Supports operations on remotes.
*/
final RemoteService remote
/**
* Convenience methods for resolving various objects.
*/
final ResolveService resolve
/**
* Supports operations on tags.
*/
final TagService tag
Git(Repository repository) {
this.repository = repository
this.branch = new BranchService(repository)
this.remote = new RemoteService(repository)
this.tag = new TagService(repository)
this.resolve = new ResolveService(repository)
}
/**
* Returns the commit located at the current HEAD of the repository.
* @return the current HEAD commit
*/
Commit head() {
return resolve.toCommit('HEAD')
}
/**
* Checks if {@code base} is an ancestor of {@code tip}.
* @param base the version that might be an ancestor
* @param tip the tip version
*/
boolean isAncestorOf(Object base, Object tip) {
Commit baseCommit = resolve.toCommit(base)
Commit tipCommit = resolve.toCommit(tip)
return GitUtil.isAncestorOf(repository, baseCommit, tipCommit)
}
/**
* Release underlying resources used by this instance. After calling close
* you should not use this instance anymore.
*/
@Override
void close() {
repository.jgit.close()
}
}

View file

@ -0,0 +1,19 @@
package org.xbib.groovy.git
import groovy.transform.Immutable
/**
* A person.
*/
@Immutable
class Person {
/**
* Name of person.
*/
String name
/**
* Email address of person.
*/
String email
}

View file

@ -0,0 +1,14 @@
package org.xbib.groovy.git
import org.eclipse.jgit.api.errors.TransportException
class PushException extends TransportException {
PushException(String message) {
super(message)
}
PushException(String message, Throwable cause) {
super(message, cause)
}
}

View file

@ -0,0 +1,24 @@
package org.xbib.groovy.git
import groovy.transform.Immutable
import org.eclipse.jgit.lib.Repository
/**
* A ref.
*/
@Immutable
class Ref {
/**
* The fully qualified name of this ref.
*/
String fullName
/**
* The simple name of the ref.
* @return the simple name
*/
String getName() {
return Repository.shortenRefName(fullName)
}
}

View file

@ -0,0 +1,39 @@
package org.xbib.groovy.git
import groovy.transform.Immutable
/**
* Remote repository.
*/
@Immutable
class Remote {
/**
* Name of the remote.
*/
String name
/**
* URL to fetch from.
*/
String url
/**
* URL to push to.
*/
String pushUrl
/**
* Specs to fetch from the remote.
*/
List fetchRefSpecs = []
/**
* Specs to push to the remote.
*/
List pushRefSpecs = []
/**
* Whether or not pushes will mirror the repository.
*/
boolean mirror
}

View file

@ -0,0 +1,34 @@
package org.xbib.groovy.git
import org.eclipse.jgit.api.Git
/**
* A repository.
*/
class Repository {
/**
* The directory the repository is contained in.
*/
File rootDir
/**
* The JGit instance opened for this repository.
*/
Git jgit
/**
* The credentials used when talking to remote repositories.
*/
Credentials credentials
Repository(File rootDir, Git jgit, Credentials credentials) {
this.rootDir = rootDir
this.jgit = jgit
this.credentials = credentials
}
@Override
String toString() {
return "Repository(${rootDir.canonicalPath})"
}
}

View file

@ -0,0 +1,60 @@
package org.xbib.groovy.git
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Status of the current working tree and index.
*/
@EqualsAndHashCode
@ToString(includeNames=true)
class Status {
final Changes staged
final Changes unstaged
final Set<String> conflicts
Status(Map args = [:]) {
def invalidArgs = args.keySet() - ['staged', 'unstaged', 'conflicts']
if (invalidArgs) {
throw new IllegalArgumentException("Following keys are not supported: ${invalidArgs}")
}
this.staged = 'staged' in args ? new Changes(args.staged) : new Changes()
this.unstaged = 'unstaged' in args ? new Changes(args.unstaged) : new Changes()
this.conflicts = 'conflicts' in args ? args.conflicts : []
}
@EqualsAndHashCode
@ToString(includeNames=true)
class Changes {
final Set<String> added
final Set<String> modified
final Set<String> removed
Changes(Map args = [:]) {
def invalidArgs = args.keySet() - ['added', 'modified', 'removed']
if (invalidArgs) {
throw new IllegalArgumentException("Following keys are not supported: ${invalidArgs}")
}
this.added = 'added' in args ? args.added : []
this.modified = 'modified' in args ? args.modified : []
this.removed = 'removed' in args ? args.removed : []
}
/**
* Gets all changed files.
* @return all changed files
*/
Set<String> getAllChanges() {
return added + modified + removed
}
}
/**
* Whether the repository has any changes or conflicts.
* @return {@code true} if there are no changes either staged or unstaged or
* any conflicts, {@code false} otherwise
*/
boolean isClean() {
return (staged.allChanges + unstaged.allChanges + conflicts).empty
}
}

View file

@ -0,0 +1,49 @@
package org.xbib.groovy.git
import groovy.transform.Immutable
import java.time.ZonedDateTime
import org.eclipse.jgit.lib.Repository
/**
* A tag.
*/
@Immutable(knownImmutableClasses=[ZonedDateTime])
class Tag {
/**
* The commit this tag points to.
*/
Commit commit
/**
* The person who created the tag.
*/
Person tagger
/**
* The full name of this tag.
*/
String fullName
/**
* The full tag message.
*/
String fullMessage
/**
* The shortened tag message.
*/
String shortMessage
/**
* The time the commit was created with the time zone of the committer, if available.
*/
ZonedDateTime dateTime
/**
* The simple name of this tag.
* @return the simple name
*/
String getName() {
return Repository.shortenRefName(fullName)
}
}

View file

@ -0,0 +1,57 @@
package org.xbib.groovy.git.auth
import org.xbib.groovy.git.Credentials
class AuthConfig {
static final String USERNAME_OPTION = 'org.xbib.groovy.git.auth.username'
static final String PASSWORD_OPTION = 'org.xbib.groovy.git.auth.password'
static final String USERNAME_ENV_VAR = 'GROOVY_GIT_USER'
static final String PASSWORD_ENV_VAR = 'GROOVY_GIT_PASS'
private final Map<String, String> props
private final Map<String, String> env
private AuthConfig(Map<String, String> props, Map<String, String> env) {
this.props = props
this.env = env
GitSystemReader.install()
}
/**
* Constructs and returns a {@link Credentials} instance reflecting the
* settings in the system properties.
* @return a credentials instance reflecting the settings in the system
* properties, or, if the username isn't set, {@code null}
*/
Credentials getHardcodedCreds() {
String username = props[USERNAME_OPTION] ?: env[USERNAME_ENV_VAR]
String password = props[PASSWORD_OPTION] ?: env[PASSWORD_ENV_VAR]
return new Credentials(username, password)
}
/**
* Factory method to construct an authentication configuration from the
* given properties and environment.
* @param properties the properties to use in this configuration
* @param env the environment vars to use in this configuration
* @return the constructed configuration
* @throws IllegalArgumentException if force is set to an invalid option
*/
static AuthConfig fromMap(Map props, Map env = [:]) {
return new AuthConfig(props, env)
}
/**
* Factory method to construct an authentication configuration from the
* current system properties and environment variables.
* @return the constructed configuration
* @throws IllegalArgumentException if force is set to an invalid option
*/
static AuthConfig fromSystem() {
return fromMap(System.properties, System.env)
}
}

View file

@ -0,0 +1,173 @@
package org.xbib.groovy.git.auth;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.TimeZone;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.time.MonotonicClock;
public class GitSystemReader extends SystemReader {
private static final Pattern PATH_SPLITTER = Pattern.compile(Pattern.quote(File.pathSeparator));
private final SystemReader delegate;
private final String gitSsh;
public GitSystemReader(SystemReader delegate, String gitSsh) {
this.delegate = delegate;
this.gitSsh = gitSsh;
}
@Override
public String getHostname() {
return delegate.getHostname();
}
@Override
public String getenv(String variable) {
String value = delegate.getenv(variable);
if ("GIT_SSH".equals(variable) && value == null) {
return gitSsh;
} else {
return value;
}
}
@Override
public String getProperty(String key) {
return delegate.getProperty(key);
}
@Override
public FileBasedConfig openUserConfig(Config parent, FS fs) {
return delegate.openUserConfig(parent, fs);
}
@Override
public FileBasedConfig openSystemConfig(Config parent, FS fs) {
return delegate.openSystemConfig(parent, fs);
}
@Override
public FileBasedConfig openJGitConfig(Config parent, FS fs) {
return delegate.openJGitConfig(parent, fs);
}
@Override
public long getCurrentTime() {
return delegate.getCurrentTime();
}
@Override
public MonotonicClock getClock() {
return delegate.getClock();
}
@Override
public int getTimezone(long when) {
return delegate.getTimezone(when);
}
@Override
public TimeZone getTimeZone() {
return delegate.getTimeZone();
}
@Override
public Locale getLocale() {
return delegate.getLocale();
}
@Override
public SimpleDateFormat getSimpleDateFormat(String pattern) {
return delegate.getSimpleDateFormat(pattern);
}
@Override
public SimpleDateFormat getSimpleDateFormat(String pattern, Locale locale) {
return delegate.getSimpleDateFormat(pattern, locale);
}
@Override
public DateFormat getDateTimeInstance(int dateStyle, int timeStyle) {
return delegate.getDateTimeInstance(dateStyle, timeStyle);
}
@Override
public boolean isWindows() {
return delegate.isWindows();
}
@Override
public boolean isMacOS() {
return delegate.isWindows();
}
@Override
public void checkPath(String path) throws CorruptObjectException {
delegate.checkPath(path);
}
@Override
public void checkPath(byte[] path) throws CorruptObjectException {
delegate.checkPath(path);
}
public static void install() {
SystemReader current = SystemReader.getInstance();
String gitSsh = Stream.of("ssh")
.map(GitSystemReader::findExecutable)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElse(null);
SystemReader grgit = new GitSystemReader(current, gitSsh);
SystemReader.setInstance(grgit);
}
private static Optional<String> findExecutable(String exe) {
List<String> extensions = Optional.ofNullable(System.getenv("PATHEXT"))
.map(PATH_SPLITTER::splitAsStream)
.map(stream -> stream.collect(Collectors.toList()))
.orElse(Collections.emptyList());
Function<Path, Stream<Path>> getCandidatePaths = dir -> {
// assume PATHEXT is only set on Windows
if (extensions.isEmpty()) {
return Stream.of(dir.resolve(exe));
} else {
return extensions.stream()
.map(ext -> dir.resolve(exe + ext));
}
};
return PATH_SPLITTER.splitAsStream(System.getenv("PATH"))
.map(Paths::get)
.flatMap(getCandidatePaths)
.filter(Files::isExecutable)
.map(Path::toAbsolutePath)
.map(Path::toString)
.findFirst();
}
}

View file

@ -0,0 +1,34 @@
package org.xbib.groovy.git.auth
import org.xbib.groovy.git.Credentials
import org.eclipse.jgit.api.TransportCommand
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
final class TransportOpUtil {
private TransportOpUtil() {
}
/**
* Configures the given transport command with the given credentials.
* @param cmd the command to configure
* @param credentials the hardcoded credentials to use, if not {@code null}
*/
static void configure(TransportCommand cmd, Credentials credentials) {
AuthConfig config = AuthConfig.fromSystem()
cmd.setCredentialsProvider(determineCredentialsProvider(config, credentials))
}
private static CredentialsProvider determineCredentialsProvider(AuthConfig config, Credentials credentials) {
Credentials systemCreds = config.hardcodedCreds
if (credentials?.populated) {
return new UsernamePasswordCredentialsProvider(credentials.username, credentials.password)
} else if (systemCreds?.populated) {
return new UsernamePasswordCredentialsProvider(systemCreds.username, systemCreds.password)
} else {
return null
}
}
}

View file

@ -0,0 +1,15 @@
package org.xbib.groovy.git.internal;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.codehaus.groovy.transform.GroovyASTTransformationClass;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@GroovyASTTransformationClass("org.xbib.groovy.git.internal.AnnotateAtRuntimeASTTransformation")
public @interface AnnotateAtRuntime {
String[] annotations() default {};
}

View file

@ -0,0 +1,34 @@
package org.xbib.groovy.git.internal;
import java.util.List;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.AbstractASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;
@GroovyASTTransformation
public final class AnnotateAtRuntimeASTTransformation extends AbstractASTTransformation {
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
AnnotationNode annotation = (AnnotationNode) nodes[0];
AnnotatedNode parent = (AnnotatedNode) nodes[1];
ClassNode clazz = (ClassNode) parent;
List<String> annotations = getMemberList(annotation, "annotations");
for (String name : annotations) {
// !!! UGLY HACK !!!
// Groovy won't think the class is an annotation when creating a ClassNode just based on the name.
// Instead, we create a node based on an interface and then overwrite the name to get the interface
// we actually want.
ClassNode base = new ClassNode(FunctionalInterface.class);
base.setName(name);
clazz.addAnnotation(new AnnotationNode(base));
}
}
}

View file

@ -0,0 +1,40 @@
package org.xbib.groovy.git.internal
import java.util.concurrent.Callable
import org.xbib.groovy.git.Configurable
class OpSyntax {
static def noArgOperation(Class<Callable> opClass, Object[] classArgs) {
def op = opClass.newInstance(classArgs)
return op.call()
}
static def mapOperation(Class<Callable> opClass, Object[] classArgs, Map args) {
def op = opClass.newInstance(classArgs)
args.forEach { key, value ->
op[key] = value
}
return op.call()
}
static def samOperation(Class<Callable> opClass, Object[] classArgs, Configurable arg) {
def op = opClass.newInstance(classArgs)
arg.configure(op)
return op.call()
}
static def closureOperation(Class<Callable> opClass, Object[] classArgs, Closure closure) {
def op = opClass.newInstance(classArgs)
Object originalDelegate = closure.delegate
closure.delegate = op
closure.resolveStrategy = Closure.DELEGATE_FIRST
closure.call()
closure.delegate = originalDelegate
return op.call()
}
}

View file

@ -0,0 +1,12 @@
package org.xbib.groovy.git.internal;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Operation {
String value();
}

View file

@ -0,0 +1,18 @@
package org.xbib.groovy.git.internal;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Callable;
import org.codehaus.groovy.transform.GroovyASTTransformationClass;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@GroovyASTTransformationClass("org.xbib.groovy.git.internal.WithOperationsASTTransformation")
public @interface WithOperations {
Class<? extends Callable<?>>[] staticOperations() default {};
Class<? extends Callable<?>>[] instanceOperations() default {};
}

View file

@ -0,0 +1,180 @@
package org.xbib.groovy.git.internal;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import groovy.lang.Closure;
import org.xbib.groovy.git.Configurable;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.FieldExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.AbstractASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class WithOperationsASTTransformation extends AbstractASTTransformation {
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
AnnotationNode annotation = (AnnotationNode) nodes[0];
AnnotatedNode parent = (AnnotatedNode) nodes[1];
if (parent instanceof ClassNode) {
ClassNode clazz = (ClassNode) parent;
List<ClassNode> staticOps = getClassList(annotation, "staticOperations");
List<ClassNode> instanceOps = getClassList(annotation, "instanceOperations");
staticOps.forEach(op -> makeMethods(clazz, op, true));
instanceOps.forEach(op -> makeMethods(clazz, op, false));
}
}
private void makeMethods(ClassNode targetClass, ClassNode opClass, boolean isStatic) {
AnnotationNode annotation = opClass.getAnnotations(classFromType(Operation.class)).stream()
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Class is not annotated with @Operation: " + opClass));
String opName = getMemberStringValue(annotation, "value");
ClassNode opReturn = opClass.getDeclaredMethod("call", new Parameter[] {}).getReturnType();
targetClass.addMethod(makeNoArgMethod(targetClass, opName, opClass, opReturn, isStatic));
targetClass.addMethod(makeMapMethod(targetClass, opName, opClass, opReturn, isStatic));
targetClass.addMethod(makeSamMethod(targetClass, opName, opClass, opReturn, isStatic));
targetClass.addMethod(makeClosureMethod(targetClass, opName, opClass, opReturn, isStatic));
}
private MethodNode makeNoArgMethod(ClassNode targetClass, String opName, ClassNode opClass, ClassNode opReturn, boolean isStatic) {
Parameter[] parms = new Parameter[] {};
Statement code = new ExpressionStatement(
new StaticMethodCallExpression(
classFromType(OpSyntax.class),
"noArgOperation",
new ArgumentListExpression(
new ClassExpression(opClass),
new ArrayExpression(
classFromType(Object.class),
opConstructorParms(targetClass, isStatic)))));
return new MethodNode(opName, modifiers(isStatic), opReturn, parms, new ClassNode[] {}, code);
}
private MethodNode makeMapMethod(ClassNode targetClass, String opName, ClassNode opClass, ClassNode opReturn, boolean isStatic) {
ClassNode parmType = classFromType(Map.class);
GenericsType[] generics = genericsFromTypes(String.class, Object.class);
parmType.setGenericsTypes(generics);
Parameter[] parms = new Parameter[] {new Parameter(parmType, "args")};
Statement code = new ExpressionStatement(
new StaticMethodCallExpression(
classFromType(OpSyntax.class),
"mapOperation",
new ArgumentListExpression(
new ClassExpression(opClass),
new ArrayExpression(
classFromType(Object.class),
opConstructorParms(targetClass, isStatic)),
new VariableExpression("args"))));
return new MethodNode(opName, modifiers(isStatic), opReturn, parms, new ClassNode[] {}, code);
}
private MethodNode makeSamMethod(ClassNode targetClass, String opName, ClassNode opClass, ClassNode opReturn, boolean isStatic) {
ClassNode parmType = classFromType(Configurable.class);
GenericsType[] generics = new GenericsType[] {new GenericsType(opClass)};
parmType.setGenericsTypes(generics);
Parameter[] parms = new Parameter[] {new Parameter(parmType, "arg")};
Statement code = new ExpressionStatement(
new StaticMethodCallExpression(
classFromType(OpSyntax.class),
"samOperation",
new ArgumentListExpression(
new ClassExpression(opClass),
new ArrayExpression(
classFromType(Object.class), opConstructorParms(targetClass, isStatic)),
new VariableExpression("arg"))));
return new MethodNode(opName, modifiers(isStatic), opReturn, parms, new ClassNode[] {}, code);
}
private MethodNode makeClosureMethod(ClassNode targetClass, String opName, ClassNode opClass, ClassNode opReturn, boolean isStatic) {
ClassNode parmType = classFromType(Closure.class);
Parameter[] parms = new Parameter[] {new Parameter(parmType, "arg")};
Statement code = new ExpressionStatement(
new StaticMethodCallExpression(
classFromType(OpSyntax.class),
"closureOperation",
new ArgumentListExpression(
new ClassExpression(opClass),
new ArrayExpression(
classFromType(Object.class),
opConstructorParms(targetClass, isStatic)),
new VariableExpression("arg"))));
return new MethodNode(opName, modifiers(isStatic), opReturn, parms, new ClassNode[] {}, code);
}
public ClassNode classFromType(Type type) {
if (type instanceof Class) {
Class<?> clazz = (Class<?>) type;
if (clazz.isPrimitive()) {
return ClassHelper.make(clazz);
} else {
return ClassHelper.makeWithoutCaching(clazz, false);
}
} else if (type instanceof ParameterizedType) {
ParameterizedType ptype = (ParameterizedType) type;
ClassNode base = classFromType(ptype.getRawType());
GenericsType[] generics = genericsFromTypes(ptype.getActualTypeArguments());
base.setGenericsTypes(generics);
return base;
} else {
throw new IllegalArgumentException("Unsupported type: " + type.getClass());
}
}
public GenericsType[] genericsFromTypes(Type... types) {
return Arrays.stream(types)
.map(this::classFromType)
.map(GenericsType::new)
.toArray(GenericsType[]::new);
}
public List<Expression> opConstructorParms(ClassNode targetClass, boolean isStatic) {
if (isStatic) {
return Collections.emptyList();
} else {
return Collections.singletonList(new FieldExpression(targetClass.getField("repository")));
}
}
public int modifiers(boolean isStatic) {
int modifiers = Modifier.PUBLIC | Modifier.FINAL;
if (isStatic) {
modifiers |= Modifier.STATIC;
}
return modifiers;
}
}

View file

@ -0,0 +1,38 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.eclipse.jgit.api.AddCommand
/**
* Adds files to the index.
*/
@Operation('add')
class AddOp implements Callable<Void> {
private final Repository repo
/**
* Patterns of files to add to the index.
*/
Set<String> patterns = []
/**
* {@code true} if changes to all currently tracked files should be added
* to the index, {@code false} otherwise.
*/
boolean update = false
AddOp(Repository repo) {
this.repo = repo
}
Void call() {
AddCommand cmd = repo.jgit.add()
patterns.each { cmd.addFilepattern(it) }
cmd.update = update
cmd.call()
return null
}
}

View file

@ -0,0 +1,39 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.util.CoercionUtil
import org.eclipse.jgit.api.ApplyCommand
/**
* Apply a patch to the index.
*/
@Operation('apply')
class ApplyOp implements Callable<Void> {
private final Repository repo
/**
* The patch file to apply to the index.
* @see {@link CoercionUtil#toFile(Object)}
*/
Object patch
ApplyOp(Repository repo) {
this.repo = repo
}
@Override
Void call() {
ApplyCommand cmd = repo.jgit.apply()
if (!patch) {
throw new IllegalStateException('Must set a patch file.')
}
CoercionUtil.toFile(patch).withInputStream { stream ->
cmd.patch = stream
cmd.call()
}
return
}
}

View file

@ -0,0 +1,70 @@
package org.xbib.groovy.git.operation
import org.eclipse.jgit.api.CreateBranchCommand
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode
import org.eclipse.jgit.lib.Ref
import org.xbib.groovy.git.Branch
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.xbib.groovy.git.util.GitUtil
import java.util.concurrent.Callable
/**
* Adds a branch to the repository. Returns the newly created {@link Branch}.
*/
@Operation('add')
class BranchAddOp implements Callable<Branch> {
private final Repository repo
/**
* The name of the branch to add.
*/
String name
/**
* The commit the branch should start at. If this is a remote branch
* it will be automatically tracked.
* @see {@link ResolveService#toRevisionString(Object)}
*/
Object startPoint
/**
* The tracking mode to use. If {@code null}, will use the default
* behavior.
*/
Mode mode
BranchAddOp(Repository repo) {
this.repo = repo
}
Branch call() {
if (mode && !startPoint) {
throw new IllegalStateException('Cannot set mode if no start point.')
}
CreateBranchCommand cmd = repo.jgit.branchCreate()
cmd.name = name
cmd.force = false
if (startPoint) {
String rev = new ResolveService(repo).toRevisionString(startPoint)
cmd.startPoint = rev
}
if (mode) { cmd.upstreamMode = mode.jgit }
Ref ref = cmd.call()
return GitUtil.resolveBranch(repo, ref)
}
static enum Mode {
TRACK(SetupUpstreamMode.TRACK),
NO_TRACK(SetupUpstreamMode.NOTRACK)
private final SetupUpstreamMode jgit
Mode(SetupUpstreamMode jgit) {
this.jgit = jgit
}
}
}

View file

@ -0,0 +1,71 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Branch
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.CreateBranchCommand
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode
import org.eclipse.jgit.lib.Ref
/**
* Changes a branch's start point and/or upstream branch. Returns the changed {@link Branch}.
*/
@Operation('change')
class BranchChangeOp implements Callable<Branch> {
private final Repository repo
/**
* The name of the branch to change.
*/
String name
/**
* The commit the branch should now start at.
* @see {@link ResolveService#toRevisionString(Object)}
*/
Object startPoint
/**
* The tracking mode to use.
*/
Mode mode
BranchChangeOp(Repository repo) {
this.repo = repo
}
Branch call() {
if (!GitUtil.resolveBranch(repo, name)) {
throw new IllegalStateException("Branch does not exist: ${name}")
}
if (!startPoint) {
throw new IllegalArgumentException('Must set new startPoint.')
}
CreateBranchCommand cmd = repo.jgit.branchCreate()
cmd.name = name
cmd.force = true
if (startPoint) {
String rev = new ResolveService(repo).toRevisionString(startPoint)
cmd.startPoint = rev
}
if (mode) { cmd.upstreamMode = mode.jgit }
Ref ref = cmd.call()
return GitUtil.resolveBranch(repo, ref)
}
static enum Mode {
TRACK(SetupUpstreamMode.TRACK),
NO_TRACK(SetupUpstreamMode.NOTRACK)
private final SetupUpstreamMode jgit
Mode(SetupUpstreamMode jgit) {
this.jgit = jgit
}
}
}

View file

@ -0,0 +1,55 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Branch
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.ListBranchCommand
/**
* Lists branches in the repository. Returns a list of {@link Branch}.
*/
@Operation('list')
class BranchListOp implements Callable<List<Branch>> {
private final Repository repo
/**
* Which branches to return.
*/
Mode mode = Mode.LOCAL
/**
* Commit ref branches must contains
*/
Object contains = null
BranchListOp(Repository repo) {
this.repo = repo
}
List<Branch> call() {
ListBranchCommand cmd = repo.jgit.branchList()
cmd.listMode = mode.jgit
if (contains) {
cmd.contains = new ResolveService(repo).toRevisionString(contains)
}
return cmd.call().collect {
GitUtil.resolveBranch(repo, it.name)
}
}
static enum Mode {
ALL(ListBranchCommand.ListMode.ALL),
REMOTE(ListBranchCommand.ListMode.REMOTE),
LOCAL(null)
private final ListBranchCommand.ListMode jgit
private Mode(ListBranchCommand.ListMode jgit) {
this.jgit = jgit
}
}
}

View file

@ -0,0 +1,41 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.eclipse.jgit.api.DeleteBranchCommand
/**
* Removes one or more branches from the repository. Returns a list of
* the fully qualified branch names that were removed.
*/
@Operation('remove')
class BranchRemoveOp implements Callable<List<String>> {
private final Repository repo
/**
* List of all branche names to remove.
* @see {@link ResolveService#toBranchName(Object)}
*/
List names = []
/**
* If {@code false} (the default), only remove branches that
* are merged into another branch. If {@code true} will delete
* regardless.
*/
boolean force = false
BranchRemoveOp(Repository repo) {
this.repo = repo
}
List<String> call() {
DeleteBranchCommand cmd = repo.jgit.branchDelete()
cmd.setBranchNames(names.collect { new ResolveService(repo).toBranchName(it) } as String[])
cmd.force = force
return cmd.call()
}
}

View file

@ -0,0 +1,46 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Branch
import org.xbib.groovy.git.BranchStatus
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.eclipse.jgit.lib.BranchTrackingStatus
/**
* Gets the tracking status of a branch. Returns a {@link BranchStatus}.
*
* <pre>
* def status = git.branch.status(name: 'the-branch')
* </pre>
*/
@Operation('status')
class BranchStatusOp implements Callable<BranchStatus> {
private final Repository repo
/**
* The branch to get the status of.
* @see {@link ResolveService#toBranch(Object)}
*/
Object name
BranchStatusOp(Repository repo) {
this.repo = repo
}
BranchStatus call() {
Branch realBranch = new ResolveService(repo).toBranch(name)
if (realBranch.trackingBranch) {
BranchTrackingStatus status = BranchTrackingStatus.of(repo.jgit.repository, realBranch.fullName)
if (status) {
return new BranchStatus(realBranch, status.aheadCount, status.behindCount)
} else {
throw new IllegalStateException("Could not retrieve status for ${name}")
}
} else {
throw new IllegalStateException("${name} is not set to track another branch")
}
}
}

View file

@ -0,0 +1,62 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.eclipse.jgit.api.CheckoutCommand
/**
* Checks out a branch to the working tree. Does not support checking out
* specific paths.
*/
@Operation('checkout')
class CheckoutOp implements Callable<Void> {
private final Repository repo
/**
* The branch or commit to checkout.
* @see {@link ResolveService#toBranchName(Object)}
*/
Object branch
/**
* {@code true} if the branch does not exist and should be created,
* {@code false} (the default) otherwise
*/
boolean createBranch = false
/**
* If {@code createBranch} or {@code orphan} is {@code true}, start the new branch
* at this commit.
* @see {@link ResolveService#toRevisionString(Object)}
*/
Object startPoint
/**
* {@code true} if the new branch is to be an orphan,
* {@code false} (the default) otherwise
*/
boolean orphan = false
CheckoutOp(Repository repo) {
this.repo = repo
}
Void call() {
if (startPoint && !createBranch && !orphan) {
throw new IllegalArgumentException('cannot set a start point if createBranch and orphan are false')
} else if ((createBranch || orphan) && !branch) {
throw new IllegalArgumentException('must specify branch name to create')
}
CheckoutCommand cmd = repo.jgit.checkout()
ResolveService resolve = new ResolveService(repo)
if (branch) { cmd.name = resolve.toBranchName(branch) }
cmd.createBranch = createBranch
cmd.startPoint = resolve.toRevisionString(startPoint)
cmd.orphan = orphan
cmd.call()
return null
}
}

View file

@ -0,0 +1,54 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.eclipse.jgit.api.CleanCommand
/**
* Remove untracked files from the working tree. Returns the list of
* file paths deleted.
*/
@Operation('clean')
class CleanOp implements Callable<Set<String>> {
private final Repository repo
/**
* The paths to clean. {@code null} if all paths should be included.
*/
Set<String> paths
/**
* {@code true} if untracked directories should also be deleted,
* {@code false} (the default) otherwise
*/
boolean directories = false
/**
* {@code true} if the files should be returned, but not deleted,
* {@code false} (the default) otherwise
*/
boolean dryRun = false
/**
* {@code false} if files ignored by {@code .gitignore} should
* also be deleted, {@code true} (the default) otherwise
*/
boolean ignore = true
CleanOp(Repository repo) {
this.repo = repo
}
Set<String> call() {
CleanCommand cmd = repo.jgit.clean()
if (paths) {
cmd.paths = paths
}
cmd.cleanDirectories = directories
cmd.dryRun = dryRun
cmd.ignore = ignore
return cmd.call()
}
}

View file

@ -0,0 +1,82 @@
package org.xbib.groovy.git.operation
import org.xbib.groovy.git.Git
import java.util.concurrent.Callable
import org.xbib.groovy.git.Credentials
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.auth.TransportOpUtil
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.util.CoercionUtil
import org.eclipse.jgit.api.CloneCommand
/**
* Clones an existing repository. Returns a {@link org.xbib.groovy.git.Git} pointing
* to the resulting repository.
*/
@Operation('clone')
class CloneOp implements Callable<Git> {
/**
* The directory to put the cloned repository.
* @see {@link CoercionUtil#toFile(Object)}
*/
Object dir
/**
* The URI to the repository to be cloned.
*/
String uri
/**
* The name of the remote for the upstream repository. Defaults
* to {@code origin}.
*/
String remote = 'origin'
/**
* {@code true} if the resulting repository should be bare,
* {@code false} (the default) otherwise.
*/
boolean bare = false
/**
* {@code true} (the default) if a working tree should be checked out,
* {@code false} otherwise
*/
boolean checkout = true
/**
* The remote ref that should be checked out after the repository is
* cloned. Defaults to {@code master}.
*/
String refToCheckout
/**
* The username and credentials to use when checking out the
* repository and for subsequent remote operations on the
* repository. This is only needed if hardcoded credentials
* should be used.
*/
Credentials credentials
Git call() {
if (!checkout && refToCheckout) {
throw new IllegalArgumentException('cannot specify a refToCheckout and set checkout to false')
}
CloneCommand cmd = org.eclipse.jgit.api.Git.cloneRepository()
TransportOpUtil.configure(cmd, credentials)
cmd.directory = CoercionUtil.toFile(dir)
cmd.setURI(uri)
cmd.remote = remote
cmd.bare = bare
cmd.noCheckout = !checkout
if (refToCheckout) {
cmd.branch = refToCheckout
}
org.eclipse.jgit.api.Git jgit = cmd.call()
Repository repo = new Repository(CoercionUtil.toFile(dir), jgit, credentials)
return new Git(repo)
}
}

View file

@ -0,0 +1,85 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Commit
import org.xbib.groovy.git.Person
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.CommitCommand
import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.revwalk.RevCommit
/**
* Commits staged changes to the repository. Returns the new {@code Commit}.
*/
@Operation('commit')
class CommitOp implements Callable<Commit> {
private final Repository repo
/**
* Commit message.
*/
String message
/**
* Comment to put in the reflog.
*/
String reflogComment
/**
* The person who committed the changes. Uses the git-config
* setting, if {@code null}.
*/
Person committer
/**
* The person who authored the changes. Uses the git-config
* setting, if {@code null}.
*/
Person author
/**
* Only include these paths when committing. {@code null} to
* include all staged changes.
*/
Set<String> paths = []
/**
* Commit changes to all previously tracked files, even if
* they aren't staged, if {@code true}.
*/
boolean all = false
/**
* {@code true} if the previous commit should be amended with
* these changes.
*/
boolean amend = false
CommitOp(Repository repo) {
this.repo = repo
}
Commit call() {
CommitCommand cmd = repo.jgit.commit()
cmd.message = message
cmd.reflogComment = reflogComment
if (committer) {
cmd.committer = new PersonIdent(committer.name, committer.email)
}
if (author) {
cmd.author = new PersonIdent(author.name, author.email)
}
paths.each {
cmd.setOnly(it)
}
if (all) {
cmd.all = all
}
cmd.amend = amend
RevCommit commit = cmd.call()
return GitUtil.convertCommit(repo, commit)
}
}

View file

@ -0,0 +1,54 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.eclipse.jgit.api.DescribeCommand
/**
* Find the nearest tag reachable. Returns an {@link String}}.
*/
@Operation('describe')
class DescribeOp implements Callable<String> {
private final Repository repo
DescribeOp(Repository repo){
this.repo = repo
}
/**
* Sets the commit to be described. Defaults to HEAD.
* @see {@link ResolveService#toRevisionString(Object)}
*/
Object commit
/**
* Whether to always use long output format or not.
*/
boolean longDescr
/**
* Include non-annotated tags when determining nearest tag.
*/
boolean tags
/**
* glob patterns to match tags against before they are considered
*/
List<String> match = []
String call(){
DescribeCommand cmd = repo.jgit.describe()
if (commit) {
cmd.setTarget(new ResolveService(repo).toRevisionString(commit))
}
cmd.setLong(longDescr)
cmd.setTags(tags)
if (match) {
cmd.setMatch(match as String[])
}
return cmd.call()
}
}

View file

@ -0,0 +1,75 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.auth.TransportOpUtil
import org.xbib.groovy.git.internal.Operation
import org.eclipse.jgit.api.FetchCommand
import org.eclipse.jgit.transport.RefSpec
import org.eclipse.jgit.transport.TagOpt
/**
* Fetch changes from remotes.
*/
@Operation('fetch')
class FetchOp implements Callable<Void> {
private final Repository repo
/**
* Which remote should be fetched.
*/
String remote
/**
* List of refspecs to fetch.
*/
List refSpecs = []
/**
* {@code true} if branches removed by the remote should be
* removed locally.
*/
boolean prune = false
/**
* How should tags be handled.
*/
TagMode tagMode = TagMode.AUTO
FetchOp(Repository repo) {
this.repo = repo
}
/**
* Provides a string conversion to the enums.
*/
void setTagMode(String mode) {
tagMode = mode.toUpperCase()
}
Void call() {
FetchCommand cmd = repo.jgit.fetch()
TransportOpUtil.configure(cmd, repo.credentials)
if (remote) { cmd.remote = remote }
cmd.refSpecs = refSpecs.collect {
new RefSpec(it)
}
cmd.removeDeletedRefs = prune
cmd.tagOpt = tagMode.jgit
cmd.call()
return null
}
enum TagMode {
AUTO(TagOpt.AUTO_FOLLOW),
ALL(TagOpt.FETCH_TAGS),
NONE(TagOpt.NO_TAGS)
final TagOpt jgit
private TagMode(TagOpt opt) {
this.jgit = opt
}
}
}

View file

@ -0,0 +1,36 @@
package org.xbib.groovy.git.operation
import org.xbib.groovy.git.Git
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.util.CoercionUtil
import org.eclipse.jgit.api.InitCommand
/**
* Initializes a new repository. Returns a {@link org.xbib.groovy.git.Git} pointing
* to the resulting repository.
*/
@Operation('init')
class InitOp implements Callable<Git> {
/**
* {@code true} if the repository should not have a
* working tree, {@code false} (the default) otherwise
*/
boolean bare = false
/**
* The directory to initialize the repository in.
* @see {@link CoercionUtil#toFile(Object)}
*/
Object dir
Git call() {
InitCommand cmd = org.eclipse.jgit.api.Git.init()
cmd.bare = bare
cmd.directory = CoercionUtil.toFile(dir)
org.eclipse.jgit.api.Git jgit = cmd.call()
Repository repo = new Repository(CoercionUtil.toFile(dir), jgit, null)
return new Git(repo)
}
}

View file

@ -0,0 +1,62 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Commit
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.LogCommand
/**
* Gets a log of commits in the repository. Returns a list of {@link Commit}s.
* Since a Git history is not necessarilly a line, these commits may not be in
* a strict order.
*/
@Operation('log')
class LogOp implements Callable<List<Commit>> {
private final Repository repo
/**
* @see {@link ResolveService#toRevisionString(Object)}
*/
List includes = []
/**
* @see {@link ResolveService#toRevisionString(Object)}
*/
List excludes = []
List paths = []
int skipCommits = -1
int maxCommits = -1
LogOp(Repository repo) {
this.repo = repo
}
void range(Object since, Object until) {
excludes << since
includes << until
}
List<Commit> call() {
LogCommand cmd = repo.jgit.log()
ResolveService resolve = new ResolveService(repo)
def toObjectId = { rev ->
String revstr = resolve.toRevisionString(rev)
GitUtil.resolveRevObject(repo, revstr, true).id
}
includes.collect(toObjectId).each { object ->
cmd.add(object)
}
excludes.collect(toObjectId).each { object ->
cmd.not(object)
}
paths.each { path ->
cmd.addPath(path as String)
}
cmd.skip = skipCommits
cmd.maxCount = maxCommits
return cmd.call().collect { GitUtil.convertCommit(repo, it) }.asImmutable()
}
}

View file

@ -0,0 +1,39 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Ref
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.auth.TransportOpUtil
import org.xbib.groovy.git.internal.Operation
import org.eclipse.jgit.api.LsRemoteCommand
import org.eclipse.jgit.lib.ObjectId
/**
* List references in a remote repository.
*/
@Operation('lsremote')
class LsRemoteOp implements Callable<Map<Ref, String>> {
private final Repository repo
String remote = 'origin'
boolean heads = false
boolean tags = false
LsRemoteOp(Repository repo) {
this.repo = repo
}
Map<Ref, String> call() {
LsRemoteCommand cmd = repo.jgit.lsRemote()
TransportOpUtil.configure(cmd, repo.credentials)
cmd.remote = remote
cmd.heads = heads
cmd.tags = tags
return cmd.call().collectEntries { jgitRef ->
Ref ref = new Ref(jgitRef.getName())
[(ref): ObjectId.toString(jgitRef.getObjectId())]
}.asImmutable()
}
}

View file

@ -0,0 +1,124 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.MergeCommand
import org.eclipse.jgit.api.MergeResult
/**
* Merges changes from a single head. This is a simplified version of
* merge. If any conflict occurs the merge will throw an exception. The
* conflicting files can be identified with {@code grgit.status()}.
*
* <p>Merge another head into the current branch.</p>
*
* <pre>
* grgit.merge(head: 'some-branch')
* </pre>
*
* <p>Merge with another mode.</p>
*
* <pre>
* grgit.merge(mode: MergeOp.Mode.ONLY_FF)
* </pre>
*/
@Operation('merge')
class MergeOp implements Callable<Void> {
private final Repository repo
/**
* The head to merge into the current HEAD.
* @see {@link ResolveService#toRevisionString(Object)}
*/
Object head
/**
* The message to use for the merge commit
*/
String message
/**
* How to handle the merge.
*/
Mode mode
MergeOp(Repository repo) {
this.repo = repo
}
void setMode(String mode) {
this.mode = mode.toUpperCase().replace('-', '_')
}
Void call() {
MergeCommand cmd = repo.jgit.merge()
if (head) {
/*
* we want to preserve ref name in merge commit msg. if it's a ref, don't
* resolve down to commit id
*/
def ref = repo.jgit.repository.findRef(head)
if (ref == null) {
def revstr = new ResolveService(repo).toRevisionString(head)
cmd.include(GitUtil.resolveObject(repo, revstr))
} else {
cmd.include(ref)
}
}
if (message) {
cmd.setMessage(message)
}
switch (mode) {
case Mode.ONLY_FF:
cmd.fastForward = MergeCommand.FastForwardMode.FF_ONLY
break
case Mode.CREATE_COMMIT:
cmd.fastForward = MergeCommand.FastForwardMode.NO_FF
break
case Mode.SQUASH:
cmd.squash = true
break
case Mode.NO_COMMIT:
cmd.commit = false
break
}
MergeResult result = cmd.call()
if (!result.mergeStatus.successful) {
throw new IllegalStateException("could not merge (conflicting files can be retrieved with a call to grgit.status()): ${result}")
}
return null
}
static enum Mode {
/**
* Fast-forwards if possible, creates a merge commit otherwise.
* Behaves like --ff.
*/
DEFAULT,
/**
* Only merges if a fast-forward is possible.
* Behaves like --ff-only.
*/
ONLY_FF,
/**
* Always creates a merge commit (even if a fast-forward is possible).
* Behaves like --no-ff.
*/
CREATE_COMMIT,
/**
* Squashes the merged changes into one set and leaves them uncommitted.
* Behaves like --squash.
*/
SQUASH,
/**
* Merges changes, but does not commit them. Behaves like --no-commit.
*/
NO_COMMIT
}
}

View file

@ -0,0 +1,61 @@
package org.xbib.groovy.git.operation
import org.xbib.groovy.git.Git
import java.util.concurrent.Callable
import org.xbib.groovy.git.Credentials
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.util.CoercionUtil
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
/**
* Opens an existing repository. Returns a {@link org.xbib.groovy.git.Git} pointing
* to the resulting repository.
*/
@Operation('open')
class OpenOp implements Callable<Git> {
/**
* Hardcoded credentials to use for remote operations.
*/
Credentials credentials
/**
* The directory to open the repository from. Incompatible
* with {@code currentDir}.
* @see {@link CoercionUtil#toFile(Object)}
*/
Object dir
/**
* The directory to begin searching from the repository
* from. Incompatible with {@code dir}.
* @see {@link CoercionUtil#toFile(Object)}
*/
Object currentDir
Git call() {
if (dir && currentDir) {
throw new IllegalArgumentException('Cannot use both dir and currentDir.')
} else if (dir) {
def dirFile = CoercionUtil.toFile(dir)
def repo = new Repository(dirFile, org.eclipse.jgit.api.Git.open(dirFile), credentials)
return new Git(repo)
} else {
FileRepositoryBuilder builder = new FileRepositoryBuilder()
builder.readEnvironment()
if (currentDir) {
File currentDirFile = CoercionUtil.toFile(currentDir)
builder.findGitDir(currentDirFile)
} else {
builder.findGitDir()
}
if(builder.getGitDir() == null){
throw new IllegalStateException('No .git directory found!');
}
def jgitRepo = builder.build()
org.eclipse.jgit.api.Git jgit = new org.eclipse.jgit.api.Git(jgitRepo)
Repository repo = new Repository(jgitRepo.directory, jgit, credentials)
return new Git(repo)
}
}
}

View file

@ -0,0 +1,55 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.auth.TransportOpUtil
import org.xbib.groovy.git.internal.Operation
import org.eclipse.jgit.api.PullCommand
import org.eclipse.jgit.api.PullResult
/**
* Pulls changes from the remote on the current branch. If the changes
* conflict, the pull will fail, any conflicts can be retrieved with
* {@code git.status()}, and throwing an exception.
*/
@Operation('pull')
class PullOp implements Callable<Void> {
private final Repository repo
/**
* The name of the remote to pull. If not set, the current branch's
* configuration will be used.
*/
String remote
/**
* The name of the remote branch to pull. If not set, the current branch's
* configuration will be used.
*/
String branch
/**
* Rebase on top of the changes when they are pulled in, if
* {@code true}. {@code false} (the default) otherwise.
*/
boolean rebase = false
PullOp(Repository repo) {
this.repo = repo
}
Void call() {
PullCommand cmd = repo.jgit.pull()
if (remote) { cmd.remote = remote }
if (branch) { cmd.remoteBranchName = branch }
cmd.rebase = rebase
TransportOpUtil.configure(cmd, repo.credentials)
PullResult result = cmd.call()
if (!result.successful) {
throw new IllegalStateException("Could not pull: ${result}")
}
return null
}
}

View file

@ -0,0 +1,91 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.PushException
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.auth.TransportOpUtil
import org.xbib.groovy.git.internal.Operation
import org.eclipse.jgit.api.PushCommand
import org.eclipse.jgit.transport.RemoteRefUpdate
/**
* Push changes to a remote repository.
*/
@Operation('push')
class PushOp implements Callable<Void> {
private final Repository repo
/**
* The remote to push to.
*/
String remote
/**
* The refs or refspecs to use when pushing. If {@code null}
* and {@code all} is {@code false} only push the current branch.
*/
List refsOrSpecs = []
/**
* {@code true} to push all branches, {@code false} (the default)
* to only push the current one.
*/
boolean all = false
/**
* {@code true} to push tags, {@code false} (the default) otherwise.
*/
boolean tags = false
/**
* {@code true} if branches should be pushed even if they aren't
* a fast-forward, {@code false} (the default) if it should fail.
*/
boolean force = false
/**
* {@code true} if result of this operation should be just estimation
* of real operation result, no real push is performed.
* {@code false} (the default) if real push to remote repo should be performed.
*
* @since 0.4.1
*/
boolean dryRun = false
PushOp(Repository repo) {
this.repo = repo
}
Void call() {
PushCommand cmd = repo.jgit.push()
TransportOpUtil.configure(cmd, repo.credentials)
if (remote) {
cmd.remote = remote
}
refsOrSpecs.each {
cmd.add(it)
}
if (all) {
cmd.setPushAll()
}
if (tags) {
cmd.setPushTags()
}
cmd.force = force
cmd.dryRun = dryRun
def failures = []
cmd.call().each { result ->
result.remoteUpdates.findAll { update ->
!(update.status == RemoteRefUpdate.Status.OK || update.status == RemoteRefUpdate.Status.UP_TO_DATE)
}.each { update ->
String info = "${update.srcRef} to ${update.remoteName}"
String message = update.message ? " (${update.message})" : ''
failures << "${info}${message}"
}
}
if (failures) {
throw new PushException("Failed to push: ${failures.join(',')}")
}
return null
}
}

View file

@ -0,0 +1,82 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Remote
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.lib.Config
import org.eclipse.jgit.transport.RefSpec
import org.eclipse.jgit.transport.RemoteConfig
import org.eclipse.jgit.transport.URIish
/**
* Adds a remote to the repository. Returns the newly created {@link Remote}.
* If remote with given name already exists, this command will fail.
*/
@Operation('add')
class RemoteAddOp implements Callable<Remote> {
private final Repository repository
/**
* Name of the remote.
*/
String name
/**
* URL to fetch from.
*/
String url
/**
* URL to push to.
*/
String pushUrl
/**
* Specs to fetch from the remote.
*/
List fetchRefSpecs = []
/**
* Specs to push to the remote.
*/
List pushRefSpecs = []
/**
* Whether or not pushes will mirror the repository.
*/
boolean mirror
RemoteAddOp(Repository repo) {
this.repository = repo
}
@Override
Remote call() {
Config config = repository.jgit.repository.config
if (RemoteConfig.getAllRemoteConfigs(config).find { it.name == name }) {
throw new IllegalStateException("remote $name already exists")
}
def toUri = {
url -> new URIish(url)
}
def toRefSpec = {
spec -> new RefSpec(spec)
}
RemoteConfig remote = new RemoteConfig(config, name)
if (url) {
remote.addURI(toUri(url))
}
if (pushUrl) {
remote.addPushURI(toUri(pushUrl))
}
remote.fetchRefSpecs = (fetchRefSpecs ?: ["+refs/heads/*:refs/remotes/$name/*"]).collect(toRefSpec)
remote.pushRefSpecs = pushRefSpecs.collect(toRefSpec)
remote.mirror = mirror
remote.update(config)
config.save()
return GitUtil.convertRemote(remote)
}
}

View file

@ -0,0 +1,31 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.Remote
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.transport.RemoteConfig
/**
* Lists remotes in the repository. Returns a list of {@link Remote}.
*/
@Operation('list')
class RemoteListOp implements Callable<List<Remote>> {
private final Repository repository
RemoteListOp(Repository repo) {
this.repository = repo
}
@Override
List<Remote> call() {
return RemoteConfig.getAllRemoteConfigs(repository.jgit.repository.config).collect { rc ->
if (rc.getURIs().size() > 1 || rc.pushURIs.size() > 1) {
throw new IllegalArgumentException("does not currently support multiple URLs in remote: [uris: ${rc.uris}, pushURIs:${rc.pushURIs}]")
}
GitUtil.convertRemote(rc)
}
}
}

View file

@ -0,0 +1,79 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.eclipse.jgit.api.ResetCommand
/**
* Reset changes in the repository.
*/
@Operation('reset')
class ResetOp implements Callable<Void> {
private final Repository repo
/**
* The paths to reset.
*/
Set<String> paths = []
/**
* The commit to reset back to. Defaults to HEAD.
* @see {@link ResolveService#toRevisionString(Object)}
*/
Object commit
/**
* The mode to use when resetting.
*/
Mode mode = Mode.MIXED
ResetOp(Repository repo) {
this.repo = repo
}
void setMode(String mode) {
this.mode = mode.toUpperCase()
}
Void call() {
if (!paths.empty && mode != Mode.MIXED) {
throw new IllegalStateException('Cannot set mode when resetting paths.')
}
ResetCommand cmd = repo.jgit.reset()
paths.each { cmd.addPath(it) }
if (commit) {
cmd.ref = new ResolveService(repo).toRevisionString(commit)
}
if (paths.empty) {
cmd.mode = mode.jgit
}
cmd.call()
return null
}
static enum Mode {
/**
* Reset the index and working tree.
*/
HARD(ResetCommand.ResetType.HARD),
/**
* Reset the index, but not the working tree.
*/
MIXED(ResetCommand.ResetType.MIXED),
/**
* Only reset the HEAD. Leave the index and working tree as-is.
*/
SOFT(ResetCommand.ResetType.SOFT)
private final ResetCommand.ResetType jgit
private Mode(ResetCommand.ResetType jgit) {
this.jgit = jgit
}
}
}

View file

@ -0,0 +1,42 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Commit
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.RevertCommand
import org.eclipse.jgit.revwalk.RevCommit
/**
* Revert one or more commits. Returns the new HEAD {@link Commit}.
*/
@Operation('revert')
class RevertOp implements Callable<Commit> {
private final Repository repo
/**
* List of commits to revert.
* @see {@link ResolveService#toRevisionString(Object)}
*/
List<Object> commits = []
RevertOp(Repository repo) {
this.repo = repo
}
Commit call() {
RevertCommand cmd = repo.jgit.revert()
commits.each {
String revstr = new ResolveService(repo).toRevisionString(it)
cmd.include(GitUtil.resolveObject(repo, revstr))
}
RevCommit commit = cmd.call()
if (cmd.failingResult) {
throw new IllegalStateException("Could not merge reverted commits (conflicting files can be retrieved with a call to grgit.status()): ${cmd.failingResult}")
}
return GitUtil.convertCommit(repo, commit)
}
}

View file

@ -0,0 +1,39 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.eclipse.jgit.api.RmCommand
/**
* Remove files from the index and (optionally) delete them from the working tree.
* Note that wildcards are not supported.
*/
@Operation('remove')
class RmOp implements Callable<Void> {
private final Repository repo
/**
* The file patterns to remove.
*/
Set<String> patterns = []
/**
* {@code true} if files should only be removed from the index,
* {@code false} (the default) otherwise.
*/
boolean cached = false
RmOp(Repository repo) {
this.repo = repo
}
Void call() {
RmCommand cmd = repo.jgit.rm()
patterns.each { cmd.addFilepattern(it) }
cmd.cached = cached
cmd.call()
return null
}
}

View file

@ -0,0 +1,75 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.CommitDiff
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.diff.RenameDetector
import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.treewalk.TreeWalk
/**
* Show changes made in a commit.
* Returns changes made in commit in the form of {@link CommitDiff}.
*/
@Operation('show')
class ShowOp implements Callable<CommitDiff> {
private final Repository repo
/**
* The commit to show
* @see {@link org.xbib.groovy.git.service.ResolveService#toRevisionString(Object)}
*/
Object commit
ShowOp(Repository repo) {
this.repo = repo
}
CommitDiff call() {
if (!commit) {
throw new IllegalArgumentException('You must specify which commit to show')
}
def revString = new ResolveService(repo).toRevisionString(commit)
def commitId = GitUtil.resolveRevObject(repo, revString)
def parentId = GitUtil.resolveParents(repo, commitId).find()
def commit = GitUtil.resolveCommit(repo, commitId)
TreeWalk walk = new TreeWalk(repo.jgit.repository)
walk.recursive = true
if (parentId) {
walk.addTree(parentId.tree)
walk.addTree(commitId.tree)
List initialEntries = DiffEntry.scan(walk)
RenameDetector detector = new RenameDetector(repo.jgit.repository)
detector.addAll(initialEntries)
List entries = detector.compute()
Map entriesByType = entries.groupBy { it.changeType }
return new CommitDiff(
commit: commit,
added: entriesByType[ChangeType.ADD].collect { it.newPath },
copied: entriesByType[ChangeType.COPY].collect { it.newPath },
modified: entriesByType[ChangeType.MODIFY].collect { it.newPath },
removed: entriesByType[ChangeType.DELETE].collect { it.oldPath },
renamed: entriesByType[ChangeType.RENAME].collect { it.newPath }
)
} else {
walk.addTree(commitId.tree)
def added = []
while (walk.next()) {
added << walk.pathString
}
return new CommitDiff(
commit: commit,
added: added
)
}
}
}

View file

@ -0,0 +1,26 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.Status
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.StatusCommand
/**
* Gets the current status of the repository. Returns an {@link Status}.
*/
@Operation('status')
class StatusOp implements Callable<Status> {
private final Repository repo
StatusOp(Repository repo) {
this.repo = repo
}
Status call() {
StatusCommand cmd = repo.jgit.status()
return GitUtil.convertStatus(cmd.call())
}
}

View file

@ -0,0 +1,73 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Person
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.Tag
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.TagCommand
import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.lib.Ref
/**
* Adds a tag to the repository. Returns the newly created {@link Tag}.
*/
@Operation('add')
class TagAddOp implements Callable<Tag> {
private final Repository repo
/**
* The name of the tag to create.
*/
String name
/**
* The message to put on the tag.
*/
String message
/**
* The person who created the tag.
*/
Person tagger
/**
* {@code true} (the default) if an annotated tag should be
* created, {@code false} otherwise.
*/
boolean annotate = true
/**
* {@code true} to overwrite an existing tag, {@code false}
* (the default) otherwise
*/
boolean force = false
/**
* The commit the tag should point to.
* @see {@link ResolveService#toRevisionString(Object)}
*/
Object pointsTo
TagAddOp(Repository repo) {
this.repo = repo
}
Tag call() {
TagCommand cmd = repo.jgit.tag()
cmd.name = name
cmd.message = message
if (tagger) { cmd.tagger = new PersonIdent(tagger.name, tagger.email) }
cmd.annotated = annotate
cmd.forceUpdate = force
if (pointsTo) {
def revstr = new ResolveService(repo).toRevisionString(pointsTo)
cmd.objectId = GitUtil.resolveRevObject(repo, revstr)
}
Ref ref = cmd.call()
return GitUtil.resolveTag(repo, ref)
}
}

View file

@ -0,0 +1,28 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.Tag
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.ListTagCommand
/**
* Lists tags in the repository. Returns a list of {@link Tag}.
*/
@Operation('list')
class TagListOp implements Callable<List<Tag>> {
private final Repository repo
TagListOp(Repository repo) {
this.repo = repo
}
List<Tag> call() {
ListTagCommand cmd = repo.jgit.tagList()
return cmd.call().collect {
GitUtil.resolveTag(repo, it)
}
}
}

View file

@ -0,0 +1,33 @@
package org.xbib.groovy.git.operation
import java.util.concurrent.Callable
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.Operation
import org.xbib.groovy.git.service.ResolveService
import org.eclipse.jgit.api.DeleteTagCommand
/**
* Removes one or more tags from the repository. Returns a list of
* the fully qualified tag names that were removed.
*/
@Operation('remove')
class TagRemoveOp implements Callable<List<String>> {
private final Repository repo
/**
* Names of tags to remove.
* @see {@link ResolveService#toTagName(Object)}
*/
List names = []
TagRemoveOp(Repository repo) {
this.repo = repo
}
List<String> call() {
DeleteTagCommand cmd = repo.jgit.tagDelete()
cmd.tags = names.collect { new ResolveService(repo).toTagName(it) }
return cmd.call()
}
}

View file

@ -0,0 +1,50 @@
package org.xbib.groovy.git.service
import org.xbib.groovy.git.Branch
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.WithOperations
import org.xbib.groovy.git.operation.BranchAddOp
import org.xbib.groovy.git.operation.BranchChangeOp
import org.xbib.groovy.git.operation.BranchListOp
import org.xbib.groovy.git.operation.BranchRemoveOp
import org.xbib.groovy.git.operation.BranchStatusOp
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.lib.Ref
/**
* Provides support for performing branch-related operations on
* a Git repository.
*
* <p>
* Details of each operation's properties and methods are available on the
* doc page for the class. The following operations are supported directly on
* this service instance.
* </p>
*
* <ul>
* <li>{@link org.xbib.groovy.git.operation.BranchAddOp add}</li>
* <li>{@link org.xbib.groovy.git.operation.BranchChangeOp change}</li>
* <li>{@link org.xbib.groovy.git.operation.BranchListOp list}</li>
* <li>{@link org.xbib.groovy.git.operation.BranchRemoveOp remove}</li>
* <li>{@link org.xbib.groovy.git.operation.BranchStatusOp status}</li>
* </ul>
*
*/
@WithOperations(instanceOperations=[BranchListOp, BranchAddOp, BranchRemoveOp, BranchChangeOp, BranchStatusOp])
class BranchService {
private final Repository repository
BranchService(Repository repository) {
this.repository = repository
}
/**
* Gets the branch associated with the current HEAD.
* @return the branch or {@code null} if the HEAD is detached
*/
Branch getCurrent() {
Ref ref = repository.jgit.repository.exactRef('HEAD')?.target
return ref ? GitUtil.resolveBranch(repository, ref) : null
}
}

View file

@ -0,0 +1,29 @@
package org.xbib.groovy.git.service
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.WithOperations
import org.xbib.groovy.git.operation.RemoteAddOp
import org.xbib.groovy.git.operation.RemoteListOp
/**
* Provides support for remote-related operations on a Git repository.
*
* <p>
* Details of each operation's properties and methods are available on the
* doc page for the class. The following operations are supported directly on
* this service instance.
* </p>
*
* <ul>
* <li>{@link org.xbib.groovy.git.operation.RemoteAddOp add}</li>
* <li>{@link org.xbib.groovy.git.operation.RemoteListOp list}</li>
* </ul>
*/
@WithOperations(instanceOperations=[RemoteListOp, RemoteAddOp])
class RemoteService {
private final Repository repository
RemoteService(Repository repository) {
this.repository = repository
}
}

View file

@ -0,0 +1,207 @@
package org.xbib.groovy.git.service
import org.xbib.groovy.git.Branch
import org.xbib.groovy.git.Commit
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.Ref
import org.xbib.groovy.git.Tag
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.lib.ObjectId
/**
* Convenience methods to resolve various objects.
*/
class ResolveService {
private final Repository repository
ResolveService(Repository repository) {
this.repository = repository
}
/**
* Resolves an object ID from the given object. Can handle any of the following
* types:
*
* <ul>
* <li>{@link Commit}</li>
* <li>{@link Tag}</li>
* <li>{@link Branch}</li>
* <li>{@link Ref}</li>
* </ul>
*
* @param object the object to resolve
* @return the corresponding object id
*/
String toObjectId(Object object) {
if (object == null) {
return null
} else if (object instanceof Commit) {
return object.id
} else if (object instanceof Branch || object instanceof Tag || object instanceof Ref) {
return ObjectId.toString(repository.jgit.repository.exactRef(object.fullName).objectId)
} else {
throwIllegalArgument(object)
}
}
/**
* Resolves a commit from the given object. Can handle any of the following
* types:
*
* <ul>
* <li>{@link Commit}</li>
* <li>{@link Tag}</li>
* <li>{@link Branch}</li>
* <li>{@link String}</li>
* <li>{@link GString}</li>
* </ul>
*
* <p>
* String arguments can be in the format of any
* <a href="http://git-scm.com/docs/gitrevisions.html">Git revision string</a>.
* </p>
* @param object the object to resolve
* @return the corresponding commit
*/
Commit toCommit(Object object) {
if (object == null) {
return null
} else if (object instanceof Commit) {
return object
} else if (object instanceof Tag) {
return object.commit
} else if (object instanceof Branch) {
return GitUtil.resolveCommit(repository, object.fullName)
} else if (object instanceof String || object instanceof GString) {
return GitUtil.resolveCommit(repository, object)
} else {
throwIllegalArgument(object)
}
}
/**
* Resolves a branch from the given object. Can handle any of the following
* types:
* <ul>
* <li>{@link Branch}</li>
* <li>{@link String}</li>
* <li>{@link GString}</li>
* </ul>
* @param object the object to resolve
* @return the corresponding commit
*/
Branch toBranch(Object object) {
if (object == null) {
return null
} else if (object instanceof Branch) {
return object
} else if (object instanceof String || object instanceof GString) {
return GitUtil.resolveBranch(repository, object)
} else {
throwIllegalArgument(object)
}
}
/**
* Resolves a branch name from the given object. Can handle any of the following
* types:
* <ul>
* <li>{@link String}</li>
* <li>{@link GString}</li>
* <li>{@link Branch}</li>
* </ul>
* @param object the object to resolve
* @return the corresponding branch name
*/
String toBranchName(Object object) {
if (object == null) {
return object
} else if (object instanceof String || object instanceof GString) {
return object
} else if (object instanceof Branch) {
return object.fullName
} else {
throwIllegalArgument(object)
}
}
/**
* Resolves a tag from the given object. Can handle any of the following
* types:
* <ul>
* <li>{@link Tag}</li>
* <li>{@link String}</li>
* <li>{@link GString}</li>
* </ul>
* @param object the object to resolve
* @return the corresponding commit
*/
Tag toTag(Object object) {
if (object == null) {
return object
} else if (object instanceof Tag) {
return object
} else if (object instanceof String || object instanceof GString) {
GitUtil.resolveTag(repository, object)
} else {
throwIllegalArgument(object)
}
}
/**
* Resolves a tag name from the given object. Can handle any of the following
* types:
* <ul>
* <li>{@link String}</li>
* <li>{@link GString}</li>
* <li>{@link Tag}</li>
* </ul>
* @param object the object to resolve
* @return the corresponding tag name
*/
String toTagName(Object object) {
if (object == null) {
return object
} else if (object instanceof String || object instanceof GString) {
return object
} else if (object instanceof Tag) {
return object.fullName
} else {
throwIllegalArgument(object)
}
}
/**
* Resolves a revision string that corresponds to the given object. Can
* handle any of the following types:
* <ul>
* <li>{@link Commit}</li>
* <li>{@link Tag}</li>
* <li>{@link Branch}</li>
* <li>{@link String}</li>
* <li>{@link GString}</li>
* </ul>
* @param object the object to resolve
* @return the corresponding commit
*/
String toRevisionString(Object object) {
if (object == null) {
return object
} else if (object instanceof Commit) {
return object.id
} else if (object instanceof Tag) {
return object.fullName
} else if (object instanceof Branch) {
return object.fullName
} else if (object instanceof String || object instanceof GString) {
return object
} else {
throwIllegalArgument(object)
}
}
private void throwIllegalArgument(Object object) {
throw new IllegalArgumentException("Can't handle the following object (${object}) of class (${object.class})")
}
}

View file

@ -0,0 +1,33 @@
package org.xbib.groovy.git.service
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.internal.WithOperations
import org.xbib.groovy.git.operation.TagAddOp
import org.xbib.groovy.git.operation.TagListOp
import org.xbib.groovy.git.operation.TagRemoveOp
/**
* Provides support for performing tag-related operations on
* a Git repository.
*
* <p>
* Details of each operation's properties and methods are available on the
* doc page for the class. The following operations are supported directly on
* this service instance.
* </p>
*
* <ul>
* <li>{@link org.xbib.groovy.git.operation.TagAddOp add}</li>
* <li>{@link org.xbib.groovy.git.operation.TagListOp list}</li>
* <li>{@link org.xbib.groovy.git.operation.TagRemoveOp remove}</li>
* </ul>
*
*/
@WithOperations(instanceOperations=[TagListOp, TagAddOp, TagRemoveOp])
class TagService {
private final Repository repository
TagService(Repository repository) {
this.repository = repository
}
}

View file

@ -0,0 +1,19 @@
package org.xbib.groovy.git.util
import java.nio.file.Path
final class CoercionUtil {
private CoercionUtil() {
}
static File toFile(Object obj) {
if (obj instanceof File) {
return obj
} else if (obj instanceof Path) {
return obj.toFile()
} else {
return new File(obj.toString())
}
}
}

View file

@ -0,0 +1,246 @@
package org.xbib.groovy.git.util
import java.time.Instant
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
import org.xbib.groovy.git.Branch
import org.xbib.groovy.git.Commit
import org.xbib.groovy.git.Person
import org.xbib.groovy.git.Remote
import org.xbib.groovy.git.Repository
import org.xbib.groovy.git.Status
import org.xbib.groovy.git.Tag
import org.eclipse.jgit.errors.IncorrectObjectTypeException
import org.eclipse.jgit.lib.BranchConfig
import org.eclipse.jgit.lib.Config
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.revwalk.RevObject
import org.eclipse.jgit.revwalk.RevTag
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.transport.RemoteConfig
/**
* Utility class to perform operations against JGit objects.
*/
class GitUtil {
private GitUtil() {
}
/**
* Resolves a JGit {@code ObjectId} using the given revision string.
* @param repo the Grgit repository to resolve the object from
* @param revstr the revision string to use
* @return the resolved object
*/
static ObjectId resolveObject(Repository repo, String revstr) {
ObjectId object = repo.jgit.repository.resolve(revstr)
return object
}
/**
* Resolves a JGit {@code RevObject} using the given revision string.
* @param repo the Grgit repository to resolve the object from
* @param revstr the revision string to use
* @param peel whether or not to peel the resolved object
* @return the resolved object
*/
static RevObject resolveRevObject(Repository repo, String revstr, boolean peel = false) {
ObjectId id = resolveObject(repo, revstr)
RevWalk walk = new RevWalk(repo.jgit.repository)
RevObject rev = walk.parseAny(id)
return peel ? walk.peel(rev) : rev
}
/**
* Resolves the parents of an object.
* @param repo the Grgit repository to resolve the parents from
* @param id the object to get the parents of
* @return the parents of the commit
*/
static Set<ObjectId> resolveParents(Repository repo, ObjectId id) {
RevWalk walk = new RevWalk(repo.jgit.repository)
RevCommit rev = walk.parseCommit(id)
return rev.parents.collect {
walk.parseCommit(it)
}
}
/**
* Resolves a Grgit {@code Commit} using the given revision string.
* @param repo the Grgit repository to resolve the commit from
* @param revstr the revision string to use
* @return the resolved commit
*/
static Commit resolveCommit(Repository repo, String revstr) {
ObjectId id = resolveObject(repo, revstr)
return resolveCommit(repo, id)
}
/**
* Resolves a Grgit {@code Commit} using the given object.
* @param repo the Grgit repository to resolve the commit from
* @param id the object id of the commit to resolve
* @return the resolved commit
*/
static Commit resolveCommit(Repository repo, ObjectId id) {
RevWalk walk = new RevWalk(repo.jgit.repository)
return convertCommit(repo, walk.parseCommit(id))
}
/**
* Converts a JGit commit to a Grgit commit.
* @param rev the JGit commit to convert
* @return a corresponding Grgit commit
*/
static Commit convertCommit(Repository repo, RevCommit rev) {
Map props = [:]
props.id = ObjectId.toString(rev)
props.abbreviatedId = repo.jgit.repository.newObjectReader().abbreviate(rev).name()
PersonIdent committer = rev.committerIdent
props.committer = new Person(committer.name, committer.emailAddress)
PersonIdent author = rev.authorIdent
props.author = new Person(author.name, author.emailAddress)
Instant instant = Instant.ofEpochSecond(rev.commitTime)
ZoneId zone = Optional.ofNullable(rev.committerIdent.timeZone)
.map { it.toZoneId() }
.orElse(ZoneOffset.UTC)
props.dateTime = ZonedDateTime.ofInstant(instant, zone)
props.fullMessage = rev.fullMessage
props.shortMessage = rev.shortMessage
props.parentIds = rev.parents.collect { ObjectId.toString(it) }
return new Commit(props)
}
/**
* Resolves a Grgit tag from a name.
* @param repo the Grgit repository to resolve from
* @param name the name of the tag to resolve
* @return the resolved tag
*/
static Tag resolveTag(Repository repo, String name) {
Ref ref = repo.jgit.repository.getRef(name)
return resolveTag(repo, ref)
}
/**
* Resolves a Grgit Tag from a JGit ref.
* @param repo the Grgit repository to resolve from
* @param ref the JGit ref to resolve
* @return the resolved tag
*/
static Tag resolveTag(Repository repo, Ref ref) {
Map props = [:]
props.fullName = ref.name
try {
RevWalk walk = new RevWalk(repo.jgit.repository)
RevTag rev = walk.parseTag(ref.objectId)
RevObject target = walk.peel(rev)
walk.parseBody(rev.object)
props.commit = convertCommit(repo, target)
PersonIdent tagger = rev.taggerIdent
props.tagger = new Person(tagger.name, tagger.emailAddress)
props.fullMessage = rev.fullMessage
props.shortMessage = rev.shortMessage
Instant instant = rev.taggerIdent.when.toInstant()
ZoneId zone = Optional.ofNullable(rev.taggerIdent.timeZone)
.map { it.toZoneId() }
.orElse(ZoneOffset.UTC)
props.dateTime = ZonedDateTime.ofInstant(instant, zone)
} catch (IncorrectObjectTypeException e) {
props.commit = resolveCommit(repo, ref.objectId)
}
return new Tag(props)
}
/**
* Resolves a Grgit branch from a name.
* @param repo the Grgit repository to resolve from
* @param name the name of the branch to resolve
* @return the resolved branch
*/
static Branch resolveBranch(Repository repo, String name) {
Ref ref = repo.jgit.repository.findRef(name)
return resolveBranch(repo, ref)
}
/**
* Resolves a Grgit branch from a JGit ref.
* @param repo the Grgit repository to resolve from
* @param ref the JGit ref to resolve
* @return the resolved branch or {@code null} if the {@code ref} is
* {@code null}
*/
static Branch resolveBranch(Repository repo, Ref ref) {
if (ref == null) {
return null
}
Map props = [:]
props.fullName = ref.name
String shortName = org.eclipse.jgit.lib.Repository.shortenRefName(props.fullName)
Config config = repo.jgit.repository.config
BranchConfig branchConfig = new BranchConfig(config, shortName)
if (branchConfig.trackingBranch) {
props.trackingBranch = resolveBranch(repo, branchConfig.trackingBranch)
}
return new Branch(props)
}
/**
* Converts a JGit status to a Grgit status.
* @param jgitStatus the status to convert
* @return the converted status
*/
static Status convertStatus(org.eclipse.jgit.api.Status jgitStatus) {
return new Status(
staged: [
added: jgitStatus.added,
modified: jgitStatus.changed,
removed: jgitStatus.removed
],
unstaged: [
added: jgitStatus.untracked,
modified: jgitStatus.modified,
removed: jgitStatus.missing
],
conflicts: jgitStatus.conflicting
)
}
/**
* Converts a JGit remote to a Grgit remote.
* @param rc the remote config to convert
* @return the converted remote
*/
static Remote convertRemote(RemoteConfig rc) {
return new Remote(
name: rc.name,
url: rc.uris.find(),
pushUrl: rc.pushURIs.find(),
fetchRefSpecs: rc.fetchRefSpecs.collect { it.toString() },
pushRefSpecs: rc.pushRefSpecs.collect { it.toString() },
mirror: rc.mirror)
}
/**
* Checks if {@code base} is an ancestor of {@code tip}.
* @param repo the repository to look in
* @param base the version that might be an ancestor
* @param tip the tip version
*/
static boolean isAncestorOf(Repository repo, Commit base, Commit tip) {
org.eclipse.jgit.lib.Repository jgit = repo.jgit.repo
RevWalk revWalk = new RevWalk(jgit)
RevCommit baseCommit = revWalk.lookupCommit(jgit.resolve(base.id))
RevCommit tipCommit = revWalk.lookupCommit(jgit.resolve(tip.id))
return revWalk.isMergedInto(baseCommit, tipCommit)
}
}

View file

@ -0,0 +1,13 @@
package org.xbib.groovy.git
interface PlatformSpecific {
}
interface WindowsSpecific extends PlatformSpecific {
}
interface LinuxSpecific extends PlatformSpecific {
}
interface MacSpecific extends PlatformSpecific {
}

View file

@ -0,0 +1,63 @@
package org.xbib.groovy.git
import org.xbib.groovy.git.util.GitUtil
import org.eclipse.jgit.api.ListBranchCommand.ListMode
import org.eclipse.jgit.transport.RemoteConfig
final class GitTestUtil {
private GitTestUtil() {
}
static File repoFile(Git grgit, String path, boolean makeDirs = true) {
def file = new File(grgit.repository.rootDir, path)
if (makeDirs) file.parentFile.mkdirs()
return file
}
static File repoDir(Git grgit, String path) {
def file = new File(grgit.repository.rootDir, path)
file.mkdirs()
return file
}
static Branch branch(String fullName, String trackingBranchFullName = null) {
Branch trackingBranch = trackingBranchFullName ? branch(trackingBranchFullName) : null
return new Branch(fullName, trackingBranch)
}
static List branches(Git grgit, boolean trim = false) {
return grgit.repository.jgit.branchList().with {
listMode = ListMode.ALL
delegate.call()
}.collect { trim ? it.name - 'refs/heads/' : it.name }
}
static List remoteBranches(Git grgit) {
return grgit.repository.jgit.branchList().with {
listMode = ListMode.REMOTE
delegate.call()
}.collect { it.name - 'refs/remotes/origin/' }
}
static List tags(Git grgit) {
return grgit.repository.jgit.tagList().call().collect {
it.name - 'refs/tags/'
}
}
static List remotes(Git grgit) {
def jgitConfig = grgit.repository.jgit.getRepository().config
return RemoteConfig.getAllRemoteConfigs(jgitConfig).collect { it.name}
}
static Commit resolve(Git grgit, String revstr) {
return GitUtil.resolveCommit(grgit.repository, revstr)
}
static void configure(Git grgit, Closure closure) {
def config = grgit.repository.jgit.getRepository().config
config.with(closure)
config.save()
}
}

View file

@ -0,0 +1,40 @@
package org.xbib.groovy.git
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
class MultiGitOpSpec extends Specification {
@Rule TemporaryFolder tempDir = new TemporaryFolder()
Person person = new Person('Bruce Wayne', 'bruce.wayne@wayneindustries.com')
protected Git init(String name) {
File repoDir = tempDir.newFolder(name).canonicalFile
org.eclipse.jgit.api.Git jgit = org.eclipse.jgit.api.Git.init().setDirectory(repoDir).call()
// Don't want the user's git config to conflict with test expectations
jgit.repo.FS.userHome = null
jgit.repo.config.with {
setString('user', null, 'name', person.name)
setString('user', null, 'email', person.email)
save()
}
Git.open(dir: repoDir)
}
protected Git clone(String name, Git remote) {
File repoDir = tempDir.newFolder(name)
return Git.clone {
dir = repoDir
uri = remote.repository.rootDir.toURI()
}
}
protected File repoFile(Git git, String path, boolean makeDirs = true) {
GitTestUtil.repoFile(git, path, makeDirs)
}
}

View file

@ -0,0 +1,38 @@
package org.xbib.groovy.git
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
class SimpleGitOpSpec extends Specification {
@Rule TemporaryFolder tempDir = new TemporaryFolder()
Git git
Person person = new Person('Jörg Prante', 'joergprante@gmail.com')
def setup() {
File repoDir = tempDir.newFolder('repo')
org.eclipse.jgit.api.Git jgit = org.eclipse.jgit.api.Git.init().setDirectory(repoDir).call()
// Don't want the user's git config to conflict with test expectations
jgit.repo.FS.userHome = null
jgit.repo.config.with {
setString('user', null, 'name', person.name)
setString('user', null, 'email', person.email)
save()
}
git = Git.open(dir: repoDir)
}
protected File repoFile(String path, boolean makeDirs = true) {
return GitTestUtil.repoFile(git, path, makeDirs)
}
protected File repoDir(String path) {
return GitTestUtil.repoDir(git, path)
}
}

View file

@ -0,0 +1,33 @@
package org.xbib.groovy.git.auth
import org.xbib.groovy.git.Credentials
import spock.lang.Specification
class AuthConfigSpec extends Specification {
def 'getHardcodedCreds returns creds if username and password are set with properties'() {
given:
def props = [(AuthConfig.USERNAME_OPTION): 'myuser', (AuthConfig.PASSWORD_OPTION): 'mypass']
expect:
AuthConfig.fromMap(props).getHardcodedCreds() == new Credentials('myuser', 'mypass')
}
def 'getHardcodedCreds returns creds if username and password are set with env'() {
given:
def env = [(AuthConfig.USERNAME_ENV_VAR): 'myuser', (AuthConfig.PASSWORD_ENV_VAR): 'mypass']
expect:
AuthConfig.fromMap([:], env).getHardcodedCreds() == new Credentials('myuser', 'mypass')
}
def 'getHardcodedCreds returns creds if username is set and password is not'() {
given:
def props = [(AuthConfig.USERNAME_OPTION): 'myuser']
expect:
AuthConfig.fromMap(props).getHardcodedCreds() == new Credentials('myuser', null)
}
def 'getHardcodedCreds are not populated if username is not set'() {
expect:
!AuthConfig.fromMap([:]).getHardcodedCreds().isPopulated()
}
}

View file

@ -0,0 +1,78 @@
package org.xbib.groovy.git.operation
import org.xbib.groovy.git.Status
import org.xbib.groovy.git.SimpleGitOpSpec
class AddOpSpec extends SimpleGitOpSpec {
def 'adding specific file only adds that file'() {
given:
repoFile('1.txt') << '1'
repoFile('2.txt') << '2'
repoFile('test/3.txt') << '3'
when:
git.add(patterns:['1.txt'])
then:
git.status() == new Status(
staged: [added: ['1.txt']],
unstaged: [added: ['2.txt', 'test/3.txt']]
)
}
def 'adding specific directory adds all files within it'() {
given:
repoFile('1.txt') << '1'
repoFile('something/2.txt') << '2'
repoFile('test/3.txt') << '3'
repoFile('test/4.txt') << '4'
repoFile('test/other/5.txt') << '5'
when:
git.add(patterns:['test'])
then:
git.status() == new Status(
staged: [added: ['test/3.txt', 'test/4.txt', 'test/other/5.txt']],
unstaged: [added: ['1.txt', 'something/2.txt']]
)
}
def 'adding file pattern does not work due to lack of JGit support'() {
given:
repoFile('1.bat') << '1'
repoFile('something/2.txt') << '2'
repoFile('test/3.bat') << '3'
repoFile('test/4.txt') << '4'
repoFile('test/other/5.txt') << '5'
when:
git.add(patterns:['**/*.txt'])
then:
git.status() == new Status(
unstaged: [added: ['1.bat', 'test/3.bat', 'something/2.txt', 'test/4.txt', 'test/other/5.txt']]
)
/*
* TODO: get it to work like this
* status.added == ['something/2.txt', 'test/4.txt', 'test/other/5.txt'] as Set
* status.untracked == ['1.bat', 'test/3.bat'] as Set
*/
}
def 'adding with update true only adds/removes files already in the index'() {
given:
repoFile('1.bat') << '1'
repoFile('something/2.txt') << '2'
repoFile('test/3.bat') << '3'
git.add(patterns:['.'])
git.repository.jgit.commit().setMessage('Test').call()
repoFile('1.bat') << '1'
repoFile('something/2.txt') << '2'
assert repoFile('test/3.bat').delete()
repoFile('test/4.txt') << '4'
repoFile('test/other/5.txt') << '5'
when:
git.add(patterns:['.'], update:true)
then:
git.status() == new Status(
staged: [modified: ['1.bat', 'something/2.txt'], removed: ['test/3.bat']],
unstaged: [added: ['test/4.txt', 'test/other/5.txt']]
)
}
}

View file

@ -0,0 +1,31 @@
package org.xbib.groovy.git.operation
import org.xbib.groovy.git.SimpleGitOpSpec
class ApplyOpSpec extends SimpleGitOpSpec {
def 'apply with no patch fails'() {
when:
git.apply()
then:
thrown(IllegalStateException)
}
def 'apply with patch succeeds'() {
given:
repoFile('1.txt') << 'something'
repoFile('2.txt') << 'something else\n'
git.add(patterns:['.'])
git.commit(message: 'Test')
def patch = tempDir.newFile()
this.class.getResourceAsStream('/org/xbib/groovy/git/operation/sample.patch').withStream { stream ->
patch << stream
}
when:
git.apply(patch: patch)
then:
repoFile('1.txt').text == 'something'
repoFile('2.txt').text == 'something else\nis being added\n'
repoFile('3.txt').text == 'some new stuff\n'
}
}

Some files were not shown because too many files have changed in this diff Show more