Compose your project using git repositories and Ruby tasks.
GitCompound combines features of Git submodules and common dependency managers like Bundler or Composer.
Create Compoundfile
or .gitcompound
manifest:
name :base_component
maintainer 'Your Name <your.email@example.com>'
component :vendor_1 do
version '~>1.1'
source 'git@github.com:/user/repository'
destination 'src/component_1'
end
component :second_component do
version '>=2.0'
source 'git@github.com:/user/repository_2'
destination 'src/component_2'
end
component :my_component do
branch 'feature/new-feature'
source '/my/component_3/repository'
destination 'src/component_3'
end
task 'print details', :each do |_path, component|
puts "Component `#{component.name}` installed in #{component.path}"
end
Then run gitcompound build
.
GitCompound will also process manifests found in dependencies.
GitCompound has common features of both Git submodules and dependency managers like Bundler or Composer. It is a distributed dependency manager and alternative to Git submodules that gives you more flexibility.
It is particularly useful when you need to develop your project dependencies at the same time as project itself.
Core features:
-
GitCompound introduces Domain Specific Language designed for writing manifests.
-
Manifest file is used to specify dependencies of your project.
-
Manifest can declare dependencies on components using different version strategies (Rubygems-like version, tag, branch or explicit SHA).
-
Manifests will be processed in hierarchical way. Manifest that
gitcompound
command is run against is root manifest.GitCompound
processes all subsequent manifests found in dependencies using depth-first search of dependency graph. -
Root manifest and manifests of each subsequent component can declare Ruby tasks that will be executed when component is built or updated.
-
It is possible to install dependencies in root directory (
Dir.pwd
wheregitcompound
command is invoked). Each subsequent component can install their dependencies into root directory (see overview for destination DSL method). -
Build process creates lockfile in
.gitcompound.lock
. It locks components on specific commit SHAs. It is then possible to build components directly, depending on versions (SHAs) from lockfile. When lockfile is present if will be used each timebuild
command is invoked. -
It is possible to use repositories, that has been used as dependencies, for development (like in Git submodules).
gitcompound build [manifest]
-- builds project from manifest or lockfile
If manifest is not supplied, it uses Compoundfile
or .gitcompound
gitcompound update [manifest]
-- updates project and lockfile
If parameter --perserve-lock
is present, lockfile will not be updated.
gitcompound check [manifest]
-- checks for circular dependencies, conflicting dependencies
and name constraints
gitcompound show [manifest]
-- prints project structure
gitcompound help
-- prints help message
-
--verbose
-- turns debug mode onThis will show you what happens under the hood. All Git commands, procedure steps and tasks being executed will be printed.
-
--disable-colors
-- disables ANSI colors in output
-
Use
name
method to specify name of manifest or component that this manifest is included in. -
Use
maintainer
method to specify maintainer of component. This can take more arguments (separated by comma), for example:maintaner 'First maintaner', 'Second maintaner'
-
Add dependency to required component using
component
method.This method takes two arguments -- name of component (as symbol) and implicit or explicit block.
Beware that
GitCompound
checks name constraints, so if you rely on component with name:component_1
, and this component hasCompoundfile
inside that declares it's name as something else than:component_1
--GitCompound
will raise exception. -
Components can use following version strategies:
-
version
-- Rubygems-like version strategyThis strategy uses Git tags to determine available component versions. If tag matches
/^v?#{Gem::Version::VERSION_PATTERN}$/
then it is considered to be version tag and therefore it can be used with Rubygems syntax (like pessimistic version constraint operator) -
tag
-- use component sources specified by tag -
branch
-- use HEAD of given branch -
sha
-- use explicitly set commit SHAIf SHA points to HEAD of some existing branch, this branch will be checked out instead of SHA. This will prevent going into detached state. If SHA does not point to any HEAD of existing branch, component destination repository will be left in detached state.
-
-
Provide path to source repository using
source
method of manifest domain specific language.It will be used as source to clone repository into destination directory.
This can take
:shallow
argument. When:shallow
is set, shallow clone will be performed (--branch #{ref} --depth 1
):component :bootstrap do version '~>3.3.5' source 'git@github.com:twbs/bootstrap', :shallow destination '/vendor/bootstrap' end
However using it is not recommended at all.
It can be helpful when required component is big and you need to build your project only once, but it will cause issues with update. You will not be able to update it properly. Use it with caution !
-
Use
destination
method to specify destination path where component will be cloned into.This should be relative path in most cases.
Relative path is always relative to parent component directory. So if you define
component_1
with destination pathcomponent_1/
, and this component will depend oncomponent_2
with destination path set tosrc/component_2
, you will end with./component_1/src/component_2
directory after building components.When path is absolute -- it will be always relative to
Dir.pwd
where you invokegitcompound *
commands. So if you havecomponent_1
with destination path set to/component_1
andcomponent_1
which has manifest that depends oncomponent_2
with destination path set to/component_2
, thenGitCompound
will create two separate directories inDir.pwd
:./component_1
and./component_2
.Use absolute paths with caution as it affects how parent projects will be built. Ceraintly components that are libraries should not use it at all. If component is project -- it can take benefit from using absolute paths in component destination.
-
Running tasks
It is possible to use
task
method to define new task.task
method takes 2 or 3 arguments. First one is task name (symbol). Second one is optional task type that define how, and in which context, task will be executed. Third one is block that will be excuted.Currently there are three task types:
-
:manifest
type (this is default) -- run task in context of current manifestTask will be executed only once, in context of current manifest this task is defined in. Example:
task :print_manifest_name do |dir, manifest| puts "Current manifest name is #{manifest.name} and dir is: #{dir}" end
-
:each
type -- run task for each component defined in current manifestThis executes task for all components that are explicitly defined in current manifest. Example:
task :print_component_name, :each do |dir, component| puts "Current component name: #{component.name}" puts "Current component source: #{component.origin}" puts "Current component destination: #{component.path}" puts "Component directory: #{dir}" end
Note that
dir
here is the same ascomponent.path
. -
:all
type -- run task for all child components of this manifestTask for
:all
components will be executed in context of every component this manifest is parent of.It will be executed for all components defined in manifest itself and for all components that are included in child manifests (obviously child manifests are manifests included in components defined in parent manifest).
Example:
task :print_all_component_names, :all do |dir, component| puts "Component #{component.name} destination dir: #{dir}" end
By default
GitCompound
executes only tasks defined in root manifest.This is default behaviour dictated by security reasons. Since all tasks (also those defined in child component) are visited in reverse order it is possible to execute them too.
If you know what you are doing and it is your conscious decision to run all tasks in project pass
--allow-nested-subtasks
options tobuild
orupdate
command.It can be beneficial approach, but it has to be done with caution.
-
-
Accessing manifests
GitCompound
tries to be as fast as possible. To achieve this it tries to access manifest of required component without cloning it first. Is uses different strategies to achieve this. Currently only two strategies are available:GitArchiveStrategy
-- it usesgit archive
command to access single file in remote repository,GithubStrategy
-- it uses http request toraw.githubusercontent.com
to access manifest file at GitHub.
It is possible to create new strategies by implementing new strategy base on
GitCompound::Repository::RemoteFile::RemoteFileStrategy
abstraction. -
Using lockfile (
.gitcompound.lock
)If lockfile is present,
gitcompound build
command will ignore manifest, and build components, with versions locked in commit SHA. You can always be sure that your environment will be unchange when building from lockfile. -
Building manifest
Behavior of
build
command depends on whether lockfile exists or not.-
When lockfile does not exist -- components will be built using versions stored in manifest.
Then lockfile
.gitcompound.lock
will be created, locking each component on specific commit SHA that component has been checkout on during build process. -
If lockfile is present it will be used as source for update process (see "Updating manifest" below)
-
-
Updating manifest
When using
gitcompound update
command each subsequent component will be either built, updated or replaced:- built (cloned) -- if destination directory does not exist
- updated (fetch & checkout) -- if component exists, matches origin and existing SHA != new SHA
- replaced (remove & clone) -- if component exists but doesn't match origin remote
When invoking update process via
gitcompound update
command, versions specified in manifest file will be used.
Take a look at issues at GitHub.
This is free software licensed under MIT license