3 # "set -e" isn't active inside command substitutions unless we use POSIX mode
4 # (which I don't want to do right now) or inherit_errexit (which would introduce
5 # a dependency on bash >= 4.4). So implement our own version for now.
10 USAGE="add <subtree_path> <upstream_commit>
11 or: git subtree-lite update <subtree_path> <upstream_commit>
12 or: git subtree-lite diff [<diff_option>...] <subtree_path>
13 or: git subtree-lite import <subtree_path> [<upstream_commit>]"
15 read -r -d '' LONG_USAGE <<'LONG_USAGE' || true
16 "git subtree-lite" imports the content of another repository (called "upstream")
17 as of a particular commit into a subtree of your repository. It remembers the
18 commit ID that was imported in an ".upstream" file in the subtree, so if you
19 modify the subtree and then import a different upstream commit, the changes will
20 be merged. You can use "git subtree-lite" to bundle libraries, as an
21 alternative to "git submodule" or "git subtree".
23 Semantically, a subtree managed with this tool is equivalent to a submodule
24 pointer with a layer of project-specific modifications. Like "git subtree" and
25 unlike a git submodule, the main project's version of the the content is in the
26 main tree, so no special handling is needed to read or write it. But like a git
27 submodule and unlike "git subtree", the upstream commit pointer is just data
28 that can be merged and reverted, and this tool doesn't clutter the main project
29 history with extra merge commits. The upstream commit pointer doesn't enjoy any
30 of the special tool support of submodules, but most of it isn't relevant with
31 the content in the main tree (an exception might be
32 {fetch,push}.recurseSubmodules for developers who update or diff the subtree).
34 Operations that access the upstream repository require that you have a local
35 copy of it and set the "subtree-lite.<subtree_path>.repo" configuration option
36 to its path. The path may be absolute or relative to the .git directory of the
37 current repository (the common directory if you use "git worktree"). This
38 mechanism is subject to change and may be made more sophisticated and automated
39 (like submodules) in the future.
42 http://marc.info/?l=git&m=147752326122139&w=2
44 git subtree-lite add <subtree_path> <upstream_commit>
45 Add a subtree from the given upstream commit. Follow the directions to
46 configure the path to the upstream repository.
48 git subtree-lite update <subtree_path> <upstream_commit>
49 Update the subtree to be based on the given upstream commit.
51 (To remove a subtree, just use "git rm -r".)
53 git subtree-lite diff [<diff_option>...] <subtree_path>
54 Diff the subtree against the original upstream content. Diff options are
55 accepted, but paths to limit the diff currently are not supported.
57 git subtree-lite import <subtree_path> [<upstream_commit>]
58 Low-level command: generate the "imported commit" corresponding to the given
59 upstream commit (with the content moved to the subtree and the .upstream
60 file added) and print its ID. Only the tree of this commit is meaningful.
64 # Let's simplify matters for now and not allow running in a subdirectory.
65 . "$(git --exec-path)/git-sh-setup"
72 tmpdir="$(mktemp --tmpdir -d git-subtree-lite.XXXXXXXXXX)"
75 function ensure_init_tmp_repo {
76 if [ -z "$tmp_repo" ]; then
77 tmp_repo="$tmpdir/repo"
78 git init --quiet --bare "$tmp_repo"
79 (cd "$subtree_repo" && readlink --canonicalize "$(git rev-parse --git-path objects)") >"$tmp_repo/objects/info/alternates"
83 function setup_subtree {
84 # XXX Introduce a name like submodules have? Either abuse .gitmodules and
85 # call "git submodule--helper name", or reimplement the lookup?
86 local opt_name="subtree-lite.$subtree_path.repo"
87 local common_dir="$(git rev-parse --git-common-dir)"
88 subtree_repo="$(git config "$opt_name")" || die "Please get a local copy of the upstream repository and run:
90 git config $opt_name <path_to_upstream_repository>
92 before using this tool. The path may be absolute or relative to
93 $(readlink --canonicalize "$common_dir") ."
94 subtree_repo="$(cd "$common_dir" && readlink --canonicalize "$subtree_repo")"
96 upstream_file="$subtree_path/.upstream"
97 if [ -f "$upstream_file" ]; then
98 cur_upstream_commit="$(< "$upstream_file")"
100 cur_upstream_commit=""
104 function reproducible_commit_tree {
105 GIT_AUTHOR_NAME='git-subtree-lite' \
106 GIT_AUTHOR_EMAIL='git-subtree-lite@invalid' \
107 GIT_AUTHOR_DATE='@0 +0000' \
108 GIT_COMMITTER_NAME='git-subtree-lite' \
109 GIT_COMMITTER_EMAIL='git-subtree-lite@invalid' \
110 GIT_COMMITTER_DATE='@0 +0000' \
114 function canonicalize_upstream_commit {
115 (cd "$subtree_repo" && git rev-parse --verify "$1^{commit}")
118 # TODO: We should cache this, but even if we don't stop git from GC-ing the
119 # underlying commits, how to stop the mapping from growing indefinitely?
120 function import_commit {
126 git read-tree -i --prefix="$subtree_path/" "$commit"
127 # Hm, I suppose this would be a great place to add a way to exclude files
128 # the superproject doesn't care about. But not now.
130 # Create the .upstream file. If we do it here, then the right thing ends up
131 # happening during both add/update and diff without any more code.
132 uf_blob="$(echo "$commit" | git hash-object -t blob -w --stdin)"
133 git update-index --add --cacheinfo "100644,$uf_blob,$subtree_path/.upstream"
134 t="$(git write-tree)"
135 # Reuse the same object for the same upstream commit until the repository is
137 c="$(reproducible_commit_tree -m "git-subtree-lite temporary commit" "$t")"
138 git update-ref HEAD "$c"
140 # XXX: The incremental fetch protocol is only based on detection of common
141 # commits, so unless we already have the exact same imported commit, this
142 # fetch will send the entire tree. If we cache previous imported commits in
143 # the main repository, then we can add it as an alternate of the temporary
144 # repository (!) and the previous imported commits will be detected as common.
145 git fetch --quiet "$tmp_repo" HEAD 2>&1 | { grep -v --line-regexp 'warning: no common commits' >&2 || true; }
146 git rev-parse FETCH_HEAD # to stdout
152 new_upstream_commit_expr="$2"
155 ! [ -e "$subtree_path" ] || die "Error: $subtree_path already exists on the filesystem."
156 ! [ -n "$(git ls-files "$subtree_path")" ] || die "Error: $subtree_path already exists in the git index."
158 new_upstream_commit="$(canonicalize_upstream_commit "$new_upstream_commit_expr")"
159 new_imported_commit="$(import_commit "$new_upstream_commit")"
160 git read-tree --prefix= -u "$new_imported_commit"
163 function cmd_update {
166 new_upstream_commit_expr="$2"
169 [ -n "$cur_upstream_commit" ] || die "Error: $subtree_path is not a subtree set up with this tool."
171 new_upstream_commit="$(canonicalize_upstream_commit "$new_upstream_commit_expr")"
172 new_imported_commit="$(import_commit "$new_upstream_commit")"
173 old_imported_commit="$(import_commit "$cur_upstream_commit")"
174 tree2="$(git write-tree)"
175 # Note: the .upstream file is already in the imported commits.
176 git read-tree -mu "$old_imported_commit" "$tree2" "$new_imported_commit"
177 git merge-index -o git-merge-one-file -a
180 function cmd_import {
181 [ $# -ge 1 ] && [ $# -le 2 ] || usage
185 new_upstream_commit="$(canonicalize_upstream_commit "$2")"
186 elif [ -n "$cur_upstream_commit" ]; then
187 new_upstream_commit="$cur_upstream_commit"
189 die "Error: $subtree_path is not added yet and no upstream commit was given."
191 import_commit "$new_upstream_commit"
195 [ $# -ge 1 ] || usage
196 subtree_path="${@: -1:1}"
198 # This could be expensive, but it's the only way to honor uncommitted changes
199 # (and --cached). Even if we added the subtree repo to
200 # GIT_ALTERNATE_OBJECT_DIRECTORIES, git has no way to diff a subtree of the
201 # worktree against the root of a given commit.
202 cur_imported_commit="$(import_commit "$cur_upstream_commit")"
203 git diff "${@:1:$#-1}" "$cur_imported_commit" -- "$subtree_path"
206 [ $# -ge 1 ] || usage
218 (*) die "Unknown command $cmd.";;