Skip to main content

pully 🪀

pipeline status coverage report latest release pre-commit code style: black imports: isort cici enabled code style: prettier

pully is a tool for managing a large number of Git repository checkouts effectively.

Why pully

pully was created to make managing and automating changes to multiple projects easier. There are two general use cases pully is designed to address. First, a mass action where a a set of projects is cloned and the same action is run on each project. Second, managing a local workspace for a set of projects.

Large scale changes

pully helps solve problems where you need to make the same action across multiple projects.

  • Update the copyright date in the license file of all projects.
  • Update pre-commit versions in all projects.
  • Add badgie to all projects.

The recommended workflow in this case is to use pully to clone workspaces for mass actions not regular development. Avoid using the same project workspaces for both regular development and automated actions. Different branches, new files, and unknown state that are common in work in progress are likely to interfere with scripted changes.

Manage local workspaces

pully can be used to manage project checkouts for regular development. This makes onboarding new developers or setting up new environments easier. Common tasks like pulling the latest changes or pruning remote branches are easy to do. Beware of scripting changes to multiple projects since the status of each project may different and cause unexpected results.

With great power comes great responsibility

We recommend tremendous caution when automating repository changes. Some tips:

  • Avoid making and committing changes in a single command.
  • Ensure that a human is reviewing all changes before committing.
  • Automate the creation of branches and merge requests as a minimally privileged user.

We expect some of these workflows to become pully features in the future.

Installation

python3 -m pip install pully

Quickstart

# add projects for pully to track
pully add -G saferatday0/library

# clone/fetch all projects
pully get

# run commands in checked out projects
pully run touch .gitkeep

Usage

Initialize a project workspace

Run pully init to create a project workspace.

pully init

A .pully.json will be created in the current directory that will be used to track project checkouts.

Add projects for tracking

Track any project by path:

pully add -P saferatday0/badgie
$ pully add -P saferatday0/badgie
searching for group id by full path saferatday0
searching for project id by full path
found project ids: [70752539]
found pully file at /home/user/projects/.pully.json
adding saferatday0/badgie

Track any group by path (subgroups are supported):

pully add -G saferatday0/library
$ pully add -G saferatday0/library
searching for group id by full path saferatday0/library
found group ids: [108399662]
creating new pullyfile at /home/user/projects
adding saferatday0/library/ansible
adding saferatday0/library/asciidoctor
...
adding saferatday0/library/verible
adding saferatday0/library/zola

Only newly added projects will be printed.

Clone/fetch all repositories

Call pully get to clone tracked projects to your local filesystem:

pully get
$ pully get
found pully file at /home/user/projects/.pully.json
cloning saferatday0/library/ansible (70326944)
cloning saferatday0/library/asciidoctor (70588591)
...
cloning saferatday0/library/zola (70432884)
cloning saferatday0/badgie (70752539)

The download strategy can be selected with -s/--strategy. Multiple strategies are available:

  • clone (default) - Clone the repository, skipping if it is already present.

  • fetch - Clone the repository if not found, or fetch updates if found.

  • pull - Clone the repository if not found, or fetch and merge updates if found.

The clone, fetch, or pull depth can be set with --depth. This is particularly useful when you want to run the same command on many projects and only need the latest commit.

List all checked out projects

pully ls
$ pully ls
found pully file at /home/user/projects/.pully.json
cloned  70326944  saferatday0/library/ansible
cloned  70588591  saferatday0/library/asciidoctor
-       70588591  saferatday0/library/container
...

Filter by project path

The ls, pull, and run commands support filtering by local path prefix using the -p/--path-prefix option:

$ pully get -p saferatday0/badgie
found pully file at /home/bweir/Projects/gitlab.com/.pully.json
fetching saferatday0/badgie (70752539)
fetching saferatday0/badgie.me (45125259)

Running commands on repositories

The pully run command can be used to run a command in each repository.

Commands can be run in one of two ways:

  • As arguments

  • Using -c/--command

As arguments

When run this way, commands are run directly, a shell is not instantiated, and executable paths are fully resolved:

pully run git status

Command line options can be passed using --:

pully run -- ls -l

Using -c/--command

When run this way, a shell is launched, so shell features like indirection and pipes can be used

pully run -c 'echo "$(pwd) $(git branch)"'

["/bin/sh", "-euc"] is the default entrypoint. A different entrypoint can be specified with -e/--entrypoint:

pully run -e '["python3", "-c"]' -c 'fp = open(".gitignore", "a"); fp.write("*.pdf\n");'
$ pully run -e '["python3", "-c"]' -c 'fp = open(".gitignore", "a"); fp.write("*.pdf\n");'
found pully file at /home/user/projects/.pully.json
running saferatday0/library/ansible --------------------------------------------
running saferatday0/library/asciidoctor ----------------------------------------
...
$ cd saferatday0/badgie/
$ git diff
diff --git a/.gitignore b/.gitignore
index c84b6c9..e56720a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ build/
 env.sh

 node_modules/
+*.pdf

Project environment variables

pully run populates a few environment variables with information about the current context.

PULLY_PROJECT_PATH

The path to the cloned project repository relative to the pully file directory.

$ echo $PULLY_PROJECT_PATH
saferatday0/handbook

PULLY_PROJECT_FULL_PATH

The full path to the cloned project repository.

$ echo $PULLY_PROJECT_FULL_PATH
/home/user/Projects/saferatday0/handbook

Cleanup

Reverting changes is easy:

pully run git checkout .
$ pully run git checkout .
found pully file at /home/bweir/blah/.pully.json
running saferatday0/library/ansible --------------------------------------------
Updated 1 path from the index
running saferatday0/library/asciidoctor ----------------------------------------
Updated 1 path from the index

Private repositories

pully can clone private repositories by setting the GITLAB_PRIVATE_TOKEN environment variable.

Note: This classic token requires at least the read_repository permission to pull, and the write_repository permission to write.

export GITLAB_PRIVATE_TOKEN="XXXXXX"
pully add -G private/namespace
pully get

Certain organizations may require SSH authentication to pull repositories. If you recieve git pull-related error messages, ensure an ssh-agent is running, and your SSH identity is added to it:

pgrep ssh-agent # Check if an ssh-agent is running
eval "$(ssh-agent -s)" # If not, start one
ssh-add ~/.ssh/id_rsa # Point to your SSH private key

License

Copyright 2025 UL Research Institutes.

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.

Metadata

Release files for pully 0.14.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pully 0.14.0
File Size Uploaded Provenance
pully-0.14.0.tar.gz 19.8 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pully 0.14.0
File Interpreter ABI Platform
pully-0.14.0-py3-none-any.whl Python 3 none any Details

Total release size: 39.3 kB

Release files / pully-0.14.0.tar.gz

Download URL pully-0.14.0.tar.gz
Size 19.8 kB
Tags Source
SHA-256 checksum
How to use checksums
c2b64d29f90e14bedbb982963118ff07d10ab4a19e3052101200234222907119
BLAKE2b-256 checksum
How to use checksums
12db3e1c39c265bda5da62b1ce2e17e2d569d739c42978582c0ec166194f0b5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / pully-0.14.0-py3-none-any.whl

Download URL pully-0.14.0-py3-none-any.whl
Size 19.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6fa3e133c760e93497c37867a8b78914da3219b2e4faf9c583d3626bc4a3ac65
BLAKE2b-256 checksum
How to use checksums
5001e76aa0256c76848e64bd3006484296b8095e2f76fd101e6d63c24c08c9da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.2.0 CPython/3.11.15

Security for pully 0.14.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page