Browse Source

Merge with master.kernel.org:/pub/scm/git/git.git

maint
Peter Anvin 20 years ago
parent
commit
79a9d8ea0d
  1. 1
      .gitignore
  2. 15
      Documentation/git-clone.txt
  3. 9
      Documentation/git-ls-files.txt
  4. 67
      Makefile
  5. 13
      apply.c
  6. 5
      cache.h
  7. 6
      debian/changelog
  8. 1
      diff-files.c
  9. 3
      diff.c
  10. 28
      fsck-objects.c
  11. 8
      git-archimport.perl
  12. 7
      git-bisect.sh
  13. 6
      git-branch.sh
  14. 3
      git-checkout.sh
  15. 17
      git-commit.sh
  16. 18
      git-cvsimport.perl
  17. 25
      git-fetch.sh
  18. 786
      git-merge-recursive.py
  19. 22
      git-merge.sh
  20. 7
      git-sh-setup.sh
  21. 6
      git-status.sh
  22. 55
      git.sh
  23. 192
      http-fetch.c
  24. 2
      index.c
  25. 10
      init-db.c
  26. 6
      ls-files.c
  27. 2
      mailsplit.c
  28. 14
      read-cache.c
  29. 18
      read-tree.c
  30. 187
      refs.c
  31. 16
      setup.c
  32. 2
      sha1_file.c
  33. 17
      sha1_name.c
  34. 13
      show-branch.c
  35. 34
      symbolic-ref.c
  36. 1
      t/Makefile
  37. 69
      t/t3002-ls-files-dashpath.sh
  38. 6
      t/t5000-tar-tree.sh
  39. 6
      t/t5400-send-pack.sh
  40. 29
      t/t6002-rev-list-bisect.sh
  41. 2
      tar-tree.c
  42. 24
      update-index.c
  43. 49
      update-ref.c
  44. 4
      usage.c

1
.gitignore vendored

@ -82,6 +82,7 @@ git-ssh-push @@ -82,6 +82,7 @@ git-ssh-push
git-ssh-upload
git-status
git-stripspace
git-symbolic-ref
git-tag
git-tar-tree
git-unpack-file

15
Documentation/git-clone.txt

@ -9,7 +9,7 @@ git-clone - Clones a repository. @@ -9,7 +9,7 @@ git-clone - Clones a repository.

SYNOPSIS
--------
'git clone' [-l] [-u <upload-pack>] [-q] <repository> <directory>
'git clone' [-l [-s]] [-q] [-n] [-u <upload-pack>] <repository> <directory>

DESCRIPTION
-----------
@ -17,6 +17,7 @@ Clones a repository into a newly created directory. @@ -17,6 +17,7 @@ Clones a repository into a newly created directory.

OPTIONS
-------
--local::
-l::
When the repository to clone from is on a local machine,
this flag bypasses normal "git aware" transport
@ -25,10 +26,22 @@ OPTIONS @@ -25,10 +26,22 @@ OPTIONS
The files under .git/objects/ directory are hardlinked
to save space when possible.

--shared::
-s::
When the repository to clone is on the local machine,
instead of using hard links automatically setup
.git/objects/info/alternatives to share the objects
with the source repository

--quiet::
-q::
Operate quietly. This flag is passed to "rsync" and
"git-clone-pack" commands when given.

-n::
No checkout of HEAD is performed after the clone is complete.

--upload-pack <upload-pack>::
-u <upload-pack>::
When given, and the repository to clone from is handled
by 'git-clone-pack', '--exec=<upload-pack>' is passed to

9
Documentation/git-ls-files.txt

@ -13,7 +13,7 @@ SYNOPSIS @@ -13,7 +13,7 @@ SYNOPSIS
(-[c|d|o|i|s|u|k|m])\*
[-x <pattern>|--exclude=<pattern>]
[-X <file>|--exclude-from=<file>]
[--exclude-per-directory=<file>]
[--exclude-per-directory=<file>] [--] [<file>]\*

DESCRIPTION
-----------
@ -77,6 +77,13 @@ OPTIONS @@ -77,6 +77,13 @@ OPTIONS
K to be killed
? other

--::
Do not interpret any more arguments as options.

<file>::
Files to show. If no files are given all files which match the other
specified criteria are shown.

Output
------
show files just outputs the filename unless '--stage' is specified in

67
Makefile

@ -50,7 +50,7 @@ @@ -50,7 +50,7 @@

# DEFINES += -DUSE_STDEV

GIT_VERSION = 0.99.7.GIT
GIT_VERSION = 0.99.8.GIT

CFLAGS = -g -O2 -Wall
ALL_CFLAGS = $(CFLAGS) $(PLATFORM_DEFINES) $(DEFINES)
@ -103,25 +103,29 @@ SIMPLE_PROGRAMS = \ @@ -103,25 +103,29 @@ SIMPLE_PROGRAMS = \

# ... and all the rest
PROGRAMS = \
git-apply$X git-cat-file$X git-checkout-index$X \
git-clone-pack$X git-commit-tree$X git-convert-objects$X \
git-diff-files$X git-diff-index$X git-diff-stages$X \
git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
git-hash-object$X git-init-db$X git-local-fetch$X \
git-ls-files$X git-ls-tree$X git-merge-base$X \
git-merge-index$X git-mktag$X git-pack-objects$X \
git-patch-id$X git-peek-remote$X git-prune-packed$X \
git-read-tree$X git-receive-pack$X git-rev-list$X \
git-rev-parse$X git-send-pack$X git-show-branch$X \
git-show-index$X git-ssh-fetch$X git-ssh-upload$X \
git-tar-tree$X git-unpack-file$X git-unpack-objects$X \
git-update-index$X git-update-server-info$X \
git-upload-pack$X git-verify-pack$X git-write-tree$X \
git-update-ref$X $(SIMPLE_PROGRAMS)
git-apply$X git-cat-file$X \
git-checkout-index$X git-clone-pack$X git-commit-tree$X \
git-convert-objects$X git-diff-files$X \
git-diff-index$X git-diff-stages$X \
git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
git-hash-object$X git-init-db$X \
git-local-fetch$X git-ls-files$X git-ls-tree$X git-merge-base$X \
git-merge-index$X git-mktag$X git-pack-objects$X git-patch-id$X \
git-peek-remote$X git-prune-packed$X git-read-tree$X \
git-receive-pack$X git-rev-list$X git-rev-parse$X \
git-send-pack$X git-show-branch$X \
git-show-index$X git-ssh-fetch$X \
git-ssh-upload$X git-tar-tree$X git-unpack-file$X \
git-unpack-objects$X git-update-index$X git-update-server-info$X \
git-upload-pack$X git-verify-pack$X git-write-tree$X \
git-update-ref$X git-symbolic-ref$X \
$(SIMPLE_PROGRAMS)

# Backward compatibility -- to be removed after 1.0
PROGRAMS += git-ssh-pull$X git-ssh-push$X

GIT_LIST_TWEAK =

PYMODULES = \
gitMergeCommon.py

@ -131,6 +135,8 @@ endif @@ -131,6 +135,8 @@ endif

ifdef WITH_SEND_EMAIL
SCRIPT_PERL += git-send-email.perl
else
GIT_LIST_TWEAK += -e '/^send-email$$/d'
endif

LIB_FILE=libgit.a
@ -181,6 +187,10 @@ endif @@ -181,6 +187,10 @@ endif
ifneq (,$(findstring arm,$(shell uname -m)))
ARM_SHA1 = YesPlease
endif
ifeq ($(shell uname -s),OpenBSD)
NEEDS_LIBICONV = YesPlease
PLATFORM_DEFINES += -I/usr/local/include -L/usr/local/lib
endif

ifndef NO_CURL
ifdef CURLDIR
@ -206,18 +216,32 @@ endif @@ -206,18 +216,32 @@ endif
ifndef NO_OPENSSL
LIB_OBJS += epoch.o
OPENSSL_LIBSSL = -lssl
ifdef OPENSSLDIR
# Again this may be problematic -- gcc does not always want -R.
CFLAGS += -I$(OPENSSLDIR)/include
OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
else
OPENSSL_LINK =
endif
else
DEFINES += '-DNO_OPENSSL'
MOZILLA_SHA1 = 1
OPENSSL_LIBSSL =
endif
ifdef NEEDS_SSL_WITH_CRYPTO
LIB_4_CRYPTO = -lcrypto -lssl
LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl
else
LIB_4_CRYPTO = -lcrypto
LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto
endif
ifdef NEEDS_LIBICONV
LIB_4_ICONV = -liconv
ifdef ICONVDIR
# Again this may be problematic -- gcc does not always want -R.
CFLAGS += -I$(ICONVDIR)/include
ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
else
ICONV_LINK =
endif
LIB_4_ICONV = $(ICONV_LINK) -liconv
else
LIB_4_ICONV =
endif
@ -273,8 +297,13 @@ all: @@ -273,8 +297,13 @@ all:
git: git.sh Makefile
rm -f $@+ $@
sed -e '1s|#!.*/sh|#!$(SHELL_PATH)|' \
<<<<<<< Makefile
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-e 's/@@X@@/$(X)/g' <$@.sh >$@+
=======
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
$(GIT_LIST_TWEAK) <$@.sh >$@+
>>>>>>> .merge_file_3QHyD4
chmod +x $@+
mv $@+ $@


13
apply.c

@ -723,6 +723,16 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc @@ -723,6 +723,16 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
return offset;
}

static inline int metadata_changes(struct patch *patch)
{
return patch->is_rename > 0 ||
patch->is_copy > 0 ||
patch->is_new > 0 ||
patch->is_delete ||
(patch->old_mode && patch->new_mode &&
patch->old_mode != patch->new_mode);
}

static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
{
int hdrsize, patchsize;
@ -733,6 +743,9 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) @@ -733,6 +743,9 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)

patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch);

if (!patchsize && !metadata_changes(patch))
die("patch with only garbage at line %d", linenr);

return offset + hdrsize + patchsize;
}


5
cache.h

@ -189,6 +189,7 @@ extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2) @@ -189,6 +189,7 @@ extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)
extern char *sha1_file_name(const unsigned char *sha1);
extern char *sha1_pack_name(const unsigned char *sha1);
extern char *sha1_pack_index_name(const unsigned char *sha1);
extern const unsigned char null_sha1[20];

int git_mkstemp(char *path, size_t n, const char *template);

@ -228,6 +229,10 @@ extern int has_pack_index(const unsigned char *sha1); @@ -228,6 +229,10 @@ extern int has_pack_index(const unsigned char *sha1);
extern int get_sha1(const char *str, unsigned char *sha1);
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
extern int read_ref(const char *filename, unsigned char *sha1);
extern const char *resolve_ref(const char *path, unsigned char *sha1, int);
extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
extern int validate_symref(const char *git_HEAD);

/* General helper functions */
extern void usage(const char *err) NORETURN;

6
debian/changelog vendored

@ -1,3 +1,9 @@ @@ -1,3 +1,9 @@
git-core (0.99.8-0) unstable; urgency=low

* GIT 0.99.8

-- Junio C Hamano <junkio@cox.net> Sun, 2 Oct 2005 12:54:26 -0700

git-core (0.99.7-0) unstable; urgency=low

* GIT 0.99.7

1
diff-files.c

@ -34,7 +34,6 @@ static void show_modified(int oldmode, int mode, @@ -34,7 +34,6 @@ static void show_modified(int oldmode, int mode,

int main(int argc, const char **argv)
{
static const unsigned char null_sha1[20] = { 0, };
const char **pathspec;
const char *prefix = setup_git_directory();
int entries, i;

3
diff.c

@ -10,7 +10,6 @@ @@ -10,7 +10,6 @@
#include "diffcore.h"

static const char *diff_opts = "-pu";
static unsigned char null_sha1[20] = { 0, };

static int use_size_cache;

@ -414,7 +413,7 @@ void diff_free_filespec_data(struct diff_filespec *s) @@ -414,7 +413,7 @@ void diff_free_filespec_data(struct diff_filespec *s)
static void prep_temp_blob(struct diff_tempfile *temp,
void *blob,
unsigned long size,
unsigned char *sha1,
const unsigned char *sha1,
int mode)
{
int fd;

28
fsck-objects.c

@ -402,25 +402,17 @@ static void fsck_object_dir(const char *path) @@ -402,25 +402,17 @@ static void fsck_object_dir(const char *path)

static int fsck_head_link(void)
{
int fd, count;
char hex[40];
unsigned char sha1[20];
static char path[PATH_MAX], link[PATH_MAX];
const char *git_dir = get_git_dir();

snprintf(path, sizeof(path), "%s/HEAD", git_dir);
if (readlink(path, link, sizeof(link)) < 0)
return error("HEAD is not a symlink");
if (strncmp("refs/heads/", link, 11))
return error("HEAD points to something strange (%s)", link);
fd = open(path, O_RDONLY);
if (fd < 0)
return error("HEAD: %s", strerror(errno));
count = read(fd, hex, sizeof(hex));
close(fd);
if (count < 0)
return error("HEAD: %s", strerror(errno));
if (count < 40 || get_sha1_hex(hex, sha1))
const char *git_HEAD = strdup(git_path("HEAD"));
const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */

if (!git_refs_heads_master)
return error("HEAD is not a symbolic ref");
if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
return error("HEAD points to something strange (%s)",
git_refs_heads_master + pfxlen);
if (!memcmp(null_sha1, sha1, 20))
return error("HEAD: not a valid git pointer");
return 0;
}

8
git-archimport.perl

@ -228,10 +228,12 @@ foreach my $ps (@psets) { @@ -228,10 +228,12 @@ foreach my $ps (@psets) {
# skip commits already in repo
#
if (ptag($ps->{id})) {
$opt_v && print "Skipping already imported: $ps->{id}\n";
$opt_v && print " * Skipping already imported: $ps->{id}\n";
next;
}

print " * Starting to work on $ps->{id}\n";

#
# create the branch if needed
#
@ -675,6 +677,10 @@ sub find_parents { @@ -675,6 +677,10 @@ sub find_parents {
# that branch.
#
foreach my $branch (keys %branches) {

# check that we actually know about the branch
next unless -e "$git_dir/refs/heads/$branch";

my $mergebase = `git-merge-base $branch $ps->{branch}`;
die "Cannot find merge base for $branch and $ps->{branch}" if $?;
chomp $mergebase;

7
git-bisect.sh

@ -38,7 +38,8 @@ bisect_start() { @@ -38,7 +38,8 @@ bisect_start() {
# Verify HEAD. If we were bisecting before this, reset to the
# top-of-line master first!
#
head=$(readlink $GIT_DIR/HEAD) || die "Bad HEAD - I need a symlink"
head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
die "Bad HEAD - I need a symbolic ref"
case "$head" in
refs/heads/bisect*)
git checkout master || exit
@ -46,7 +47,7 @@ bisect_start() { @@ -46,7 +47,7 @@ bisect_start() {
refs/heads/*)
;;
*)
die "Bad HEAD - strange symlink"
die "Bad HEAD - strange symbolic ref"
;;
esac

@ -135,7 +136,7 @@ bisect_next() { @@ -135,7 +136,7 @@ bisect_next() {
echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
git checkout new-bisect || exit
mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
ln -sf refs/heads/bisect "$GIT_DIR/HEAD"
GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
git-show-branch "$rev"
}


6
git-branch.sh

@ -14,7 +14,8 @@ If two arguments, create a new branch <branchname> based off of <start-point>. @@ -14,7 +14,8 @@ If two arguments, create a new branch <branchname> based off of <start-point>.

delete_branch () {
option="$1" branch_name="$2"
headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
sed -e 's|^refs/heads/||')
case ",$headref," in
",$branch_name,")
die "Cannot delete the branch you are on." ;;
@ -67,7 +68,8 @@ done @@ -67,7 +68,8 @@ done

case "$#" in
0)
headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
sed -e 's|^refs/heads/||')
git-rev-parse --symbolic --all |
sed -ne 's|^refs/heads/||p' |
sort |

3
git-checkout.sh

@ -71,7 +71,8 @@ if [ "$?" -eq 0 ]; then @@ -71,7 +71,8 @@ if [ "$?" -eq 0 ]; then
echo $new > "$GIT_DIR/refs/heads/$newbranch"
branch="$newbranch"
fi
[ "$branch" ] && ln -sf "refs/heads/$branch" "$GIT_DIR/HEAD"
[ "$branch" ] &&
GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch"
rm -f "$GIT_DIR/MERGE_HEAD"
else
exit 1

17
git-commit.sh

@ -153,15 +153,8 @@ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then @@ -153,15 +153,8 @@ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
fi >>.editmsg

PARENTS="-p HEAD"
if [ ! -r "$GIT_DIR/HEAD" ]; then
if [ -z "$(git-ls-files)" ]; then
echo Nothing to commit 1>&2
exit 1
fi
PARENTS=""
current=
else
current=$(git-rev-parse --verify HEAD)
if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
then
if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
fi
@ -194,6 +187,12 @@ else @@ -194,6 +187,12 @@ else
export GIT_AUTHOR_EMAIL
export GIT_AUTHOR_DATE
fi
else
if [ -z "$(git-ls-files)" ]; then
echo Nothing to commit 1>&2
exit 1
fi
PARENTS=""
fi
git-status >>.editmsg
if [ "$?" != "0" -a ! -f $GIT_DIR/MERGE_HEAD ]

18
git-cvsimport.perl

@ -510,7 +510,7 @@ unless($pid) { @@ -510,7 +510,7 @@ unless($pid) {

my $state = 0;

my($patchset,$date,$author,$branch,$ancestor,$tag,$logmsg);
my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
my(@old,@new);
my $commit = sub {
my $pid;
@ -591,11 +591,11 @@ my $commit = sub { @@ -591,11 +591,11 @@ my $commit = sub {
}

exec("env",
"GIT_AUTHOR_NAME=$author",
"GIT_AUTHOR_EMAIL=$author",
"GIT_AUTHOR_NAME=$author_name",
"GIT_AUTHOR_EMAIL=$author_email",
"GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
"GIT_COMMITTER_NAME=$author",
"GIT_COMMITTER_EMAIL=$author",
"GIT_COMMITTER_NAME=$author_name",
"GIT_COMMITTER_EMAIL=$author_email",
"GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
"git-commit-tree", $tree,@par);
die "Cannot exec git-commit-tree: $!\n";
@ -638,7 +638,7 @@ my $commit = sub { @@ -638,7 +638,7 @@ my $commit = sub {
print $out "object $cid\n".
"type commit\n".
"tag $xtag\n".
"tagger $author <$author>\n"
"tagger $author_name <$author_email>\n"
or die "Cannot create tag object $xtag: $!\n";
close($out)
or die "Cannot create tag object $xtag: $!\n";
@ -683,7 +683,11 @@ while(<CVS>) { @@ -683,7 +683,11 @@ while(<CVS>) {
$state=3;
} elsif($state == 3 and s/^Author:\s+//) {
s/\s+$//;
$author = $_;
if (/^(.*?)\s+<(.*)>/) {
($author_name, $author_email) = ($1, $2);
} else {
$author_name = $author_email = $_;
}
$state = 4;
} elsif($state == 4 and s/^Branch:\s+//) {
s/\s+$//;

25
git-fetch.sh

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"

tags=
append=
force=
update_head_ok=
@ -17,6 +18,9 @@ do @@ -17,6 +18,9 @@ do
-f|--f|--fo|--for|--forc|--force)
force=t
;;
-t|--t|--ta|--tag|--tags)
tags=t
;;
-u|--u|--up|--upd|--upda|--updat|--update|--update-|--update-h|\
--update-he|--update-hea|--update-head|--update-head-|\
--update-head-o|--update-head-ok)
@ -158,7 +162,26 @@ case "$update_head_ok" in @@ -158,7 +162,26 @@ case "$update_head_ok" in
;;
esac

for ref in $(get_remote_refs_for_fetch "$@")
# If --tags (and later --heads or --all) is specified, then we are
# not talking about defaults stored in Pull: line of remotes or
# branches file, and just fetch those and refspecs explicitly given.
# Otherwise we do what we always did.

reflist=$(get_remote_refs_for_fetch "$@")
if test "$tags"
then
taglist=$(git-ls-remote --tags "$remote" | awk '{ print "."$2":"$2 }')
if test "$#" -gt 1
then
# remote URL plus explicit refspecs; we need to merge them.
reflist="$reflist $taglist"
else
# No explicit refspecs; fetch tags only.
reflist=$taglist
fi
fi

for ref in $reflist
do
refs="$refs $ref"


786
git-merge-recursive.py

@ -7,9 +7,6 @@ from sets import Set @@ -7,9 +7,6 @@ from sets import Set
sys.path.append('@@GIT_PYTHON_PATH@@')
from gitMergeCommon import *

# The actual merge code
# ---------------------

originalIndexFile = os.environ.get('GIT_INDEX_FILE',
os.environ.get('GIT_DIR', '.git') + '/index')
temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
@ -21,11 +18,23 @@ def setupIndex(temporary): @@ -21,11 +18,23 @@ def setupIndex(temporary):
pass
if temporary:
newIndex = temporaryIndexFile
os.environ
else:
newIndex = originalIndexFile
os.environ['GIT_INDEX_FILE'] = newIndex

# This is a global variable which is used in a number of places but
# only written to in the 'merge' function.

# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and
# don't update the working directory.
# False => Leave unmerged entries in the cache and update
# the working directory.

cacheOnly = False

# The entry point to the merge code
# ---------------------------------

def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
'''Merge the commits h1 and h2, return the resulting virtual
commit object and a flag indicating the cleaness of the merge.'''
@ -35,6 +44,7 @@ def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0): @@ -35,6 +44,7 @@ def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
def infoMsg(*args):
sys.stdout.write(' '*callDepth)
printList(args)

infoMsg('Merging:')
infoMsg(h1)
infoMsg(h2)
@ -46,27 +56,27 @@ def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0): @@ -46,27 +56,27 @@ def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
infoMsg(x)
sys.stdout.flush()

Ms = ca[0]
mergedCA = ca[0]
for h in ca[1:]:
[Ms, ignore] = merge(Ms, h,
'Temporary shared merge branch 1',
'Temporary shared merge branch 2',
graph, callDepth+1)
assert(isinstance(Ms, Commit))
[mergedCA, dummy] = merge(mergedCA, h,
'Temporary shared merge branch 1',
'Temporary shared merge branch 2',
graph, callDepth+1)
assert(isinstance(mergedCA, Commit))

global cacheOnly
if callDepth == 0:
setupIndex(False)
cleanCache = False
cacheOnly = False
else:
setupIndex(True)
runProgram(['git-read-tree', h1.tree()])
cleanCache = True
cacheOnly = True

[shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), Ms.tree(),
branch1Name, branch2Name,
cleanCache)
[shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
branch1Name, branch2Name)

if clean or cleanCache:
if clean or cacheOnly:
res = Commit(None, [h1, h2], tree=shaRes)
graph.addNode(res)
else:
@ -89,16 +99,255 @@ def getFilesAndDirs(tree): @@ -89,16 +99,255 @@ def getFilesAndDirs(tree):

return [files, dirs]

# Those two global variables are used in a number of places but only
# written to in 'mergeTrees' and 'uniquePath'. They keep track of
# every file and directory in the two branches that are about to be
# merged.
currentFileSet = None
currentDirectorySet = None

def mergeTrees(head, merge, common, branch1Name, branch2Name):
'''Merge the trees 'head' and 'merge' with the common ancestor
'common'. The name of the head branch is 'branch1Name' and the name of
the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
where tree is the resulting tree and cleanMerge is True iff the
merge was clean.'''
assert(isSha(head) and isSha(merge) and isSha(common))

if common == merge:
print 'Already uptodate!'
return [head, True]

if cacheOnly:
updateArg = '-i'
else:
updateArg = '-u'

[out, code] = runProgram(['git-read-tree', updateArg, '-m',
common, head, merge], returnCode = True)
if code != 0:
die('git-read-tree:', out)

[tree, code] = runProgram('git-write-tree', returnCode=True)
tree = tree.rstrip()
if code != 0:
global currentFileSet, currentDirectorySet
[currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
[filesM, dirsM] = getFilesAndDirs(merge)
currentFileSet.union_update(filesM)
currentDirectorySet.union_update(dirsM)

entries = unmergedCacheEntries()
renamesHead = getRenames(head, common, head, merge, entries)
renamesMerge = getRenames(merge, common, head, merge, entries)

cleanMerge = processRenames(renamesHead, renamesMerge,
branch1Name, branch2Name)
for entry in entries:
if entry.processed:
continue
if not processEntry(entry, branch1Name, branch2Name):
cleanMerge = False
if cleanMerge or cacheOnly:
tree = runProgram('git-write-tree').rstrip()
else:
tree = None
else:
cleanMerge = True

return [tree, cleanMerge]

# Low level file merging, update and removal
# ------------------------------------------

def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
branch1Name, branch2Name):

merge = False
clean = True

if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
clean = False
if stat.S_ISREG(aMode):
mode = aMode
sha = aSha
else:
mode = bMode
sha = bSha
else:
if aSha != oSha and bSha != oSha:
merge = True

if aMode == oMode:
mode = bMode
else:
mode = aMode

if aSha == oSha:
sha = bSha
elif bSha == oSha:
sha = aSha
elif stat.S_ISREG(aMode):
assert(stat.S_ISREG(bMode))

orig = runProgram(['git-unpack-file', oSha]).rstrip()
src1 = runProgram(['git-unpack-file', aSha]).rstrip()
src2 = runProgram(['git-unpack-file', bSha]).rstrip()
[out, code] = runProgram(['merge',
'-L', branch1Name + '/' + aPath,
'-L', 'orig/' + oPath,
'-L', branch2Name + '/' + bPath,
src1, orig, src2], returnCode=True)

sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
src1]).rstrip()

os.unlink(orig)
os.unlink(src1)
os.unlink(src2)
clean = (code == 0)
else:
assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
sha = aSha

if aSha != bSha:
clean = False

return [sha, mode, clean, merge]

def updateFile(clean, sha, mode, path):
updateCache = cacheOnly or clean
updateWd = not cacheOnly

return updateFileExt(sha, mode, path, updateCache, updateWd)

def updateFileExt(sha, mode, path, updateCache, updateWd):
if cacheOnly:
updateWd = False

if updateWd:
pathComponents = path.split('/')
for x in xrange(1, len(pathComponents)):
p = '/'.join(pathComponents[0:x])

try:
createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
except:
createDir = True
if createDir:
try:
os.mkdir(p)
except OSError, e:
die("Couldn't create directory", p, e.strerror)

prog = ['git-cat-file', 'blob', sha]
if stat.S_ISREG(mode):
try:
os.unlink(path)
except OSError:
pass
if mode & 0100:
mode = 0777
else:
mode = 0666
fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
proc = subprocess.Popen(prog, stdout=fd)
proc.wait()
os.close(fd)
elif stat.S_ISLNK(mode):
linkTarget = runProgram(prog)
os.symlink(linkTarget, path)
else:
assert(False)

if updateWd and updateCache:
runProgram(['git-update-index', '--add', '--', path])
elif updateCache:
runProgram(['git-update-index', '--add', '--cacheinfo',
'0%o' % mode, sha, path])

def removeFile(clean, path):
updateCache = cacheOnly or clean
updateWd = not cacheOnly

if updateCache:
runProgram(['git-update-index', '--force-remove', '--', path])

if updateWd:
try:
os.unlink(path)
except OSError, e:
if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
raise

def uniquePath(path, branch):
def fileExists(path):
try:
os.lstat(path)
return True
except OSError, e:
if e.errno == errno.ENOENT:
return False
else:
raise

newPath = path + '_' + branch
suffix = 0
while newPath in currentFileSet or \
newPath in currentDirectorySet or \
fileExists(newPath):
suffix += 1
newPath = path + '_' + branch + '_' + str(suffix)
currentFileSet.add(newPath)
return newPath

# Cache entry management
# ----------------------

class CacheEntry:
def __init__(self, path):
class Stage:
def __init__(self):
self.sha1 = None
self.mode = None

# Used for debugging only
def __str__(self):
if self.mode != None:
m = '0%o' % self.mode
else:
m = 'None'

if self.sha1:
sha1 = self.sha1
else:
sha1 = 'None'
return 'sha1: ' + sha1 + ' mode: ' + m
self.stages = [Stage(), Stage(), Stage()]
self.stages = [Stage(), Stage(), Stage(), Stage()]
self.path = path
self.processed = False

def __str__(self):
return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])

class CacheEntryContainer:
def __init__(self):
self.entries = {}

def add(self, entry):
self.entries[entry.path] = entry

def get(self, path):
return self.entries.get(path)

def __iter__(self):
return self.entries.itervalues()
unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
def unmergedCacheEntries():
'''Create a dictionary mapping file names to CacheEntry
@ -108,155 +357,340 @@ def unmergedCacheEntries(): @@ -108,155 +357,340 @@ def unmergedCacheEntries():
lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
lines.pop()

res = {}
res = CacheEntryContainer()
for l in lines:
m = unmergedRE.match(l)
if m:
mode = int(m.group(1), 8)
sha1 = m.group(2)
stage = int(m.group(3)) - 1
stage = int(m.group(3))
path = m.group(4)

if res.has_key(path):
e = res[path]
else:
e = res.get(path)
if not e:
e = CacheEntry(path)
res[path] = e
res.add(e)

e.stages[stage].mode = mode
e.stages[stage].sha1 = sha1
else:
die('Error: Merge program failed: Unexpected output from', \
die('Error: Merge program failed: Unexpected output from',
'git-ls-files:', l)
return res

def mergeTrees(head, merge, common, branch1Name, branch2Name,
cleanCache):
'''Merge the trees 'head' and 'merge' with the common ancestor
'common'. The name of the head branch is 'branch1Name' and the name of
the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
where tree is the resulting tree and cleanMerge is True iff the
merge was clean.'''
lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
def getCacheEntry(path, origTree, aTree, bTree):
'''Returns a CacheEntry object which doesn't have to correspond to
a real cache entry in Git's index.'''
assert(isSha(head) and isSha(merge) and isSha(common))
def parse(out):
if out == '':
return [None, None]
else:
m = lsTreeRE.match(out)
if not m:
die('Unexpected output from git-ls-tree:', out)
elif m.group(2) == 'blob':
return [m.group(3), int(m.group(1), 8)]
else:
return [None, None]

if common == merge:
print 'Already uptodate!'
return [head, True]
res = CacheEntry(path)

if cleanCache:
updateArg = '-i'
else:
updateArg = '-u'
[oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
[aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
[bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))

[out, code] = runProgram(['git-read-tree', updateArg, '-m', common, head, merge], returnCode = True)
if code != 0:
die('git-read-tree:', out)
res.stages[1].sha1 = oSha
res.stages[1].mode = oMode
res.stages[2].sha1 = aSha
res.stages[2].mode = aMode
res.stages[3].sha1 = bSha
res.stages[3].mode = bMode

cleanMerge = True
return res

[tree, code] = runProgram('git-write-tree', returnCode=True)
tree = tree.rstrip()
if code != 0:
[files, dirs] = getFilesAndDirs(head)
[filesM, dirsM] = getFilesAndDirs(merge)
files.union_update(filesM)
dirs.union_update(dirsM)
cleanMerge = True
entries = unmergedCacheEntries()
for name in entries:
if not processEntry(entries[name], branch1Name, branch2Name,
files, dirs, cleanCache):
cleanMerge = False
if cleanMerge or cleanCache:
tree = runProgram('git-write-tree').rstrip()
# Rename detection and handling
# -----------------------------

class RenameEntry:
def __init__(self,
src, srcSha, srcMode, srcCacheEntry,
dst, dstSha, dstMode, dstCacheEntry,
score):
self.srcName = src
self.srcSha = srcSha
self.srcMode = srcMode
self.srcCacheEntry = srcCacheEntry
self.dstName = dst
self.dstSha = dstSha
self.dstMode = dstMode
self.dstCacheEntry = dstCacheEntry
self.score = score

self.processed = False

class RenameEntryContainer:
def __init__(self):
self.entriesSrc = {}
self.entriesDst = {}

def add(self, entry):
self.entriesSrc[entry.srcName] = entry
self.entriesDst[entry.dstName] = entry

def getSrc(self, path):
return self.entriesSrc.get(path)

def getDst(self, path):
return self.entriesDst.get(path)

def __iter__(self):
return self.entriesSrc.itervalues()

parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
def getRenames(tree, oTree, aTree, bTree, cacheEntries):
'''Get information of all renames which occured between 'oTree' and
'tree'. We need the three trees in the merge ('oTree', 'aTree' and
'bTree') to be able to associate the correct cache entries with
the rename information. 'tree' is always equal to either aTree or bTree.'''

assert(tree == aTree or tree == bTree)
inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
'-z', oTree, tree])

ret = RenameEntryContainer()
try:
recs = inp.split("\0")
recs.pop() # remove last entry (which is '')
it = recs.__iter__()
while True:
rec = it.next()
m = parseDiffRenamesRE.match(rec)

if not m:
die('Unexpected output from git-diff-tree:', rec)

srcMode = int(m.group(1), 8)
dstMode = int(m.group(2), 8)
srcSha = m.group(3)
dstSha = m.group(4)
score = m.group(5)
src = it.next()
dst = it.next()

srcCacheEntry = cacheEntries.get(src)
if not srcCacheEntry:
srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
cacheEntries.add(srcCacheEntry)

dstCacheEntry = cacheEntries.get(dst)
if not dstCacheEntry:
dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
cacheEntries.add(dstCacheEntry)

ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
dst, dstSha, dstMode, dstCacheEntry,
score))
except StopIteration:
pass
return ret

def fmtRename(src, dst):
srcPath = src.split('/')
dstPath = dst.split('/')
path = []
endIndex = min(len(srcPath), len(dstPath)) - 1
for x in range(0, endIndex):
if srcPath[x] == dstPath[x]:
path.append(srcPath[x])
else:
tree = None
endIndex = x
break

if len(path) > 0:
return '/'.join(path) + \
'/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
'/'.join(dstPath[endIndex:]) + '}'
else:
cleanMerge = True
return src + ' => ' + dst

return [tree, cleanMerge]
def processRenames(renamesA, renamesB, branchNameA, branchNameB):
srcNames = Set()
for x in renamesA:
srcNames.add(x.srcName)
for x in renamesB:
srcNames.add(x.srcName)

def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
'''Merge one cache entry. 'files' is a Set with the files in both of
the heads that we are going to merge. 'dirs' contains the
corresponding data for directories. If 'cleanCache' is True no
non-zero stages will be left in the cache for the path
corresponding to the entry 'entry'.'''
cleanMerge = True
for path in srcNames:
if renamesA.getSrc(path):
renames1 = renamesA
renames2 = renamesB
branchName1 = branchNameA
branchName2 = branchNameB
else:
renames1 = renamesB
renames2 = renamesA
branchName1 = branchNameB
branchName2 = branchNameA
ren1 = renames1.getSrc(path)
ren2 = renames2.getSrc(path)

ren1.dstCacheEntry.processed = True
ren1.srcCacheEntry.processed = True

if ren1.processed:
continue

ren1.processed = True
removeFile(True, ren1.srcName)
if ren2:
# Renamed in 1 and renamed in 2
assert(ren1.srcName == ren2.srcName)
ren2.dstCacheEntry.processed = True
ren2.processed = True

if ren1.dstName != ren2.dstName:
print 'CONFLICT (rename/rename): Rename', \
fmtRename(path, ren1.dstName), 'in branch', branchName1, \
'rename', fmtRename(path, ren2.dstName), 'in', branchName2
cleanMerge = False

# cleanCache == True => Don't leave any non-stage 0 entries in the cache and
# don't update the working directory
# False => Leave unmerged entries and update the working directory
if ren1.dstName in currentDirectorySet:
dstName1 = uniquePath(ren1.dstName, branchName1)
print ren1.dstName, 'is a directory in', branchName2, \
'adding as', dstName1, 'instead.'
removeFile(False, ren1.dstName)
else:
dstName1 = ren1.dstName

# clean == True => non-conflict case
# False => conflict case
if ren2.dstName in currentDirectorySet:
dstName2 = uniquePath(ren2.dstName, branchName2)
print ren2.dstName, 'is a directory in', branchName1, \
'adding as', dstName2, 'instead.'
removeFile(False, ren2.dstName)
else:
dstName2 = ren1.dstName

# If cleanCache == False then the cache shouldn't be updated if clean == False
updateFile(False, ren1.dstSha, ren1.dstMode, dstName1)
updateFile(False, ren2.dstSha, ren2.dstMode, dstName2)
else:
print 'Renaming', fmtRename(path, ren1.dstName)
[resSha, resMode, clean, merge] = \
mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
ren1.dstName, ren1.dstSha, ren1.dstMode,
ren2.dstName, ren2.dstSha, ren2.dstMode,
branchName1, branchName2)

if merge:
print 'Auto-merging', ren1.dstName

if not clean:
print 'CONFLICT (content): merge conflict in', ren1.dstName
cleanMerge = False

if not cacheOnly:
updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
updateCache=True, updateWd=False)
updateFile(clean, resSha, resMode, ren1.dstName)
else:
# Renamed in 1, maybe changed in 2
if renamesA == renames1:
stage = 3
else:
stage = 2
srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1
srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode

dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1
dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode

tryMerge = False
if ren1.dstName in currentDirectorySet:
newPath = uniquePath(ren1.dstName, branchName1)
print 'CONFLICT (rename/directory): Rename', \
fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,\
'directory', ren1.dstName, 'added in', branchName2
print 'Renaming', ren1.srcName, 'to', newPath, 'instead'
cleanMerge = False
removeFile(False, ren1.dstName)
updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
elif srcShaOtherBranch == None:
print 'CONFLICT (rename/delete): Rename', \
fmtRename(ren1.srcName, ren1.dstName), 'in', \
branchName1, 'and deleted in', branchName2
cleanMerge = False
updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
elif dstShaOtherBranch:
newPath = uniquePath(ren1.dstName, branchName2)
print 'CONFLICT (rename/add): Rename', \
fmtRename(ren1.srcName, ren1.dstName), 'in', \
branchName1 + '.', ren1.dstName, 'added in', branchName2
print 'Adding as', newPath, 'instead'
updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
cleanMerge = False
tryMerge = True
elif renames2.getDst(ren1.dstName):
dst2 = renames2.getDst(ren1.dstName)
newPath1 = uniquePath(ren1.dstName, branchName1)
newPath2 = uniquePath(dst2.dstName, branchName2)
print 'CONFLICT (rename/rename): Rename', \
fmtRename(ren1.srcName, ren1.dstName), 'in', \
branchName1+'. Rename', \
fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2
print 'Renaming', ren1.srcName, 'to', newPath1, 'and', \
dst2.srcName, 'to', newPath2, 'instead'
removeFile(False, ren1.dstName)
updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
dst2.processed = True
cleanMerge = False
else:
tryMerge = True

def updateFile(clean, sha, mode, path, onlyWd=False):
updateCache = not onlyWd and (cleanCache or (not cleanCache and clean))
updateWd = onlyWd or (not cleanCache and clean)
if tryMerge:
print 'Renaming', fmtRename(ren1.srcName, ren1.dstName)
[resSha, resMode, clean, merge] = \
mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
ren1.dstName, ren1.dstSha, ren1.dstMode,
ren1.srcName, srcShaOtherBranch, srcModeOtherBranch,
branchName1, branchName2)

if updateWd:
prog = ['git-cat-file', 'blob', sha]
if stat.S_ISREG(mode):
try:
os.unlink(path)
except OSError:
pass
if mode & 0100:
mode = 0777
else:
mode = 0666
fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
proc = subprocess.Popen(prog, stdout=fd)
proc.wait()
os.close(fd)
elif stat.S_ISLNK(mode):
linkTarget = runProgram(prog)
os.symlink(linkTarget, path)
else:
assert(False)
if merge:
print 'Auto-merging', ren1.dstName

if updateWd and updateCache:
runProgram(['git-update-index', '--add', '--', path])
elif updateCache:
runProgram(['git-update-index', '--add', '--cacheinfo',
'0%o' % mode, sha, path])
if not clean:
print 'CONFLICT (rename/modify): Merge conflict in', ren1.dstName
cleanMerge = False

def removeFile(clean, path):
if cleanCache or (not cleanCache and clean):
runProgram(['git-update-index', '--force-remove', '--', path])
if not cacheOnly:
updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
updateCache=True, updateWd=False)
updateFile(clean, resSha, resMode, ren1.dstName)

if not cleanCache and clean:
try:
os.unlink(path)
except OSError, e:
if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
raise
return cleanMerge

def uniquePath(path, branch):
newPath = path + '_' + branch
suffix = 0
while newPath in files or newPath in dirs:
suffix += 1
newPath = path + '_' + branch + '_' + str(suffix)
files.add(newPath)
return newPath
# Per entry merge function
# ------------------------

debug('processing', entry.path, 'clean cache:', cleanCache)
def processEntry(entry, branch1Name, branch2Name):
'''Merge one cache entry.'''

debug('processing', entry.path, 'clean cache:', cacheOnly)

cleanMerge = True

path = entry.path
oSha = entry.stages[0].sha1
oMode = entry.stages[0].mode
aSha = entry.stages[1].sha1
aMode = entry.stages[1].mode
bSha = entry.stages[2].sha1
bMode = entry.stages[2].mode
oSha = entry.stages[1].sha1
oMode = entry.stages[1].mode
aSha = entry.stages[2].sha1
aMode = entry.stages[2].mode
bSha = entry.stages[3].sha1
bMode = entry.stages[3].mode

assert(oSha == None or isSha(oSha))
assert(aSha == None or isSha(aSha))
@ -275,28 +709,26 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache): @@ -275,28 +709,26 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
(not aSha and bSha == oSha):
# Deleted in both or deleted in one and unchanged in the other
if aSha:
print 'Removing ' + path
print 'Removing', path
removeFile(True, path)
else:
# Deleted in one and changed in the other
cleanMerge = False
if not aSha:
print 'CONFLICT (del/mod): "' + path + '" deleted in', \
branch1Name, 'and modified in', branch2Name, \
'. Version', branch2Name, ' of "' + path + \
'" left in tree'
print 'CONFLICT (delete/modify):', path, 'deleted in', \
branch1Name, 'and modified in', branch2Name + '.', \
'Version', branch2Name, 'of', path, 'left in tree.'
mode = bMode
sha = bSha
else:
print 'CONFLICT (mod/del): "' + path + '" deleted in', \
branch2Name, 'and modified in', branch1Name + \
'. Version', branch1Name, 'of "' + path + \
'" left in tree'
print 'CONFLICT (modify/delete):', path, 'deleted in', \
branch2Name, 'and modified in', branch1Name + '.', \
'Version', branch1Name, 'of', path, 'left in tree.'
mode = aMode
sha = aSha

updateFile(False, sha, mode, path)

elif (not oSha and aSha and not bSha) or \
(not oSha and not aSha and bSha):
#
@ -307,27 +739,26 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache): @@ -307,27 +739,26 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
otherBranch = branch2Name
mode = aMode
sha = aSha
conf = 'file/dir'
conf = 'file/directory'
else:
addBranch = branch2Name
otherBranch = branch1Name
mode = bMode
sha = bSha
conf = 'dir/file'
conf = 'directory/file'
if path in dirs:
if path in currentDirectorySet:
cleanMerge = False
newPath = uniquePath(path, addBranch)
print 'CONFLICT (' + conf + \
'): There is a directory with name "' + path + '" in', \
otherBranch + '. Adding "' + path + '" as "' + newPath + '"'
print 'CONFLICT (' + conf + '):', \
'There is a directory with name', path, 'in', \
otherBranch + '. Adding', path, 'as', newPath

removeFile(False, path)
path = newPath
updateFile(False, sha, mode, newPath)
else:
print 'Adding "' + path + '"'

updateFile(True, sha, mode, path)
print 'Adding', path
updateFile(True, sha, mode, path)
elif not oSha and aSha and bSha:
#
@ -336,10 +767,9 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache): @@ -336,10 +767,9 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
if aSha == bSha:
if aMode != bMode:
cleanMerge = False
print 'CONFLICT: File "' + path + \
'" added identically in both branches,', \
'but permissions conflict', '0%o' % aMode, '->', \
'0%o' % bMode
print 'CONFLICT: File', path, \
'added identically in both branches, but permissions', \
'conflict', '0%o' % aMode, '->', '0%o' % bMode
print 'CONFLICT: adding with permission:', '0%o' % aMode

updateFile(False, aSha, aMode, path)
@ -350,8 +780,9 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache): @@ -350,8 +780,9 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
cleanMerge = False
newPath1 = uniquePath(path, branch1Name)
newPath2 = uniquePath(path, branch2Name)
print 'CONFLICT (add/add): File "' + path + \
'" added non-identically in both branches.'
print 'CONFLICT (add/add): File', path, \
'added non-identically in both branches. Adding as', \
newPath1, 'and', newPath2, 'instead.'
removeFile(False, path)
updateFile(False, aSha, aMode, newPath1)
updateFile(False, bSha, bMode, newPath2)
@ -360,39 +791,24 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache): @@ -360,39 +791,24 @@ def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
#
# case D: Modified in both, but differently.
#
print 'Auto-merging', path
orig = runProgram(['git-unpack-file', oSha]).rstrip()
src1 = runProgram(['git-unpack-file', aSha]).rstrip()
src2 = runProgram(['git-unpack-file', bSha]).rstrip()
[out, ret] = runProgram(['merge',
'-L', branch1Name + '/' + path,
'-L', 'orig/' + path,
'-L', branch2Name + '/' + path,
src1, orig, src2], returnCode=True)

if aMode == oMode:
mode = bMode
print 'Auto-merging', path
[sha, mode, clean, dummy] = \
mergeFile(path, oSha, oMode,
path, aSha, aMode,
path, bSha, bMode,
branch1Name, branch2Name)
if clean:
updateFile(True, sha, mode, path)
else:
mode = aMode

sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
src1]).rstrip()

if ret != 0:
cleanMerge = False
print 'CONFLICT (content): Merge conflict in "' + path + '".'
print 'CONFLICT (content): Merge conflict in', path

if cleanCache:
if cacheOnly:
updateFile(False, sha, mode, path)
else:
updateFile(True, aSha, aMode, path)
updateFile(False, sha, mode, path, True)
else:
updateFile(True, sha, mode, path)

os.unlink(orig)
os.unlink(src1)
os.unlink(src2)
updateFileExt(aSha, aMode, path,
updateCache=True, updateWd=False)
updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
else:
die("ERROR: Fatal merge failure, shouldn't happen.")

@ -416,7 +832,7 @@ for nextArg in xrange(1, len(sys.argv)): @@ -416,7 +832,7 @@ for nextArg in xrange(1, len(sys.argv)):
try:
h1 = firstBranch = sys.argv[nextArg + 1]
h2 = secondBranch = sys.argv[nextArg + 2]
except IndexError:
except IndexError:
usage()
break

@ -428,8 +844,8 @@ try: @@ -428,8 +844,8 @@ try:

graph = buildGraph([h1, h2])

[res, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
firstBranch, secondBranch, graph)
[dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
firstBranch, secondBranch, graph)

print ''
except:

22
git-merge.sh

@ -123,10 +123,30 @@ case "$#,$common" in @@ -123,10 +123,30 @@ case "$#,$common" in
dropsave
exit 0
;;
1,*)
1,?*"$LF"?*)
# We are not doing octopus and not fast forward. Need a
# real merge.
;;
1,*)
# We are not doing octopus, not fast forward, and have only
# one common. See if it is really trivial.
echo "Trying really trivial in-index merge..."
git-update-index --refresh 2>/dev/null
if git-read-tree --trivial -m -u $common $head "$1" &&
result_tree=$(git-write-tree)
then
echo "Wonderful."
result_commit=$(
echo "$merge_msg" |
git-commit-tree $result_tree -p HEAD -p "$1"
) || exit
git-update-ref HEAD $result_commit $head
summary $result_commit
dropsave
exit 0
fi
echo "Nope."
;;
*)
# An octopus. If we can reach all the remote we are up to date.
up_to_date=t

7
git-sh-setup.sh

@ -13,10 +13,13 @@ @@ -13,10 +13,13 @@
unset CDPATH

die() {
echo "$@" >&2
echo >&2 "$@"
exit 1
}

[ -h "$GIT_DIR/HEAD" ] &&
case "$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD 2>/dev/null)" in
refs/*) : ;;
*) false ;;
esac &&
[ -d "$GIT_DIR/refs" ] &&
[ -d "$GIT_OBJECT_DIRECTORY/00" ]

6
git-status.sh

@ -31,15 +31,15 @@ report () { @@ -31,15 +31,15 @@ report () {
[ "$header" ]
}

branch=`readlink "$GIT_DIR/HEAD"`
branch=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD)
case "$branch" in
refs/heads/master) ;;
*) echo "# On branch $branch" ;;
esac

git-update-index --refresh >/dev/null 2>&1
git-update-index -q --unmerged --refresh || exit

if test -f "$GIT_DIR/HEAD"
if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
then
git-diff-index -M --cached HEAD |
sed 's/^://' |

55
git.sh

@ -26,17 +26,50 @@ esac @@ -26,17 +26,50 @@ esac

echo "Usage: git COMMAND [OPTIONS] [TARGET]"
if [ -n "$cmd" ]; then
echo " git command '$cmd' not found: commands are:"
else
echo " git commands are:"
echo "git command '$cmd' not found."
fi
echo "git commands are:"

cat <<\EOF
add apply archimport bisect branch checkout cherry clone
commit count-objects cvsimport diff fetch format-patch
fsck-cache get-tar-commit-id init-db log ls-remote octopus
pack-objects parse-remote patch-id prune pull push rebase
relink rename repack request-pull reset resolve revert
send-email shortlog show-branch status tag verify-tag
whatchanged
fmt <<\EOF | sed -e 's/^/ /'
add
apply
archimport
bisect
branch
checkout
cherry
clone
commit
count-objects
cvsimport
diff
fetch
format-patch
fsck-objects
get-tar-commit-id
init-db
log
ls-remote
octopus
pack-objects
parse-remote
patch-id
prune
pull
push
rebase
relink
rename
repack
request-pull
reset
resolve
revert
send-email
shortlog
show-branch
status
tag
verify-tag
whatchanged
EOF

192
http-fetch.c

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
#include "cache.h"
#include "commit.h"

#include "pack.h"
#include "fetch.h"

#include <curl/curl.h>
@ -13,8 +13,12 @@ @@ -13,8 +13,12 @@
#define curl_global_init(a) do { /* nothing */ } while(0)
#endif

#define PREV_BUF_SIZE 4096
#define RANGE_HEADER_SIZE 30

static CURL *curl;
static struct curl_slist *no_pragma_header;
static struct curl_slist *no_range_header;
static char curl_errorstr[CURL_ERROR_SIZE];

static char *initial_base;
@ -87,12 +91,37 @@ void prefetch(unsigned char *sha1) @@ -87,12 +91,37 @@ void prefetch(unsigned char *sha1)
{
}

int relink_or_rename(char *old, char *new) {
int ret;

ret = link(old, new);
if (ret < 0) {
/* Same Coda hack as in write_sha1_file(sha1_file.c) */
ret = errno;
if (ret == EXDEV && !rename(old, new))
return 0;
}
unlink(old);
if (ret) {
if (ret != EEXIST)
return ret;
}

return 0;
}

static int got_alternates = 0;

static int fetch_index(struct alt_base *repo, unsigned char *sha1)
{
char *filename;
char *url;
char tmpfile[PATH_MAX];
int ret;
long prev_posn = 0;
char range[RANGE_HEADER_SIZE];
struct curl_slist *range_header = NULL;
CURLcode curl_result;

FILE *indexfile;

@ -108,7 +137,8 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1) @@ -108,7 +137,8 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
repo->base, sha1_to_hex(sha1));
filename = sha1_pack_index_name(sha1);
indexfile = fopen(filename, "w");
snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
indexfile = fopen(tmpfile, "a");
if (!indexfile)
return error("Unable to open local file %s for pack index",
filename);
@ -119,13 +149,36 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1) @@ -119,13 +149,36 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
if (curl_easy_perform(curl)) {
/* If there is data present from a previous transfer attempt,
resume where it left off */
prev_posn = ftell(indexfile);
if (prev_posn>0) {
if (get_verbosely)
fprintf(stderr,
"Resuming fetch of index for pack %s at byte %ld\n",
sha1_to_hex(sha1), prev_posn);
sprintf(range, "Range: bytes=%ld-", prev_posn);
range_header = curl_slist_append(range_header, range);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header);
}

/* Clear out the Range: header after performing the request, so
other curl requests don't inherit inappropriate header data */
curl_result = curl_easy_perform(curl);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header);
if (curl_result != 0) {
fclose(indexfile);
return error("Unable to get pack index %s\n%s", url,
curl_errorstr);
}

fclose(indexfile);

ret = relink_or_rename(tmpfile, filename);
if (ret)
return error("unable to write index filename %s: %s",
filename, strerror(ret));

return 0;
}

@ -306,6 +359,12 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) @@ -306,6 +359,12 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
struct packed_git **lst;
FILE *packfile;
char *filename;
char tmpfile[PATH_MAX];
int ret;
long prev_posn = 0;
char range[RANGE_HEADER_SIZE];
struct curl_slist *range_header = NULL;
CURLcode curl_result;

if (fetch_indices(repo))
return -1;
@ -325,7 +384,8 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) @@ -325,7 +384,8 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
repo->base, sha1_to_hex(target->sha1));

filename = sha1_pack_name(target->sha1);
packfile = fopen(filename, "w");
snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
packfile = fopen(tmpfile, "a");
if (!packfile)
return error("Unable to open local file %s for pack",
filename);
@ -336,7 +396,24 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) @@ -336,7 +396,24 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);

if (curl_easy_perform(curl)) {
/* If there is data present from a previous transfer attempt,
resume where it left off */
prev_posn = ftell(packfile);
if (prev_posn>0) {
if (get_verbosely)
fprintf(stderr,
"Resuming fetch of pack %s at byte %ld\n",
sha1_to_hex(target->sha1), prev_posn);
sprintf(range, "Range: bytes=%ld-", prev_posn);
range_header = curl_slist_append(range_header, range);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header);
}

/* Clear out the Range: header after performing the request, so
other curl requests don't inherit inappropriate header data */
curl_result = curl_easy_perform(curl);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header);
if (curl_result != 0) {
fclose(packfile);
return error("Unable to get pack file %s\n%s", url,
curl_errorstr);
@ -344,11 +421,18 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) @@ -344,11 +421,18 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)

fclose(packfile);

ret = relink_or_rename(tmpfile, filename);
if (ret)
return error("unable to write pack filename %s: %s",
filename, strerror(ret));

lst = &repo->packs;
while (*lst != target)
lst = &((*lst)->next);
*lst = (*lst)->next;

if (verify_pack(target, 0))
return -1;
install_packed_git(target);

return 0;
@ -360,14 +444,33 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) @@ -360,14 +444,33 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
char *filename = sha1_file_name(sha1);
unsigned char real_sha1[20];
char tmpfile[PATH_MAX];
char prevfile[PATH_MAX];
int ret;
char *url;
char *posn;
int prevlocal;
unsigned char prev_buf[PREV_BUF_SIZE];
ssize_t prev_read = 0;
long prev_posn = 0;
char range[RANGE_HEADER_SIZE];
struct curl_slist *range_header = NULL;
CURLcode curl_result;

snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
snprintf(prevfile, sizeof(prevfile), "%s.prev", filename);

if (unlink(prevfile) && (errno != ENOENT))
return error("Failed to unlink %s (%s)",
prevfile, strerror(errno));
if (rename(tmpfile, prevfile) && (errno != ENOENT))
return error("Failed to rename %s to %s (%s)",
tmpfile, prevfile, strerror(errno));

snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX",
get_object_directory());
local = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0666);

/* Note: if another instance starts now, it will turn our new
tmpfile into its prevfile. */

local = mkstemp(tmpfile);
if (local < 0)
return error("Couldn't create temporary file %s for %s: %s\n",
tmpfile, filename, strerror(errno));
@ -396,8 +499,57 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) @@ -396,8 +499,57 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)

curl_easy_setopt(curl, CURLOPT_URL, url);

if (curl_easy_perform(curl)) {
unlink(filename);
/* If a previous temp file is present, process what was already
fetched. */
prevlocal = open(prevfile, O_RDONLY);
if (prevlocal != -1) {
do {
prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
if (prev_read>0) {
if (fwrite_sha1_file(prev_buf,
1,
prev_read,
NULL) == prev_read) {
prev_posn += prev_read;
} else {
prev_read = -1;
}
}
} while (prev_read > 0);
close(prevlocal);
}
unlink(prevfile);

/* Reset inflate/SHA1 if there was an error reading the previous temp
file; also rewind to the beginning of the local file. */
if (prev_read == -1) {
memset(&stream, 0, sizeof(stream));
inflateInit(&stream);
SHA1_Init(&c);
if (prev_posn>0) {
prev_posn = 0;
lseek(local, SEEK_SET, 0);
ftruncate(local, 0);
}
}

/* If we have successfully processed data from a previous fetch
attempt, only fetch the data we don't already have. */
if (prev_posn>0) {
if (get_verbosely)
fprintf(stderr,
"Resuming fetch of object %s at byte %ld\n",
hex, prev_posn);
sprintf(range, "Range: bytes=%ld-", prev_posn);
range_header = curl_slist_append(range_header, range);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header);
}

/* Clear out the Range: header after performing the request, so
other curl requests don't inherit inappropriate header data */
curl_result = curl_easy_perform(curl);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header);
if (curl_result != 0) {
return error("%s", curl_errorstr);
}

@ -413,20 +565,11 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) @@ -413,20 +565,11 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
unlink(tmpfile);
return error("File %s has bad hash\n", hex);
}
ret = link(tmpfile, filename);
if (ret < 0) {
/* Same Coda hack as in write_sha1_file(sha1_file.c) */
ret = errno;
if (ret == EXDEV && !rename(tmpfile, filename))
goto out;
}
unlink(tmpfile);
if (ret) {
if (ret != EEXIST)
return error("unable to write sha1 filename %s: %s",
filename, strerror(ret));
}
out:
ret = relink_or_rename(tmpfile, filename);
if (ret)
return error("unable to write sha1 filename %s: %s",
filename, strerror(ret));

pull_say("got %s\n", hex);
return 0;
}
@ -519,6 +662,7 @@ int main(int argc, char **argv) @@ -519,6 +662,7 @@ int main(int argc, char **argv)

curl = curl_easy_init();
no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
no_range_header = curl_slist_append(no_range_header, "Range:");

curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);

2
index.c

@ -29,7 +29,7 @@ int hold_index_file_for_update(struct cache_file *cf, const char *path) @@ -29,7 +29,7 @@ int hold_index_file_for_update(struct cache_file *cf, const char *path)
signal(SIGINT, remove_lock_file_on_signal);
atexit(remove_lock_file);
}
return open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0600);
return open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0666);
}

int commit_index_file(struct cache_file *cf)

10
init-db.c

@ -166,6 +166,7 @@ static void create_default_files(const char *git_dir, @@ -166,6 +166,7 @@ static void create_default_files(const char *git_dir,
{
unsigned len = strlen(git_dir);
static char path[PATH_MAX];
unsigned char sha1[20];

if (len > sizeof(path)-50)
die("insane git directory %s", git_dir);
@ -186,15 +187,14 @@ static void create_default_files(const char *git_dir, @@ -186,15 +187,14 @@ static void create_default_files(const char *git_dir,

/*
* Create the default symlink from ".git/HEAD" to the "master"
* branch
* branch, if it does not exist yet.
*/
strcpy(path + len, "HEAD");
if (symlink("refs/heads/master", path) < 0) {
if (errno != EEXIST) {
perror(path);
if (read_ref(path, sha1) < 0) {
if (create_symref(path, "refs/heads/master") < 0)
exit(1);
}
}
path[len] = 0;
copy_templates(path, len, template_path);
}


6
ls-files.c

@ -530,7 +530,7 @@ static void verify_pathspec(void) @@ -530,7 +530,7 @@ static void verify_pathspec(void)
static const char ls_files_usage[] =
"git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
"[ --exclude-per-directory=<filename> ]";
"[ --exclude-per-directory=<filename> ] [--] [<file>]*";

int main(int argc, const char **argv)
{
@ -544,6 +544,10 @@ int main(int argc, const char **argv) @@ -544,6 +544,10 @@ int main(int argc, const char **argv)
for (i = 1; i < argc; i++) {
const char *arg = argv[i];

if (!strcmp(arg, "--")) {
i++;
break;
}
if (!strcmp(arg, "-z")) {
line_terminator = 0;
continue;

2
mailsplit.c

@ -128,7 +128,7 @@ int main(int argc, char **argv) @@ -128,7 +128,7 @@ int main(int argc, char **argv)
unsigned long len = parse_email(map, size);
assert(len <= size);
sprintf(name, "%04d", ++nr);
fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0600);
fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd < 0) {
perror(name);
exit(1);

14
read-cache.c

@ -464,11 +464,15 @@ int read_cache(void) @@ -464,11 +464,15 @@ int read_cache(void)

errno = EBUSY;
if (active_cache)
return error("more than one cachefile");
return active_nr;

errno = ENOENT;
fd = open(get_index_file(), O_RDONLY);
if (fd < 0)
return (errno == ENOENT) ? 0 : error("open failed");
if (fd < 0) {
if (errno == ENOENT)
return 0;
die("index file open failed (%s)", strerror(errno));
}

size = 0; // avoid gcc warning
map = MAP_FAILED;
@ -480,7 +484,7 @@ int read_cache(void) @@ -480,7 +484,7 @@ int read_cache(void)
}
close(fd);
if (map == MAP_FAILED)
return error("mmap failed");
die("index file mmap failed (%s)", strerror(errno));

hdr = map;
if (verify_hdr(hdr, size) < 0)
@ -501,7 +505,7 @@ int read_cache(void) @@ -501,7 +505,7 @@ int read_cache(void)
unmap:
munmap(map, size);
errno = EINVAL;
return error("verify header failed");
die("index file corrupt");
}

#define WRITE_BUFFER_SIZE 8192

18
read-tree.c

@ -13,6 +13,8 @@ @@ -13,6 +13,8 @@
static int merge = 0;
static int update = 0;
static int index_only = 0;
static int nontrivial_merge = 0;
static int trivial_merges_only = 0;

static int head_idx = -1;
static int merge_size = 0;
@ -275,6 +277,9 @@ static int unpack_trees(merge_fn_t fn) @@ -275,6 +277,9 @@ static int unpack_trees(merge_fn_t fn)
if (unpack_trees_rec(posns, len, "", fn, &indpos))
return -1;

if (trivial_merges_only && nontrivial_merge)
die("Merge requires file-level merging");

check_updates(active_cache, active_nr);
return 0;
}
@ -460,6 +465,8 @@ static int threeway_merge(struct cache_entry **stages) @@ -460,6 +465,8 @@ static int threeway_merge(struct cache_entry **stages)
verify_uptodate(index);
}

nontrivial_merge = 1;

/* #2, #3, #4, #6, #7, #9, #11. */
count = 0;
if (!head_match || !remote_match) {
@ -629,9 +636,9 @@ int main(int argc, char **argv) @@ -629,9 +636,9 @@ int main(int argc, char **argv)
continue;
}

if (!strcmp(arg, "--head")) {
head_idx = stage - 1;
fn = threeway_merge;
if (!strcmp(arg, "--trivial")) {
trivial_merges_only = 1;
continue;
}

/* "-m" stands for "merge", meaning we start in stage 1 */
@ -657,7 +664,8 @@ int main(int argc, char **argv) @@ -657,7 +664,8 @@ int main(int argc, char **argv)
}
if ((update||index_only) && !merge)
usage(read_tree_usage);
if (merge && !fn) {

if (merge) {
if (stage < 2)
die("just how do you expect me to merge %d trees?", stage-1);
switch (stage - 1) {
@ -674,9 +682,7 @@ int main(int argc, char **argv) @@ -674,9 +682,7 @@ int main(int argc, char **argv)
fn = threeway_merge;
break;
}
}

if (head_idx < 0) {
if (stage - 1 >= 3)
head_idx = stage - 2;
else

187
refs.c

@ -2,19 +2,157 @@ @@ -2,19 +2,157 @@
#include "cache.h"

#include <errno.h>
#include <ctype.h>

static int read_ref(const char *refname, unsigned char *sha1)
/* We allow "recursive" symbolic refs. Only within reason, though */
#define MAXDEPTH 5

#ifndef USE_SYMLINK_HEAD
#define USE_SYMLINK_HEAD 1
#endif

int validate_symref(const char *path)
{
int ret = -1;
int fd = open(git_path("%s", refname), O_RDONLY);
struct stat st;
char *buf, buffer[256];
int len, fd;

if (lstat(path, &st) < 0)
return -1;

/* Make sure it is a "refs/.." symlink */
if (S_ISLNK(st.st_mode)) {
len = readlink(path, buffer, sizeof(buffer)-1);
if (len >= 5 && !memcmp("refs/", buffer, 5))
return 0;
return -1;
}

/*
* Anything else, just open it and try to see if it is a symbolic ref.
*/
fd = open(path, O_RDONLY);
if (fd < 0)
return -1;
len = read(fd, buffer, sizeof(buffer)-1);
close(fd);

/*
* Is it a symbolic ref?
*/
if (len < 4 || memcmp("ref:", buffer, 4))
return -1;
buf = buffer + 4;
len -= 4;
while (len && isspace(*buf))
buf++, len--;
if (len >= 5 && !memcmp("refs/", buffer, 5))
return 0;
return -1;
}

if (fd >= 0) {
char buffer[60];
if (read(fd, buffer, sizeof(buffer)) >= 40)
ret = get_sha1_hex(buffer, sha1);
const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
{
int depth = MAXDEPTH, len;
char buffer[256];

for (;;) {
struct stat st;
char *buf;
int fd;

if (--depth < 0)
return NULL;

/* Special case: non-existing file.
* Not having the refs/heads/new-branch is OK
* if we are writing into it, so is .git/HEAD
* that points at refs/heads/master still to be
* born. It is NOT OK if we are resolving for
* reading.
*/
if (lstat(path, &st) < 0) {
if (reading || errno != ENOENT)
return NULL;
memset(sha1, 0, 20);
return path;
}

/* Follow "normalized" - ie "refs/.." symlinks by hand */
if (S_ISLNK(st.st_mode)) {
len = readlink(path, buffer, sizeof(buffer)-1);
if (len >= 5 && !memcmp("refs/", buffer, 5)) {
path = git_path("%.*s", len, buffer);
continue;
}
}

/*
* Anything else, just open it and try to use it as
* a ref
*/
fd = open(path, O_RDONLY);
if (fd < 0)
return NULL;
len = read(fd, buffer, sizeof(buffer)-1);
close(fd);

/*
* Is it a symbolic ref?
*/
if (len < 4 || memcmp("ref:", buffer, 4))
break;
buf = buffer + 4;
len -= 4;
while (len && isspace(*buf))
buf++, len--;
while (len && isspace(buf[len-1]))
buf[--len] = 0;
path = git_path("%.*s", len, buf);
}
return ret;
if (len < 40 || get_sha1_hex(buffer, sha1))
return NULL;
return path;
}

int create_symref(const char *git_HEAD, const char *refs_heads_master)
{
#if USE_SYMLINK_HEAD
unlink(git_HEAD);
return symlink(refs_heads_master, git_HEAD);
#else
const char *lockpath;
char ref[1000];
int fd, len, written;

len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
if (sizeof(ref) <= len) {
error("refname too long: %s", refs_heads_master);
return -1;
}
lockpath = mkpath("%s.lock", git_HEAD);
fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
written = write(fd, ref, len);
close(fd);
if (written != len) {
unlink(lockpath);
error("Unable to write to %s", lockpath);
return -2;
}
if (rename(lockpath, git_HEAD) < 0) {
unlink(lockpath);
error("Unable to create %s", git_HEAD);
return -3;
}
return 0;
#endif
}

int read_ref(const char *filename, unsigned char *sha1)
{
if (resolve_ref(filename, sha1, 1))
return 0;
return -1;
}

static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1))
@ -54,7 +192,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u @@ -54,7 +192,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
break;
continue;
}
if (read_ref(path, sha1) < 0)
if (read_ref(git_path("%s", path), sha1) < 0)
continue;
if (!has_sha1_file(sha1))
continue;
@ -71,7 +209,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u @@ -71,7 +209,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
{
unsigned char sha1[20];
if (!read_ref("HEAD", sha1))
if (!read_ref(git_path("HEAD"), sha1))
return fn("HEAD", sha1);
return 0;
}
@ -101,33 +239,14 @@ static char *ref_lock_file_name(const char *ref) @@ -101,33 +239,14 @@ static char *ref_lock_file_name(const char *ref)
return ret;
}

static int read_ref_file(const char *filename, unsigned char *sha1) {
int fd = open(filename, O_RDONLY);
char hex[41];
if (fd < 0) {
return error("Couldn't open %s\n", filename);
}
if ((read(fd, hex, 41) < 41) ||
(hex[40] != '\n') ||
get_sha1_hex(hex, sha1)) {
error("Couldn't read a hash from %s\n", filename);
close(fd);
return -1;
}
close(fd);
return 0;
}

int get_ref_sha1(const char *ref, unsigned char *sha1)
{
char *filename;
int retval;
const char *filename;

if (check_ref_format(ref))
return -1;
filename = ref_file_name(ref);
retval = read_ref_file(filename, sha1);
free(filename);
return retval;
filename = git_path("refs/%s", ref);
return read_ref(filename, sha1);
}

static int lock_ref_file(const char *filename, const char *lock_filename,
@ -140,7 +259,7 @@ static int lock_ref_file(const char *filename, const char *lock_filename, @@ -140,7 +259,7 @@ static int lock_ref_file(const char *filename, const char *lock_filename,
return error("Couldn't open lock file for %s: %s",
filename, strerror(errno));
}
retval = read_ref_file(filename, current_sha1);
retval = read_ref(filename, current_sha1);
if (old_sha1) {
if (retval) {
close(fd);

16
setup.c

@ -76,18 +76,20 @@ const char **get_pathspec(const char *prefix, const char **pathspec) @@ -76,18 +76,20 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
* Test it it looks like we're at the top
* level git directory. We want to see a
*
* - a HEAD symlink and a refs/ directory under ".git"
* - either a .git/objects/ directory _or_ the proper
* GIT_OBJECT_DIRECTORY environment variable
* - a refs/ directory under ".git"
* - either a HEAD symlink or a HEAD file that is formatted as
* a proper "ref:".
*/
static int is_toplevel_directory(void)
{
struct stat st;

return !lstat(".git/HEAD", &st) &&
S_ISLNK(st.st_mode) &&
!access(".git/refs/", X_OK) &&
(getenv(DB_ENVIRONMENT) || !access(".git/objects/", X_OK));
if (access(".git/refs/", X_OK) ||
access(getenv(DB_ENVIRONMENT) ?
getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
validate_symref(".git/HEAD"))
return 0;
return 1;
}

const char *setup_git_directory(void)

2
sha1_file.c

@ -20,6 +20,8 @@ @@ -20,6 +20,8 @@
#endif
#endif

const unsigned char null_sha1[20] = { 0, };

static unsigned int sha1_file_open_flag = O_NOATIME;

static unsigned hexval(char c)

17
sha1_name.c

@ -119,21 +119,6 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1) @@ -119,21 +119,6 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1)
return -1;
}

static int get_sha1_file(const char *path, unsigned char *result)
{
char buffer[60];
int fd = open(path, O_RDONLY);
int len;

if (fd < 0)
return -1;
len = read(fd, buffer, sizeof(buffer));
close(fd);
if (len < 40)
return -1;
return get_sha1_hex(buffer, result);
}

static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
static const char *prefix[] = {
@ -150,7 +135,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) @@ -150,7 +135,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)

for (p = prefix; *p; p++) {
char *pathname = git_path("%s/%.*s", *p, len, str);
if (!get_sha1_file(pathname, sha1))
if (!read_ref(pathname, sha1))
return 0;
}


13
show-branch.c

@ -349,6 +349,7 @@ int main(int ac, char **av) @@ -349,6 +349,7 @@ int main(int ac, char **av)
int all_heads = 0, all_tags = 0;
int all_mask, all_revs, shown_merge_point;
char head_path[128];
const char *head_path_p;
int head_path_len;
unsigned char head_sha1[20];
int merge_base = 0;
@ -430,11 +431,15 @@ int main(int ac, char **av) @@ -430,11 +431,15 @@ int main(int ac, char **av)
if (0 <= extra)
join_revs(&list, &seen, num_rev, extra);

head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1);
if ((head_path_len < 0) || get_sha1("HEAD", head_sha1))
head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
if (head_path_p) {
head_path_len = strlen(head_path_p);
memcpy(head_path, head_path_p, head_path_len + 1);
}
else {
head_path_len = 0;
head_path[0] = 0;
else
head_path[head_path_len] = 0;
}

if (merge_base)
return show_merge_base(seen, num_rev);

34
symbolic-ref.c

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
#include "cache.h"

static const char git_symbolic_ref_usage[] =
"git-symbolic-ref name [ref]";

static int check_symref(const char *HEAD)
{
unsigned char sha1[20];
const char *git_HEAD = strdup(git_path("%s", HEAD));
const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
if (git_refs_heads_master) {
/* we want to strip the .git/ part */
int pfxlen = strlen(git_HEAD) - strlen(HEAD);
puts(git_refs_heads_master + pfxlen);
}
else
die("No such ref: %s", HEAD);
}

int main(int argc, const char **argv)
{
setup_git_directory();
switch (argc) {
case 2:
check_symref(argv[1]);
break;
case 3:
create_symref(strdup(git_path("%s", argv[1])), argv[2]);
break;
default:
usage(git_symbolic_ref_usage);
}
return 0;
}

1
t/Makefile

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@

#GIT_TEST_OPTS=--verbose --debug
SHELL_PATH ?= $(SHELL)
TAR ?= $(TAR)

T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)


69
t/t3002-ls-files-dashpath.sh

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
#!/bin/sh
#
# Copyright (c) 2005 Junio C Hamano
#

test_description='git-ls-files test (-- to terminate the path list).

This test runs git-ls-files --others with the following on the
filesystem.

path0 - a file
-foo - a file with a funny name.
-- - another file with a funny name.
'
. ./test-lib.sh

test_expect_success \
setup \
'echo frotz >path0 &&
echo frotz >./-foo &&
echo frotz >./--'

test_expect_success \
'git-ls-files without path restriction.' \
'git-ls-files --others >output &&
diff -u output - <<EOF
--
-foo
output
path0
EOF
'

test_expect_success \
'git-ls-files with path restriction.' \
'git-ls-files --others path0 >output &&
diff -u output - <<EOF
path0
EOF
'

test_expect_success \
'git-ls-files with path restriction with --.' \
'git-ls-files --others -- path0 >output &&
diff -u output - <<EOF
path0
EOF
'

test_expect_success \
'git-ls-files with path restriction with -- --.' \
'git-ls-files --others -- -- >output &&
diff -u output - <<EOF
--
EOF
'

test_expect_success \
'git-ls-files with no path restriction.' \
'git-ls-files --others -- >output &&
diff -u output - <<EOF
--
-foo
output
path0
EOF
'

test_done

6
t/t5000-tar-tree.sh

@ -50,7 +50,7 @@ test_expect_success \ @@ -50,7 +50,7 @@ test_expect_success \

test_expect_success \
'validate file modification time' \
'TZ=GMT tar tvf b.tar a/a |
'TZ=GMT $TAR tvf b.tar a/a |
awk \{print\ \$4,\ \(length\(\$5\)\<7\)\ ?\ \$5\":00\"\ :\ \$5\} \
>b.mtime &&
echo "2005-05-27 22:00:00" >expected.mtime &&
@ -63,7 +63,7 @@ test_expect_success \ @@ -63,7 +63,7 @@ test_expect_success \

test_expect_success \
'extract tar archive' \
'(cd b && tar xf -) <b.tar'
'(cd b && $TAR xf -) <b.tar'

test_expect_success \
'validate filenames' \
@ -80,7 +80,7 @@ test_expect_success \ @@ -80,7 +80,7 @@ test_expect_success \

test_expect_success \
'extract tar archive with prefix' \
'(cd c && tar xf -) <c.tar'
'(cd c && $TAR xf -) <c.tar'

test_expect_success \
'validate filenames with prefix' \

6
t/t5400-send-pack.sh

@ -20,12 +20,12 @@ test_expect_success setup ' @@ -20,12 +20,12 @@ test_expect_success setup '
commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
parent=$commit || return 1
done &&
echo "$commit" >.git/HEAD &&
git-update-ref HEAD "$commit" &&
git-clone -l ./. victim &&
cd victim &&
git-log &&
cd .. &&
echo $zero >.git/HEAD &&
git-update-ref HEAD "$zero" &&
parent=$zero &&
for i in $cnt
do
@ -33,7 +33,7 @@ test_expect_success setup ' @@ -33,7 +33,7 @@ test_expect_success setup '
commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
parent=$commit || return 1
done &&
echo "$commit" >.git/HEAD &&
git-update-ref HEAD "$commit" &&
echo Rebase &&
git-log'


29
t/t6002-rev-list-bisect.sh

@ -7,20 +7,6 @@ test_description='Tests git-rev-list --bisect functionality' @@ -7,20 +7,6 @@ test_description='Tests git-rev-list --bisect functionality'
. ./test-lib.sh
. ../t6000lib.sh # t6xxx specific functions

bc_expr()
{
bc <<EOF
scale=1
define abs(x) {
if (x>=0) { return (x); } else { return (-x); }
}
define floor(x) {
save=scale; scale=0; result=x/1; scale=save; return (result);
}
$*
EOF
}

# usage: test_bisection max-diff bisect-option head ^prune...
#
# e.g. test_bisection 1 --bisect l1 ^l0
@ -35,8 +21,19 @@ test_bisection_diff() @@ -35,8 +21,19 @@ test_bisection_diff()
_head=$1
shift 1
_bisection_size=$(git-rev-list $_bisection "$@" | wc -l)
[ -n "$_list_size" -a -n "$_bisection_size" ] || error "test_bisection_diff failed"
test_expect_success "bisection diff $_bisect_option $_head $* <= $_max_diff" "[ $(bc_expr "floor(abs($_list_size/2)-$_bisection_size)") -le $_max_diff ]"
[ -n "$_list_size" -a -n "$_bisection_size" ] ||
error "test_bisection_diff failed"

# Test if bisection size is close to half of list size within
# tolerance.
#
_bisect_err=`expr $_list_size - $_bisection_size \* 2`
test "$_bisect_err" -lt 0 && _bisect_err=`expr 0 - $_bisect_err`
_bisect_err=`expr $_bisect_err / 2` ; # floor

test_expect_success \
"bisection diff $_bisect_option $_head $* <= $_max_diff" \
'test $_bisect_err -le $_max_diff'
}

date >path0

2
tar-tree.c

@ -353,6 +353,8 @@ static void traverse_tree(void *buffer, unsigned long size, @@ -353,6 +353,8 @@ static void traverse_tree(void *buffer, unsigned long size,

if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
die("corrupt 'tree' file");
if (S_ISDIR(mode) || S_ISREG(mode))
mode |= (mode & 0100) ? 0777 : 0666;
buffer = sha1 + 20;
size -= namelen + 20;


24
update-index.c

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
* like "git-update-index *" and suddenly having all the object
* files be revision controlled.
*/
static int allow_add = 0, allow_remove = 0, allow_replace = 0, not_new = 0, quiet = 0, info_only = 0;
static int allow_add = 0, allow_remove = 0, allow_replace = 0, allow_unmerged = 0, not_new = 0, quiet = 0, info_only = 0;
static int force_remove;

/* Three functions to allow overloaded pointer return; see linux/err.h */
@ -135,7 +135,7 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce) @@ -135,7 +135,7 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce)

changed = ce_match_stat(ce, &st);
if (!changed)
return ce;
return NULL;

if (ce_modified(ce, &st))
return ERR_PTR(-EINVAL);
@ -156,16 +156,20 @@ static int refresh_cache(void) @@ -156,16 +156,20 @@ static int refresh_cache(void)
struct cache_entry *ce, *new;
ce = active_cache[i];
if (ce_stage(ce)) {
printf("%s: needs merge\n", ce->name);
has_errors = 1;
while ((i < active_nr) &&
! strcmp(active_cache[i]->name, ce->name))
i++;
i--;
if (allow_unmerged)
continue;
printf("%s: needs merge\n", ce->name);
has_errors = 1;
continue;
}

new = refresh_entry(ce);
if (!new)
continue;
if (IS_ERR(new)) {
if (not_new && PTR_ERR(new) == -ENOENT)
continue;
@ -335,6 +339,10 @@ int main(int argc, const char **argv) @@ -335,6 +339,10 @@ int main(int argc, const char **argv)
allow_remove = 1;
continue;
}
if (!strcmp(path, "--unmerged")) {
allow_unmerged = 1;
continue;
}
if (!strcmp(path, "--refresh")) {
has_errors |= refresh_cache();
continue;
@ -383,9 +391,11 @@ int main(int argc, const char **argv) @@ -383,9 +391,11 @@ int main(int argc, const char **argv)
update_one(buf.buf, prefix, prefix_length);
}
}
if (write_cache(newfd, active_cache, active_nr) ||
commit_index_file(&cache_file))
die("Unable to write new cachefile");
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_index_file(&cache_file))
die("Unable to write new cachefile");
}

return has_errors ? 1 : 0;
}

49
update-ref.c

@ -4,53 +4,6 @@ @@ -4,53 +4,6 @@

static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";

#define MAXDEPTH 5

static const char *resolve_ref(const char *path, unsigned char *sha1)
{
int depth = MAXDEPTH, len;
char buffer[256];

for (;;) {
struct stat st;
int fd;

if (--depth < 0)
return NULL;

/* Special case: non-existing file */
if (lstat(path, &st) < 0) {
if (errno != ENOENT)
return NULL;
memset(sha1, 0, 20);
return path;
}

/* Follow "normalized" - ie "refs/.." symlinks by hand */
if (S_ISLNK(st.st_mode)) {
len = readlink(path, buffer, sizeof(buffer)-1);
if (len >= 5 && !memcmp("refs/", buffer, 5)) {
path = git_path("%.*s", len, buffer);
continue;
}
}

/*
* Anything else, just open it and try to use it as
* a ref
*/
fd = open(path, O_RDONLY);
if (fd < 0)
return NULL;
len = read(fd, buffer, sizeof(buffer)-1);
close(fd);
break;
}
if (len < 40 || get_sha1_hex(buffer, sha1))
return NULL;
return path;
}

static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
{
char buf[40];
@ -84,7 +37,7 @@ int main(int argc, char **argv) @@ -84,7 +37,7 @@ int main(int argc, char **argv)
if (oldval && get_sha1(oldval, oldsha1) < 0)
die("%s: not a valid old SHA1", oldval);

path = resolve_ref(git_path("%s", refname), currsha1);
path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);
if (!path)
die("No such ref: %s", refname);


4
usage.c

@ -15,7 +15,7 @@ static void report(const char *prefix, const char *err, va_list params) @@ -15,7 +15,7 @@ static void report(const char *prefix, const char *err, va_list params)
void usage(const char *err)
{
fprintf(stderr, "usage: %s\n", err);
exit(1);
exit(129);
}

void die(const char *err, ...)
@ -25,7 +25,7 @@ void die(const char *err, ...) @@ -25,7 +25,7 @@ void die(const char *err, ...)
va_start(params, err);
report("fatal: ", err, params);
va_end(params);
exit(1);
exit(128);
}

int error(const char *err, ...)

Loading…
Cancel
Save