Status update on merge-recursive in C

This is just an update for people being interested. Alex and me were
busy with that project for a few days now. While it has progressed nicely,
there are quite a couple TODOs in merge-recursive.c, just search for "TODO".

For impatient people: yes, it passes all the tests, and yes, according
to the evil test Alex did, it is faster than the Python script.

But no, it is not yet finished. Biggest points are:

- there are still three external calls
- in the end, it should not be necessary to write the index more than once
  (just before exiting)
- a lot of things can be refactored to make the code easier and shorter

BTW we cannot just plug in git-merge-tree yet, because git-merge-tree
does not handle renames at all.

This patch is meant for testing, and as such,

- it compile the program to git-merge-recur
- it adjusts the scripts and tests to use git-merge-recur instead of
  git-merge-recursive
- it provides "TEST", a script to execute the tests regarding -recursive
- it inlines the changes to read-cache.c (read_cache_from(), discard_cache()
  and refresh_cache_entry())

Brought to you by Alex Riesen and Dscho

Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
Johannes Schindelin 2006-07-08 18:42:41 +02:00 committed by Junio C Hamano
parent 4b7ce6e2d6
commit 6d297f8137
10 changed files with 1773 additions and 52 deletions

View File

@ -167,7 +167,8 @@ PROGRAMS = \
git-upload-pack$X git-verify-pack$X \ git-upload-pack$X git-verify-pack$X \
git-symbolic-ref$X \ git-symbolic-ref$X \
git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \ git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
git-describe$X git-merge-tree$X git-blame$X git-imap-send$X git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \
git-merge-recur$X
BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \
git-count-objects$X git-diff$X git-push$X git-mailsplit$X \ git-count-objects$X git-diff$X git-push$X git-mailsplit$X \
@ -615,6 +616,11 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
merge-recursive.o path-list.o: path-list.h
git-merge-recur$X: merge-recursive.o path-list.o $(LIB_FILE)
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS)
$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
$(DIFF_OBJS): diffcore.h $(DIFF_OBJS): diffcore.h

10
TEST Executable file
View File

@ -0,0 +1,10 @@
#!/bin/sh -x
cd t || exit
./t3400-rebase.sh "$@" && \
./t6020-merge-df.sh "$@" && \
./t3401-rebase-partial.sh "$@" && \
./t6021-merge-criss-cross.sh "$@" && \
./t3402-rebase-merge.sh "$@" && \
./t6022-merge-rename.sh "$@" && \
./t6010-merge-base.sh "$@" && \
:

View File

@ -115,6 +115,7 @@ static inline unsigned int create_ce_mode(unsigned int mode)
extern struct cache_entry **active_cache; extern struct cache_entry **active_cache;
extern unsigned int active_nr, active_alloc, active_cache_changed; extern unsigned int active_nr, active_alloc, active_cache_changed;
extern struct cache_tree *active_cache_tree; extern struct cache_tree *active_cache_tree;
extern int cache_errno;
#define GIT_DIR_ENVIRONMENT "GIT_DIR" #define GIT_DIR_ENVIRONMENT "GIT_DIR"
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
@ -142,13 +143,16 @@ extern void verify_non_filename(const char *prefix, const char *name);
/* Initialize and use the cache information */ /* Initialize and use the cache information */
extern int read_cache(void); extern int read_cache(void);
extern int read_cache_from(const char *path);
extern int write_cache(int newfd, struct cache_entry **cache, int entries); extern int write_cache(int newfd, struct cache_entry **cache, int entries);
extern int discard_cache(void);
extern int verify_path(const char *path); extern int verify_path(const char *path);
extern int cache_name_pos(const char *name, int namelen); extern int cache_name_pos(const char *name, int namelen);
#define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */
#define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */
#define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */
extern int add_cache_entry(struct cache_entry *ce, int option); extern int add_cache_entry(struct cache_entry *ce, int option);
extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
extern int remove_cache_entry_at(int pos); extern int remove_cache_entry_at(int pos);
extern int remove_file_from_cache(const char *path); extern int remove_file_from_cache(const char *path);
extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);

View File

@ -9,15 +9,15 @@ USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <
LF=' LF='
' '
all_strategies='recursive octopus resolve stupid ours' all_strategies='recur recur octopus resolve stupid ours'
default_twohead_strategies='recursive' default_twohead_strategies='recur'
default_octopus_strategies='octopus' default_octopus_strategies='octopus'
no_trivial_merge_strategies='ours' no_trivial_merge_strategies='ours'
use_strategies= use_strategies=
index_merge=t index_merge=t
if test "@@NO_PYTHON@@"; then if test "@@NO_PYTHON@@"; then
all_strategies='resolve octopus stupid ours' all_strategies='recur resolve octopus stupid ours'
default_twohead_strategies='resolve' default_twohead_strategies='resolve'
fi fi

View File

@ -35,7 +35,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\".
To restore the original branch and stop rebasing run \"git rebase --abort\". To restore the original branch and stop rebasing run \"git rebase --abort\".
" "
unset newbase unset newbase
strategy=recursive strategy=recur
do_merge= do_merge=
dotest=$GIT_DIR/.dotest-merge dotest=$GIT_DIR/.dotest-merge
prec=4 prec=4
@ -292,7 +292,7 @@ then
exit $? exit $?
fi fi
if test "@@NO_PYTHON@@" && test "$strategy" = "recursive" if test "@@NO_PYTHON@@" && test "$strategy" = "recur"
then then
die 'The recursive merge strategy currently relies on Python, die 'The recursive merge strategy currently relies on Python,
which this installation of git was not configured with. Please consider which this installation of git was not configured with. Please consider

1560
merge-recursive.c Normal file

File diff suppressed because it is too large Load Diff

105
path-list.c Normal file
View File

@ -0,0 +1,105 @@
#include <stdio.h>
#include "cache.h"
#include "path-list.h"
/* if there is no exact match, point to the index where the entry could be
* inserted */
static int get_entry_index(const struct path_list *list, const char *path,
int *exact_match)
{
int left = -1, right = list->nr;
while (left + 1 < right) {
int middle = (left + right) / 2;
int compare = strcmp(path, list->items[middle].path);
if (compare < 0)
right = middle;
else if (compare > 0)
left = middle;
else {
*exact_match = 1;
return middle;
}
}
*exact_match = 0;
return right;
}
/* returns -1-index if already exists */
static int add_entry(struct path_list *list, const char *path)
{
int exact_match;
int index = get_entry_index(list, path, &exact_match);
if (exact_match)
return -1 - index;
if (list->nr + 1 >= list->alloc) {
list->alloc += 32;
list->items = xrealloc(list->items, list->alloc
* sizeof(struct path_list_item));
}
if (index < list->nr)
memmove(list->items + index + 1, list->items + index,
(list->nr - index)
* sizeof(struct path_list_item));
list->items[index].path = list->strdup_paths ?
strdup(path) : (char *)path;
list->items[index].util = NULL;
list->nr++;
return index;
}
struct path_list_item *path_list_insert(const char *path, struct path_list *list)
{
int index = add_entry(list, path);
if (index < 0)
index = 1 - index;
return list->items + index;
}
int path_list_has_path(const struct path_list *list, const char *path)
{
int exact_match;
get_entry_index(list, path, &exact_match);
return exact_match;
}
struct path_list_item *path_list_lookup(const char *path, struct path_list *list)
{
int exact_match, i = get_entry_index(list, path, &exact_match);
if (!exact_match)
return NULL;
return list->items + i;
}
void path_list_clear(struct path_list *list, int free_items)
{
if (list->items) {
int i;
if (free_items)
for (i = 0; i < list->nr; i++) {
if (list->strdup_paths)
free(list->items[i].path);
if (list->items[i].util)
free(list->items[i].util);
}
free(list->items);
}
list->items = NULL;
list->nr = list->alloc = 0;
}
void print_path_list(const char *text, const struct path_list *p)
{
int i;
if ( text )
printf("%s\n", text);
for (i = 0; i < p->nr; i++)
printf("%s:%p\n", p->items[i].path, p->items[i].util);
}

22
path-list.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef _PATH_LIST_H_
#define _PATH_LIST_H_
struct path_list_item {
char *path;
void *util;
};
struct path_list
{
struct path_list_item *items;
unsigned int nr, alloc;
unsigned int strdup_paths:1;
};
void print_path_list(const char *text, const struct path_list *p);
int path_list_has_path(const struct path_list *list, const char *path);
void path_list_clear(struct path_list *list, int free_items);
struct path_list_item *path_list_insert(const char *path, struct path_list *list);
struct path_list_item *path_list_lookup(const char *path, struct path_list *list);
#endif /* _PATH_LIST_H_ */

View File

@ -24,6 +24,11 @@ unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0;
struct cache_tree *active_cache_tree = NULL; struct cache_tree *active_cache_tree = NULL;
int cache_errno = 0;
static void *cache_mmap = NULL;
static size_t cache_mmap_size = 0;
/* /*
* This only updates the "non-critical" parts of the directory * This only updates the "non-critical" parts of the directory
* cache, ie the parts that aren't tracked by GIT, and only used * cache, ie the parts that aren't tracked by GIT, and only used
@ -577,22 +582,6 @@ int add_cache_entry(struct cache_entry *ce, int option)
return 0; return 0;
} }
/* Three functions to allow overloaded pointer return; see linux/err.h */
static inline void *ERR_PTR(long error)
{
return (void *) error;
}
static inline long PTR_ERR(const void *ptr)
{
return (long) ptr;
}
static inline long IS_ERR(const void *ptr)
{
return (unsigned long)ptr > (unsigned long)-1000L;
}
/* /*
* "refresh" does not calculate a new sha1 file or bring the * "refresh" does not calculate a new sha1 file or bring the
* cache up-to-date for mode/content changes. But what it * cache up-to-date for mode/content changes. But what it
@ -604,14 +593,16 @@ static inline long IS_ERR(const void *ptr)
* For example, you'd want to do this after doing a "git-read-tree", * For example, you'd want to do this after doing a "git-read-tree",
* to link up the stat cache details with the proper files. * to link up the stat cache details with the proper files.
*/ */
static struct cache_entry *refresh_entry(struct cache_entry *ce, int really) struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
{ {
struct stat st; struct stat st;
struct cache_entry *updated; struct cache_entry *updated;
int changed, size; int changed, size;
if (lstat(ce->name, &st) < 0) if (lstat(ce->name, &st) < 0) {
return ERR_PTR(-errno); cache_errno = errno;
return NULL;
}
changed = ce_match_stat(ce, &st, really); changed = ce_match_stat(ce, &st, really);
if (!changed) { if (!changed) {
@ -619,11 +610,13 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce, int really)
!(ce->ce_flags & htons(CE_VALID))) !(ce->ce_flags & htons(CE_VALID)))
; /* mark this one VALID again */ ; /* mark this one VALID again */
else else
return NULL; return ce;
} }
if (ce_modified(ce, &st, really)) if (ce_modified(ce, &st, really)) {
return ERR_PTR(-EINVAL); cache_errno = EINVAL;
return NULL;
}
size = ce_size(ce); size = ce_size(ce);
updated = xmalloc(size); updated = xmalloc(size);
@ -666,13 +659,13 @@ int refresh_cache(unsigned int flags)
continue; continue;
} }
new = refresh_entry(ce, really); new = refresh_cache_entry(ce, really);
if (!new) if (new == ce)
continue; continue;
if (IS_ERR(new)) { if (!new) {
if (not_new && PTR_ERR(new) == -ENOENT) if (not_new && cache_errno == ENOENT)
continue; continue;
if (really && PTR_ERR(new) == -EINVAL) { if (really && cache_errno == EINVAL) {
/* If we are doing --really-refresh that /* If we are doing --really-refresh that
* means the index is not valid anymore. * means the index is not valid anymore.
*/ */
@ -728,40 +721,44 @@ static int read_index_extension(const char *ext, void *data, unsigned long sz)
} }
int read_cache(void) int read_cache(void)
{
return read_cache_from(get_index_file());
}
/* remember to discard_cache() before reading a different cache! */
int read_cache_from(const char *path)
{ {
int fd, i; int fd, i;
struct stat st; struct stat st;
unsigned long size, offset; unsigned long offset;
void *map;
struct cache_header *hdr; struct cache_header *hdr;
errno = EBUSY; errno = EBUSY;
if (active_cache) if (cache_mmap)
return active_nr; return active_nr;
errno = ENOENT; errno = ENOENT;
index_file_timestamp = 0; index_file_timestamp = 0;
fd = open(get_index_file(), O_RDONLY); fd = open(path, O_RDONLY);
if (fd < 0) { if (fd < 0) {
if (errno == ENOENT) if (errno == ENOENT)
return 0; return 0;
die("index file open failed (%s)", strerror(errno)); die("index file open failed (%s)", strerror(errno));
} }
size = 0; /* avoid gcc warning */ cache_mmap = MAP_FAILED;
map = MAP_FAILED;
if (!fstat(fd, &st)) { if (!fstat(fd, &st)) {
size = st.st_size; cache_mmap_size = st.st_size;
errno = EINVAL; errno = EINVAL;
if (size >= sizeof(struct cache_header) + 20) if (cache_mmap_size >= sizeof(struct cache_header) + 20)
map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); cache_mmap = mmap(NULL, cache_mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
} }
close(fd); close(fd);
if (map == MAP_FAILED) if (cache_mmap == MAP_FAILED)
die("index file mmap failed (%s)", strerror(errno)); die("index file mmap failed (%s)", strerror(errno));
hdr = map; hdr = cache_mmap;
if (verify_hdr(hdr, size) < 0) if (verify_hdr(hdr, cache_mmap_size) < 0)
goto unmap; goto unmap;
active_nr = ntohl(hdr->hdr_entries); active_nr = ntohl(hdr->hdr_entries);
@ -770,12 +767,12 @@ int read_cache(void)
offset = sizeof(*hdr); offset = sizeof(*hdr);
for (i = 0; i < active_nr; i++) { for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = (struct cache_entry *) ((char *) map + offset); struct cache_entry *ce = (struct cache_entry *) ((char *) cache_mmap + offset);
offset = offset + ce_size(ce); offset = offset + ce_size(ce);
active_cache[i] = ce; active_cache[i] = ce;
} }
index_file_timestamp = st.st_mtime; index_file_timestamp = st.st_mtime;
while (offset <= size - 20 - 8) { while (offset <= cache_mmap_size - 20 - 8) {
/* After an array of active_nr index entries, /* After an array of active_nr index entries,
* there can be arbitrary number of extended * there can be arbitrary number of extended
* sections, each of which is prefixed with * sections, each of which is prefixed with
@ -783,10 +780,10 @@ int read_cache(void)
* in 4-byte network byte order. * in 4-byte network byte order.
*/ */
unsigned long extsize; unsigned long extsize;
memcpy(&extsize, (char *) map + offset + 4, 4); memcpy(&extsize, (char *) cache_mmap + offset + 4, 4);
extsize = ntohl(extsize); extsize = ntohl(extsize);
if (read_index_extension(((const char *) map) + offset, if (read_index_extension(((const char *) cache_mmap) + offset,
(char *) map + offset + 8, (char *) cache_mmap + offset + 8,
extsize) < 0) extsize) < 0)
goto unmap; goto unmap;
offset += 8; offset += 8;
@ -795,11 +792,28 @@ int read_cache(void)
return active_nr; return active_nr;
unmap: unmap:
munmap(map, size); munmap(cache_mmap, cache_mmap_size);
errno = EINVAL; errno = EINVAL;
die("index file corrupt"); die("index file corrupt");
} }
int discard_cache()
{
int ret;
if (cache_mmap == NULL)
return 0;
ret = munmap(cache_mmap, cache_mmap_size);
cache_mmap = NULL;
cache_mmap_size = 0;
active_nr = active_cache_changed = 0;
index_file_timestamp = 0;
cache_tree_free(&active_cache_tree);
/* no need to throw away allocated active_cache */
return ret;
}
#define WRITE_BUFFER_SIZE 8192 #define WRITE_BUFFER_SIZE 8192
static unsigned char write_buffer[WRITE_BUFFER_SIZE]; static unsigned char write_buffer[WRITE_BUFFER_SIZE];
static unsigned long write_buffer_len; static unsigned long write_buffer_len;

View File

@ -51,7 +51,7 @@ test_expect_success setup '
' '
test_expect_success 'reference merge' ' test_expect_success 'reference merge' '
git merge -s recursive "reference merge" HEAD master git merge -s recur "reference merge" HEAD master
' '
test_expect_success rebase ' test_expect_success rebase '