Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
/git-show-branch
/git-show-index
/git-show-ref
/git-son
/git-sparse-checkout
/git-stage
/git-stash
Expand Down
64 changes: 64 additions & 0 deletions Documentation/git-son.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
git-son(1)
==========

NAME
----
git-son - Create an independent child repository that knows its parent

SYNOPSIS
--------
[verse]
'git son' [--inherit] [--branch <branch>] <name>

DESCRIPTION
-----------

Create a new independent Git repository inside the current working
tree as a subdirectory named `<name>`. Unlike a submodule, the child
repository is not tracked by the parent; instead, `<name>/` is added
to the parent's `.gitignore`.

The child repository is configured with a remote called `parent`
pointing back to the parent repository's origin URL (or local path
if no origin is set), allowing the child to fetch from the parent
at any time.

OPTIONS
-------
--inherit::
Fetch the parent's history into the child repository at
creation time. Without this flag, the child starts with a
single initial commit.

--branch <branch>::
When used with `--inherit`, check out the given branch from
the parent instead of the default branch. This option
requires `--inherit`.

<name>::
The name of the subdirectory (and child repository) to create.
Must not already exist.

EXAMPLES
--------

Create a simple child repository:

git son my-tool

Create a child that inherits the parent's history:

git son --inherit my-fork

Create a child starting from a specific parent branch:

git son --inherit --branch feature my-experiment

Later, from within the child, fetch updates from the parent:

cd my-tool
git fetch parent

GIT
---
Part of the linkgit:git[1] suite
1 change: 1 addition & 0 deletions Documentation/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ manpages = {
'git-show-ref.adoc' : 1,
'git-show.adoc' : 1,
'git-sh-setup.adoc' : 1,
'git-son.adoc' : 1,
'git-sparse-checkout.adoc' : 1,
'git-stage.adoc' : 1,
'git-stash.adoc' : 1,
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-quiltimport.sh
SCRIPT_SH += git-request-pull.sh
SCRIPT_SH += git-son.sh
SCRIPT_SH += git-submodule.sh
SCRIPT_SH += git-web--browse.sh

Expand Down
1 change: 1 addition & 0 deletions command-list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ git-show mainporcelain info
git-show-branch ancillaryinterrogators complete
git-show-index plumbinginterrogators
git-show-ref plumbinginterrogators
git-son mainporcelain
git-sparse-checkout mainporcelain
git-stage complete
git-stash mainporcelain
Expand Down
97 changes: 97 additions & 0 deletions git-son.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/bin/sh
#
# git-son: create an independent child repository that knows its parent
#

SUBDIRECTORY_OK='Yes'
OPTIONS_SPEC='git son [options] <name>
--
inherit fetch parent history into the son
branch= start the son from a specific parent branch
'

. git-sh-setup
require_work_tree
cd_to_toplevel

inherit=
branch=
while test $# -gt 0
do
case "$1" in
--inherit)
inherit=1 ;;
--branch)
shift
branch="$1" ;;
--)
shift; break ;;
-*)
usage ;;
*)
break ;;
esac
shift
done

name="$1"
test -n "$name" || usage

if test -n "$branch" && test -z "$inherit"
then
die "fatal: --branch requires --inherit"
fi

parent_dir="$(pwd)"
parent_remote="$(git remote get-url origin 2>/dev/null)" || parent_remote=

if test -e "$name"
then
die "fatal: '$name' already exists"
fi

mkdir "$name" || die "fatal: could not create directory '$name'"

if ! echo "$name/" >> "$parent_dir/.gitignore" 2>/dev/null
then
rm -rf "$name"
die "fatal: could not update .gitignore"
fi

cd "$name" || die "fatal: could not enter directory '$name'"

if ! git init
then
rm -rf "$parent_dir/$name"
die "fatal: could not initialize repository in '$name'"
fi

if test -n "$parent_remote"
then
git remote add parent "$parent_remote"
else
git remote add parent "$parent_dir"
fi

if test -n "$inherit"
then
git fetch parent || die "fatal: could not fetch from parent"
if test -n "$branch"
then
git checkout -b "$branch" "parent/$branch" ||
die "fatal: could not checkout branch '$branch'"
else
git checkout -b main parent/HEAD 2>/dev/null ||
git checkout -b main "parent/$(git remote show parent | sed -n 's/.*HEAD branch: //p')" 2>/dev/null ||
echo "warning: could not determine parent HEAD, starting empty"
fi
else
echo "# $name" > README.md
git add README.md
git commit -q -m "Initial commit"
fi

echo ""
echo "Created son repository '$name'"
echo " parent: ${parent_remote:-$parent_dir}"
echo " inherit: ${inherit:-no}"
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,7 @@ scripts_sh = [
'git-mergetool.sh',
'git-quiltimport.sh',
'git-request-pull.sh',
'git-son.sh',
'git-sh-i18n.sh',
'git-sh-setup.sh',
'git-submodule.sh',
Expand Down
1 change: 1 addition & 0 deletions t/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ integration_tests = [
't5004-archive-corner-cases.sh',
't5100-mailinfo.sh',
't5150-request-pull.sh',
't5151-son.sh',
't5200-update-server-info.sh',
't5300-pack-object.sh',
't5301-sliding-window.sh',
Expand Down
63 changes: 63 additions & 0 deletions t/t5151-son.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/bin/sh

test_description='Test git son command.'

GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME

. ./test-lib.sh

test_expect_success 'setup parent repository' '
echo "parent content" >file.txt &&
git add file.txt &&
git commit -m "Initial parent commit"
'

test_expect_success 'son creates child repository' '
git son my-child &&
test -d my-child &&
test -d my-child/.git
'

test_expect_success 'son sets parent remote in child' '
(
cd my-child &&
git remote get-url parent
)
'

test_expect_success 'son adds child to parent .gitignore' '
grep "my-child/" .gitignore
'

test_expect_success 'son child has initial commit' '
(
cd my-child &&
test $(git log --oneline | wc -l) -eq 1
)
'

test_expect_success 'son fails if target already exists' '
test_must_fail git son my-child
'

test_expect_success 'son with --branch requires --inherit' '
test_must_fail git son --branch main branch-child
'

test_expect_success 'son with --branch leaves no directory on failure' '
! test -e branch-child
'

test_expect_success 'son with --inherit fetches parent history' '
git init --bare "$TRASH_DIRECTORY/parent.git" &&
git push "$TRASH_DIRECTORY/parent.git" main &&
git remote add origin "file://$TRASH_DIRECTORY/parent.git" &&
git son --inherit inherited-child &&
(
cd inherited-child &&
git log --oneline parent/main
)
'

test_done
Loading