Skip to content
Talk to our solutions team

Clone or refresh a repository fleet

Every repository your build needs, present on disk and current — cloned if missing, fast-forwarded if already there — driven from a list you maintain rather than a script you edit.

When you finish you will have one flow that takes a machine with nothing checked out to a machine ready to build, and that is safe to re-run any time.

A fleet of repositories is the point where “just clone it” stops scaling.

Without thisWith this
A README with thirty git clone lines that drifts the week it is writtenOne list, read by the flow, reviewed like code
A new machine takes an hour of copy-pasteOne command, and you get coffee
Nobody is sure whether their checkout is currentRe-running is the answer, and it is cheap
A new repository is added and half the team never gets itAdd a row; everyone picks it up on their next run
One missing repository aborts the whole scriptThe run continues and tells you which one

The list is the deliverable as much as the flow is. It becomes the answer to “what makes up this product”, in a file, with a history.

You needWhy
Read access to the repositoriesCloning
A working directory with roomEverything lands under it
A list of repositoriesA CSV with a name and a URL

Step 1 — put the fleet in a list, not the flow

Section titled “Step 1 — put the fleet in a list, not the flow”

The flow should not change when the fleet does.

name,url,build
service-alpha,https://git.example.com/org/service-alpha.git,TRUE
service-beta,https://git.example.com/org/service-beta.git,TRUE
tool-gamma,https://git.example.com/org/tool-gamma.git,FALSE

The build column is not used here, but it will be by Build every service from trunk — one list, several flows.

name: refresh-fleet
workingdirectory: ~/workspace
continueonerror: true
vars:
branch: main
tables:
repos:
type: csv
file: ./fleet.csv

continueonerror: true matters. One unreachable repository should not stop the other twenty-nine from updating; you want the run to finish and tell you what it could not do.

A task with table: runs once per row, with each column available as a variable.

tasks:
- name: clone
table: repos
check: |
"{{name}}" !== ""
shell: |
if [ ! -d "{{name}}/.git" ]; then
echo "cloning {{name}}"
git clone --branch {{branch}} {{url}} {{name}}
else
echo "present: {{name}}"
fi

Separate from cloning, because the failure modes differ: a clone fails on access, an update fails on local changes. Keeping them apart tells you which happened.

- name: update
table: repos
shell: |
cd {{name}} 2>/dev/null || exit 0
git fetch --prune --quiet
git checkout {{branch}} --quiet
git pull --ff-only --quiet && echo "updated: {{name}}" \
|| echo "SKIPPED {{name}} — local changes or diverged"

--ff-only is the safety. It refuses to merge, so a repository with local work is reported and left alone rather than silently altered.

- name: summary
shell: |
echo "fleet under $(pwd):"
ls -1d */ | wc -l
name: refresh-fleet
list: true
workingdirectory: ~/workspace
continueonerror: true
vars:
branch: main
tables:
repos:
type: csv
file: ./fleet.csv
tasks:
- name: clone
table: repos
check: |
"{{name}}" !== ""
shell: |
if [ ! -d "{{name}}/.git" ]; then
echo "cloning {{name}}"
git clone --branch {{branch}} {{url}} {{name}}
else
echo "present: {{name}}"
fi
- name: update
table: repos
shell: |
cd {{name}} 2>/dev/null || exit 0
git fetch --prune --quiet
git checkout {{branch}} --quiet
git pull --ff-only --quiet && echo "updated: {{name}}" \
|| echo "SKIPPED {{name}} — local changes or diverged"
- name: summary
shell: |
echo "fleet under $(pwd):"
ls -1d */ | wc -l

Either works here, and the script is arguably clearer: the clone-or-pull decision is a conditional, which reads better in code than as a choice: node. Use the flow when this runs as one stage of a larger build.

Terminal window
kis flow -f refresh-fleet.yaml

Track a release branch instead of the mainline:

Terminal window
kis flow -f refresh-fleet.yaml -v branch=release-1.1

The run log names every repository as cloned, present, updated or skipped. Anything marked SKIPPED has local work — deal with those by hand, which is the point of not merging for you.

ChangeWhere
A different branch per repositoryAdd a branch column and use {{branch}} from the row
Shallow clones for speedgit clone --depth 1 — fine for building, not for tagging
Run it much fasterSee Build a fleet in parallel for the map pattern