Skip to content

Commit 68faf68

Browse files
author
Junio C Hamano
committed
A new merge stragety 'subtree'.
This merge strategy largely piggy-backs on git-merge-recursive. When merging trees A and B, if B corresponds to a subtree of A, B is first adjusted to match the tree structure of A, instead of reading the trees at the same level. This adjustment is also done to the common ancestor tree. If you are pulling updates from git-gui repository into git.git repository, the root level of the former corresponds to git-gui/ subdirectory of the latter. The tree object of git-gui's toplevel is wrapped in a fake tree object, whose sole entry has name 'git-gui' and records object name of the true tree, before being used by the 3-way merge code. If you are merging the other way, only the git-gui/ subtree of git.git is extracted and merged into git-gui's toplevel. The detection of corresponding subtree is done by comparing the pathnames and types in the toplevel of the tree. Heuristics galore! That's the git way ;-). Signed-off-by: Junio C Hamano <[email protected]>
1 parent ee9693e commit 68faf68

File tree

7 files changed

+373
-3
lines changed

7 files changed

+373
-3
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ git-merge-ours
7777
git-merge-recursive
7878
git-merge-resolve
7979
git-merge-stupid
80+
git-merge-subtree
8081
git-mergetool
8182
git-mktag
8283
git-mktree
@@ -148,6 +149,7 @@ test-chmtime
148149
test-date
149150
test-delta
150151
test-dump-cache-tree
152+
test-match-trees
151153
common-cmds.h
152154
*.tar.gz
153155
*.dsc

Makefile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ BUILT_INS = \
251251
# what 'all' will build and 'install' will install, in gitexecdir
252252
ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
253253

254+
ALL_PROGRAMS += git-merge-subtree$X
255+
254256
# what 'all' will build but not install in gitexecdir
255257
OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
256258
ifndef NO_TCLTK
@@ -299,7 +301,7 @@ LIB_OBJS = \
299301
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
300302
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
301303
revision.o pager.o tree-walk.o xdiff-interface.o \
302-
write_or_die.o trace.o list-objects.o grep.o \
304+
write_or_die.o trace.o list-objects.o grep.o match-trees.o \
303305
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
304306
color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
305307
convert.o
@@ -725,6 +727,9 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS
725727

726728
help.o: common-cmds.h
727729

730+
git-merge-subtree$X: git-merge-recursive$X
731+
rm -f $@ && ln git-merge-recursive$X $@
732+
728733
$(BUILT_INS): git$X
729734
$(QUIET_BUILT_IN)rm -f $@ && ln git$X $@
730735

@@ -942,6 +947,9 @@ test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
942947
test-sha1$X: test-sha1.o $(GITLIBS)
943948
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
944949

950+
test-match-trees$X: test-match-trees.o $(GITLIBS)
951+
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
952+
945953
test-chmtime$X: test-chmtime.c
946954
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
947955

cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,7 @@ extern void trace_argv_printf(const char **argv, int count, const char *format,
496496
extern int convert_to_git(const char *path, char **bufp, unsigned long *sizep);
497497
extern int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep);
498498

499+
/* match-trees.c */
500+
void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
501+
499502
#endif /* CACHE_H */

git-merge.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ test -z "$(git ls-files -u)" ||
1616
LF='
1717
'
1818

19-
all_strategies='recur recursive octopus resolve stupid ours'
19+
all_strategies='recur recursive octopus resolve stupid ours subtree'
2020
default_twohead_strategies='recursive'
2121
default_octopus_strategies='octopus'
22-
no_trivial_merge_strategies='ours'
22+
no_trivial_merge_strategies='ours subtree'
2323
use_strategies=
2424

2525
index_merge=t

match-trees.c

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
#include "cache.h"
2+
#include "tree.h"
3+
#include "tree-walk.h"
4+
5+
static int score_missing(unsigned mode, const char *path)
6+
{
7+
int score;
8+
9+
if (S_ISDIR(mode))
10+
score = -1000;
11+
else if (S_ISLNK(mode))
12+
score = -500;
13+
else
14+
score = -50;
15+
return score;
16+
}
17+
18+
static int score_differs(unsigned mode1, unsigned mode2, const char *path)
19+
{
20+
int score;
21+
22+
if (S_ISDIR(mode1) != S_ISDIR(mode2))
23+
score = -100;
24+
else if (S_ISLNK(mode1) != S_ISLNK(mode2))
25+
score = -50;
26+
else
27+
score = -5;
28+
return score;
29+
}
30+
31+
static int score_matches(unsigned mode1, unsigned mode2, const char *path)
32+
{
33+
int score;
34+
35+
/* Heh, we found SHA-1 collisions between different kind of objects */
36+
if (S_ISDIR(mode1) != S_ISDIR(mode2))
37+
score = -100;
38+
else if (S_ISLNK(mode1) != S_ISLNK(mode2))
39+
score = -50;
40+
41+
else if (S_ISDIR(mode1))
42+
score = 1000;
43+
else if (S_ISLNK(mode1))
44+
score = 500;
45+
else
46+
score = 250;
47+
return score;
48+
}
49+
50+
/*
51+
* Inspect two trees, and give a score that tells how similar they are.
52+
*/
53+
static int score_trees(const unsigned char *hash1, const unsigned char *hash2)
54+
{
55+
struct tree_desc one;
56+
struct tree_desc two;
57+
void *one_buf, *two_buf;
58+
int score = 0;
59+
enum object_type type;
60+
unsigned long size;
61+
62+
one_buf = read_sha1_file(hash1, &type, &size);
63+
if (!one_buf)
64+
die("unable to read tree (%s)", sha1_to_hex(hash1));
65+
if (type != OBJ_TREE)
66+
die("%s is not a tree", sha1_to_hex(hash1));
67+
init_tree_desc(&one, one_buf, size);
68+
two_buf = read_sha1_file(hash2, &type, &size);
69+
if (!two_buf)
70+
die("unable to read tree (%s)", sha1_to_hex(hash2));
71+
if (type != OBJ_TREE)
72+
die("%s is not a tree", sha1_to_hex(hash2));
73+
init_tree_desc(&two, two_buf, size);
74+
while (one.size | two.size) {
75+
const unsigned char *elem1 = elem1;
76+
const unsigned char *elem2 = elem2;
77+
const char *path1 = path1;
78+
const char *path2 = path2;
79+
unsigned mode1 = mode1;
80+
unsigned mode2 = mode2;
81+
int cmp;
82+
83+
if (one.size)
84+
elem1 = tree_entry_extract(&one, &path1, &mode1);
85+
if (two.size)
86+
elem2 = tree_entry_extract(&two, &path2, &mode2);
87+
88+
if (!one.size) {
89+
/* two has more entries */
90+
score += score_missing(mode2, path2);
91+
update_tree_entry(&two);
92+
continue;
93+
}
94+
if (!two.size) {
95+
/* two lacks this entry */
96+
score += score_missing(mode1, path1);
97+
update_tree_entry(&one);
98+
continue;
99+
}
100+
cmp = base_name_compare(path1, strlen(path1), mode1,
101+
path2, strlen(path2), mode2);
102+
if (cmp < 0) {
103+
/* path1 does not appear in two */
104+
score += score_missing(mode1, path1);
105+
update_tree_entry(&one);
106+
continue;
107+
}
108+
else if (cmp > 0) {
109+
/* path2 does not appear in one */
110+
score += score_missing(mode2, path2);
111+
update_tree_entry(&two);
112+
continue;
113+
}
114+
else if (hashcmp(elem1, elem2))
115+
/* they are different */
116+
score += score_differs(mode1, mode2, path1);
117+
else
118+
/* same subtree or blob */
119+
score += score_matches(mode1, mode2, path1);
120+
update_tree_entry(&one);
121+
update_tree_entry(&two);
122+
}
123+
free(one_buf);
124+
free(two_buf);
125+
return score;
126+
}
127+
128+
/*
129+
* Match one itself and its subtrees with two and pick the best match.
130+
*/
131+
static void match_trees(const unsigned char *hash1,
132+
const unsigned char *hash2,
133+
int *best_score,
134+
char **best_match,
135+
char *base,
136+
int recurse_limit)
137+
{
138+
struct tree_desc one;
139+
void *one_buf;
140+
enum object_type type;
141+
unsigned long size;
142+
143+
one_buf = read_sha1_file(hash1, &type, &size);
144+
if (!one_buf)
145+
die("unable to read tree (%s)", sha1_to_hex(hash1));
146+
if (type != OBJ_TREE)
147+
die("%s is not a tree", sha1_to_hex(hash1));
148+
init_tree_desc(&one, one_buf, size);
149+
150+
while (one.size) {
151+
const char *path;
152+
const unsigned char *elem;
153+
unsigned mode;
154+
int score;
155+
156+
elem = tree_entry_extract(&one, &path, &mode);
157+
if (!S_ISDIR(mode))
158+
goto next;
159+
score = score_trees(elem, hash2);
160+
if (*best_score < score) {
161+
char *newpath;
162+
newpath = xmalloc(strlen(base) + strlen(path) + 1);
163+
sprintf(newpath, "%s%s", base, path);
164+
free(*best_match);
165+
*best_match = newpath;
166+
*best_score = score;
167+
}
168+
if (recurse_limit) {
169+
char *newbase;
170+
newbase = xmalloc(strlen(base) + strlen(path) + 2);
171+
sprintf(newbase, "%s%s/", base, path);
172+
match_trees(elem, hash2, best_score, best_match,
173+
newbase, recurse_limit - 1);
174+
free(newbase);
175+
}
176+
177+
next:
178+
update_tree_entry(&one);
179+
}
180+
free(one_buf);
181+
}
182+
183+
/*
184+
* A tree "hash1" has a subdirectory at "prefix". Come up with a
185+
* tree object by replacing it with another tree "hash2".
186+
*/
187+
static int splice_tree(const unsigned char *hash1,
188+
char *prefix,
189+
const unsigned char *hash2,
190+
unsigned char *result)
191+
{
192+
char *subpath;
193+
int toplen;
194+
char *buf;
195+
unsigned long sz;
196+
struct tree_desc desc;
197+
unsigned char *rewrite_here;
198+
const unsigned char *rewrite_with;
199+
unsigned char subtree[20];
200+
enum object_type type;
201+
int status;
202+
203+
subpath = strchr(prefix, '/');
204+
if (!subpath)
205+
toplen = strlen(prefix);
206+
else {
207+
toplen = subpath - prefix;
208+
subpath++;
209+
}
210+
211+
buf = read_sha1_file(hash1, &type, &sz);
212+
if (!buf)
213+
die("cannot read tree %s", sha1_to_hex(hash1));
214+
init_tree_desc(&desc, buf, sz);
215+
216+
rewrite_here = NULL;
217+
while (desc.size) {
218+
const char *name;
219+
unsigned mode;
220+
const unsigned char *sha1;
221+
222+
sha1 = tree_entry_extract(&desc, &name, &mode);
223+
if (strlen(name) == toplen &&
224+
!memcmp(name, prefix, toplen)) {
225+
if (!S_ISDIR(mode))
226+
die("entry %s in tree %s is not a tree",
227+
name, sha1_to_hex(hash1));
228+
rewrite_here = (unsigned char *) sha1;
229+
break;
230+
}
231+
update_tree_entry(&desc);
232+
}
233+
if (!rewrite_here)
234+
die("entry %.*s not found in tree %s",
235+
toplen, prefix, sha1_to_hex(hash1));
236+
if (subpath) {
237+
status = splice_tree(rewrite_here, subpath, hash2, subtree);
238+
if (status)
239+
return status;
240+
rewrite_with = subtree;
241+
}
242+
else
243+
rewrite_with = hash2;
244+
hashcpy(rewrite_here, rewrite_with);
245+
status = write_sha1_file(buf, sz, tree_type, result);
246+
free(buf);
247+
return status;
248+
}
249+
250+
/*
251+
* We are trying to come up with a merge between one and two that
252+
* results in a tree shape similar to one. The tree two might
253+
* correspond to a subtree of one, in which case it needs to be
254+
* shifted down by prefixing otherwise empty directories. On the
255+
* other hand, it could cover tree one and we might need to pick a
256+
* subtree of it.
257+
*/
258+
void shift_tree(const unsigned char *hash1,
259+
const unsigned char *hash2,
260+
unsigned char *shifted,
261+
int depth_limit)
262+
{
263+
char *add_prefix;
264+
char *del_prefix;
265+
int add_score, del_score;
266+
267+
add_score = del_score = score_trees(hash1, hash2);
268+
add_prefix = xcalloc(1, 1);
269+
del_prefix = xcalloc(1, 1);
270+
271+
/*
272+
* See if one's subtree resembles two; if so we need to prefix
273+
* two with a few fake trees to match the prefix.
274+
*/
275+
match_trees(hash1, hash2, &add_score, &add_prefix, "", depth_limit);
276+
277+
/*
278+
* See if two's subtree resembles one; if so we need to
279+
* pick only subtree of two.
280+
*/
281+
match_trees(hash2, hash1, &del_score, &del_prefix, "", depth_limit);
282+
283+
/* Assume we do not have to do any shifting */
284+
hashcpy(shifted, hash2);
285+
286+
if (add_score < del_score) {
287+
/* We need to pick a subtree of two */
288+
unsigned mode;
289+
290+
if (!*del_prefix)
291+
return;
292+
293+
if (get_tree_entry(hash2, del_prefix, shifted, &mode))
294+
die("cannot find path %s in tree %s",
295+
del_prefix, sha1_to_hex(hash2));
296+
return;
297+
}
298+
299+
if (!*add_prefix)
300+
return;
301+
302+
splice_tree(hash1, add_prefix, hash2, shifted);
303+
}
304+

0 commit comments

Comments
 (0)