How it works
This page explains what git-nested does inside. You do not need it for daily use. It helps when something goes wrong.
Git call diagrams show the exact git commands of each command.
The picture
flowchart LR
U[(upstream<br/>repository)]
subgraph P [your repository]
F[ext/lib/<br/>files + .gitnested]
end
B[nested branch<br/>nested/ext%2Flib]
U -- clone / pull --> F
F -- rebuild history --> B
B -- push --> U
The nested files live in your repository as normal files. To talk to
upstream, git-nested rebuilds their history as a branch of its own. That
branch looks like upstream: its files are at the top, not under ext/lib/.
The .gitnested file
Each nested folder has a .gitnested file. Its most important fields:
Field |
Meaning |
|---|---|
|
where upstream is |
|
the upstream branch you follow |
|
the upstream commit your files are based on |
|
your commit at which the files and upstream were last equal |
commit and parent together are the sync point. They say: “at my
commit parent, the folder held upstream commit commit”.
All fields are described in
The .gitnested file.
Clone
Fetch the upstream branch.
Put its files into the folder.
Write
.gitnestedwithremote,branch,commitandparent.Make one commit, “git nested clone”.
Rebuilding the nested history
branch, pull and push all start the same way. They build the nested
branch:
Take every commit of yours since
parent.For each one, take only the files of the nested folder. Leave out
.gitnested.Make a new commit from them, on top of the upstream commit in
commit.
The result is a branch whose history starts at upstream and then has your local commits. Your commit messages and authors are kept. A commit that does not touch the folder is skipped.
This is why git-nested needs parent. If parent is lost, for example after
a squash merge, see Squash merges and rebases.
Pull
Fetch the upstream branch into
refs/nested/<subdir>/fetch.Stop if there is nothing new.
Build the nested branch, as above.
Join upstream into it:
merge(default):git mergeof upstream into the nested branch.rebase:git rebaseof your local commits onto upstream.
Put the result into the folder and update
.gitnested.Make one commit, “git nested pull”. Its message lists up to 20 upstream commits.
If step 4 has conflicts, git-nested stops. The nested branch stays checked out in a worktree, and git-nested prints the steps to finish. See Resolve conflicts.
With --no-squash, step 6 makes a merge commit instead. Its second parent is
the upstream history, moved into the folder. So git log shows each
upstream commit.
Push
Pick the target branch upstream (see below).
Fetch it. If it has commits you do not have, stop: pull first.
Build the nested branch, as above. With
rebase, rebase it onto upstream.Push the nested branch to the target branch.
With
--commit, record the pushed commit in.gitnestedand make a commit, “git nested push”.Remove the nested branch again.
Which branch does push use?
You give |
Push goes to |
|---|---|
|
|
nothing, and upstream has the tracked branch |
|
nothing, and upstream does not have the tracked branch yet |
the tracked branch |
So by default a push makes a review branch. You open a pull request upstream, it is merged, and then you pull.
--commit only works for a push to the tracked branch. Otherwise the next
pull would start from a commit the tracked branch does not have.
What does merge or rebase mean here?
The join method only changes the history of the nested branch. The commit in your repository is the same kind either way.
merge keeps the history as it happened. Each pull adds a merge commit to the nested history. A later push sends those merge commits upstream too.
rebase moves your local commits on top of upstream. The pushed history is a straight line. Use it when upstream wants linear history.
Set it once in .gitnested with git nested config <subdir> method rebase,
or per command with --rebase.
Temporary things
While it works, git-nested makes:
the nested branch
nested/<subdir>,a worktree for it under
.git/tmp/nested/<subdir>,refs under
refs/nested/<subdir>/, for examplerefs/nested/ext/lib/fetch,after
branch, a git remote with the name of the nested branch. It points at upstream.
A pull that succeeds removes the worktree and keeps the branch. The next
pull builds it again. A push removes the branch it built. git nested clean
removes branch, worktree and remote by hand.
A / in <subdir> becomes %2F in the branch name. So lib/inner gets
nested/lib%2Finner, which does not clash with nested/lib.
In a linked worktree of your repository, the name gets @<worktree> at the
end, for example nested/lib@wt. So two worktrees do not block each
other.
Clean working tree
Commands that change your repository need a clean working tree: clone,
init, pull, commit, repair and push --commit. Commit or stash your
changes first.
branch, diff and push without --commit only read committed history.
Changes elsewhere do not block them. If the nested folder has uncommitted
changes, they warn and ignore them.
Your hooks
git-nested does not run the hooks of your repository on its own commits. Those hooks are for your changes, not for upstream content. See Hooks and signing to turn them on.