Skip to content

Commit 58fe516

Browse files
author
Junio C Hamano
committed
Merge branch 'jc/merge-subtree'
* jc/merge-subtree: A new merge stragety 'subtree'. It is safe to merge this early as this is a feature that user explicitly needs to ask for and would not trigger otherwise. A known issue with the current implementation is that the subtree matching heuristics is very stupid. It could run ls-tree twice and try to count intersection. Giving it wider audience would help it to get improved by motivated volunteers. Signed-off-by: Junio C Hamano <[email protected]>
2 parents 27be481 + 68faf68 commit 58fe516

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)