Merge branch 'jc/merge'
* branch 'jc/merge': git-merge: do not leak rev-parse output used for checking internally. git-merge: tighten error checking. merge: allow merging into a yet-to-be-born branch. git-merge: make it usable as the first class UI remove merge-recursive-old
This commit is contained in:
commit
88ffc1f28a
1
.gitignore
vendored
1
.gitignore
vendored
@ -66,7 +66,6 @@ git-merge-one-file
|
|||||||
git-merge-ours
|
git-merge-ours
|
||||||
git-merge-recur
|
git-merge-recur
|
||||||
git-merge-recursive
|
git-merge-recursive
|
||||||
git-merge-recursive-old
|
|
||||||
git-merge-resolve
|
git-merge-resolve
|
||||||
git-merge-stupid
|
git-merge-stupid
|
||||||
git-mktag
|
git-mktag
|
||||||
|
@ -8,12 +8,14 @@ git-merge - Grand Unified Merge Driver
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
'git-merge' [-n] [--no-commit] [-s <strategy>]... <msg> <head> <remote> <remote>...
|
[verse]
|
||||||
|
'git-merge' [-n] [--no-commit] [--squash] [-s <strategy>]...
|
||||||
|
[--reflog-action=<action>]
|
||||||
|
-m=<msg> <remote> <remote>...
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
This is the top-level user interface to the merge machinery
|
This is the top-level interface to the merge machinery
|
||||||
which drives multiple merge strategy scripts.
|
which drives multiple merge strategy scripts.
|
||||||
|
|
||||||
|
|
||||||
@ -27,13 +29,19 @@ include::merge-options.txt[]
|
|||||||
to give a good default for automated `git-merge` invocations.
|
to give a good default for automated `git-merge` invocations.
|
||||||
|
|
||||||
<head>::
|
<head>::
|
||||||
our branch head commit.
|
Our branch head commit. This has to be `HEAD`, so new
|
||||||
|
syntax does not require it
|
||||||
|
|
||||||
<remote>::
|
<remote>::
|
||||||
other branch head merged into our branch. You need at
|
Other branch head merged into our branch. You need at
|
||||||
least one <remote>. Specifying more than one <remote>
|
least one <remote>. Specifying more than one <remote>
|
||||||
obviously means you are trying an Octopus.
|
obviously means you are trying an Octopus.
|
||||||
|
|
||||||
|
--reflog-action=<action>::
|
||||||
|
This is used internally when `git-pull` calls this command
|
||||||
|
to record that the merge was created by `pull` command
|
||||||
|
in the `ref-log` entry that results from the merge.
|
||||||
|
|
||||||
include::merge-strategies.txt[]
|
include::merge-strategies.txt[]
|
||||||
|
|
||||||
|
|
||||||
|
3
INSTALL
3
INSTALL
@ -99,9 +99,6 @@ Issues of note:
|
|||||||
- "perl" and POSIX-compliant shells are needed to use most of
|
- "perl" and POSIX-compliant shells are needed to use most of
|
||||||
the barebone Porcelainish scripts.
|
the barebone Porcelainish scripts.
|
||||||
|
|
||||||
- "python" 2.3 or more recent; if you have 2.3, you may need
|
|
||||||
to build with "make WITH_OWN_SUBPROCESS_PY=YesPlease".
|
|
||||||
|
|
||||||
- Some platform specific issues are dealt with Makefile rules,
|
- Some platform specific issues are dealt with Makefile rules,
|
||||||
but depending on your specific installation, you may not
|
but depending on your specific installation, you may not
|
||||||
have all the libraries/tools needed, or you may have
|
have all the libraries/tools needed, or you may have
|
||||||
|
45
Makefile
45
Makefile
@ -69,8 +69,6 @@ all:
|
|||||||
#
|
#
|
||||||
# Define NO_MMAP if you want to avoid mmap.
|
# Define NO_MMAP if you want to avoid mmap.
|
||||||
#
|
#
|
||||||
# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
|
|
||||||
#
|
|
||||||
# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
|
# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
|
||||||
#
|
#
|
||||||
# Define NO_SOCKADDR_STORAGE if your platform does not have struct
|
# Define NO_SOCKADDR_STORAGE if your platform does not have struct
|
||||||
@ -116,7 +114,6 @@ prefix = $(HOME)
|
|||||||
bindir = $(prefix)/bin
|
bindir = $(prefix)/bin
|
||||||
gitexecdir = $(bindir)
|
gitexecdir = $(bindir)
|
||||||
template_dir = $(prefix)/share/git-core/templates/
|
template_dir = $(prefix)/share/git-core/templates/
|
||||||
GIT_PYTHON_DIR = $(prefix)/share/git-core/python
|
|
||||||
# DESTDIR=
|
# DESTDIR=
|
||||||
|
|
||||||
# default configuration for gitweb
|
# default configuration for gitweb
|
||||||
@ -135,7 +132,7 @@ GITWEB_FAVICON = git-favicon.png
|
|||||||
GITWEB_SITE_HEADER =
|
GITWEB_SITE_HEADER =
|
||||||
GITWEB_SITE_FOOTER =
|
GITWEB_SITE_FOOTER =
|
||||||
|
|
||||||
export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
|
export prefix bindir gitexecdir template_dir
|
||||||
|
|
||||||
CC = gcc
|
CC = gcc
|
||||||
AR = ar
|
AR = ar
|
||||||
@ -179,12 +176,8 @@ SCRIPT_PERL = \
|
|||||||
git-svnimport.perl git-cvsexportcommit.perl \
|
git-svnimport.perl git-cvsexportcommit.perl \
|
||||||
git-send-email.perl git-svn.perl
|
git-send-email.perl git-svn.perl
|
||||||
|
|
||||||
SCRIPT_PYTHON = \
|
|
||||||
git-merge-recursive-old.py
|
|
||||||
|
|
||||||
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
|
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
|
||||||
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
|
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
|
||||||
$(patsubst %.py,%,$(SCRIPT_PYTHON)) \
|
|
||||||
git-cherry-pick git-status git-instaweb
|
git-cherry-pick git-status git-instaweb
|
||||||
|
|
||||||
# ... and all the rest that could be moved out of bindir to gitexecdir
|
# ... and all the rest that could be moved out of bindir to gitexecdir
|
||||||
@ -227,12 +220,6 @@ endif
|
|||||||
ifndef PERL_PATH
|
ifndef PERL_PATH
|
||||||
PERL_PATH = /usr/bin/perl
|
PERL_PATH = /usr/bin/perl
|
||||||
endif
|
endif
|
||||||
ifndef PYTHON_PATH
|
|
||||||
PYTHON_PATH = /usr/bin/python
|
|
||||||
endif
|
|
||||||
|
|
||||||
PYMODULES = \
|
|
||||||
gitMergeCommon.py
|
|
||||||
|
|
||||||
LIB_FILE=libgit.a
|
LIB_FILE=libgit.a
|
||||||
XDIFF_LIB=xdiff/lib.a
|
XDIFF_LIB=xdiff/lib.a
|
||||||
@ -423,16 +410,6 @@ endif
|
|||||||
-include config.mak.autogen
|
-include config.mak.autogen
|
||||||
-include config.mak
|
-include config.mak
|
||||||
|
|
||||||
ifdef WITH_OWN_SUBPROCESS_PY
|
|
||||||
PYMODULES += compat/subprocess.py
|
|
||||||
else
|
|
||||||
ifeq ($(NO_PYTHON),)
|
|
||||||
ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
|
|
||||||
PYMODULES += compat/subprocess.py
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifndef NO_CURL
|
ifndef NO_CURL
|
||||||
ifdef CURLDIR
|
ifdef CURLDIR
|
||||||
# This is still problematic -- gcc does not always want -R.
|
# This is still problematic -- gcc does not always want -R.
|
||||||
@ -574,8 +551,6 @@ prefix_SQ = $(subst ','\'',$(prefix))
|
|||||||
|
|
||||||
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
|
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
|
||||||
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
|
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
|
||||||
PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
|
|
||||||
GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
|
|
||||||
|
|
||||||
LIBS = $(GITLIBS) $(EXTLIBS)
|
LIBS = $(GITLIBS) $(EXTLIBS)
|
||||||
|
|
||||||
@ -622,7 +597,6 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
|
|||||||
-e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
|
-e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
|
||||||
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
|
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
|
||||||
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
|
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
|
||||||
-e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
|
|
||||||
$@.sh >$@+
|
$@.sh >$@+
|
||||||
chmod +x $@+
|
chmod +x $@+
|
||||||
mv $@+ $@
|
mv $@+ $@
|
||||||
@ -644,15 +618,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
|
|||||||
chmod +x $@+
|
chmod +x $@+
|
||||||
mv $@+ $@
|
mv $@+ $@
|
||||||
|
|
||||||
$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py GIT-CFLAGS
|
|
||||||
rm -f $@ $@+
|
|
||||||
sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
|
|
||||||
-e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \
|
|
||||||
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
|
|
||||||
$@.py >$@+
|
|
||||||
chmod +x $@+
|
|
||||||
mv $@+ $@
|
|
||||||
|
|
||||||
git-cherry-pick: git-revert
|
git-cherry-pick: git-revert
|
||||||
cp $< $@+
|
cp $< $@+
|
||||||
mv $@+ $@
|
mv $@+ $@
|
||||||
@ -689,7 +654,6 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
|
|||||||
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
|
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
|
||||||
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
|
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
|
||||||
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
|
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
|
||||||
-e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
|
|
||||||
-e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \
|
-e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \
|
||||||
-e '/@@GITWEB_CGI@@/d' \
|
-e '/@@GITWEB_CGI@@/d' \
|
||||||
-e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
|
-e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
|
||||||
@ -709,7 +673,6 @@ configure: configure.ac
|
|||||||
git$X git.spec \
|
git$X git.spec \
|
||||||
$(patsubst %.sh,%,$(SCRIPT_SH)) \
|
$(patsubst %.sh,%,$(SCRIPT_SH)) \
|
||||||
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
|
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
|
||||||
$(patsubst %.py,%,$(SCRIPT_PYTHON)) \
|
|
||||||
: GIT-VERSION-FILE
|
: GIT-VERSION-FILE
|
||||||
|
|
||||||
%.o: %.c GIT-CFLAGS
|
%.o: %.c GIT-CFLAGS
|
||||||
@ -783,7 +746,7 @@ tags:
|
|||||||
find . -name '*.[hcS]' -print | xargs ctags -a
|
find . -name '*.[hcS]' -print | xargs ctags -a
|
||||||
|
|
||||||
### Detect prefix changes
|
### Detect prefix changes
|
||||||
TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):$(GIT_PYTHON_DIR_SQ):\
|
TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
|
||||||
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
|
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
|
||||||
|
|
||||||
GIT-CFLAGS: .FORCE-GIT-CFLAGS
|
GIT-CFLAGS: .FORCE-GIT-CFLAGS
|
||||||
@ -799,7 +762,6 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
|
|||||||
# However, the environment gets quite big, and some programs have problems
|
# However, the environment gets quite big, and some programs have problems
|
||||||
# with that.
|
# with that.
|
||||||
|
|
||||||
export NO_PYTHON
|
|
||||||
export NO_SVN_TESTS
|
export NO_SVN_TESTS
|
||||||
|
|
||||||
test: all
|
test: all
|
||||||
@ -834,8 +796,6 @@ install: all
|
|||||||
$(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
|
$(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
|
||||||
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
|
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
|
||||||
$(MAKE) -C perl install
|
$(MAKE) -C perl install
|
||||||
$(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
|
|
||||||
$(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
|
|
||||||
if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
|
if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
|
||||||
then \
|
then \
|
||||||
ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
|
ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
|
||||||
@ -922,7 +882,6 @@ check-docs::
|
|||||||
case "$$v" in \
|
case "$$v" in \
|
||||||
git-merge-octopus | git-merge-ours | git-merge-recursive | \
|
git-merge-octopus | git-merge-ours | git-merge-recursive | \
|
||||||
git-merge-resolve | git-merge-stupid | git-merge-recur | \
|
git-merge-resolve | git-merge-stupid | git-merge-recur | \
|
||||||
git-merge-recursive-old | \
|
|
||||||
git-ssh-pull | git-ssh-push ) continue ;; \
|
git-ssh-pull | git-ssh-push ) continue ;; \
|
||||||
esac ; \
|
esac ; \
|
||||||
test -f "Documentation/$$v.txt" || \
|
test -f "Documentation/$$v.txt" || \
|
||||||
|
1149
compat/subprocess.py
1149
compat/subprocess.py
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,6 @@ bindir = @bindir@
|
|||||||
#gitexecdir = @libexecdir@/git-core/
|
#gitexecdir = @libexecdir@/git-core/
|
||||||
datarootdir = @datarootdir@
|
datarootdir = @datarootdir@
|
||||||
template_dir = @datadir@/git-core/templates/
|
template_dir = @datadir@/git-core/templates/
|
||||||
GIT_PYTHON_DIR = @datadir@/git-core/python
|
|
||||||
|
|
||||||
mandir=@mandir@
|
mandir=@mandir@
|
||||||
|
|
||||||
@ -23,7 +22,6 @@ VPATH = @srcdir@
|
|||||||
export exec_prefix mandir
|
export exec_prefix mandir
|
||||||
export srcdir VPATH
|
export srcdir VPATH
|
||||||
|
|
||||||
NO_PYTHON=@NO_PYTHON@
|
|
||||||
NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
|
NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
|
||||||
NO_OPENSSL=@NO_OPENSSL@
|
NO_OPENSSL=@NO_OPENSSL@
|
||||||
NO_CURL=@NO_CURL@
|
NO_CURL=@NO_CURL@
|
||||||
|
39
configure.ac
39
configure.ac
@ -75,20 +75,6 @@ GIT_ARG_SET_PATH(shell)
|
|||||||
# Define PERL_PATH to provide path to Perl.
|
# Define PERL_PATH to provide path to Perl.
|
||||||
GIT_ARG_SET_PATH(perl)
|
GIT_ARG_SET_PATH(perl)
|
||||||
#
|
#
|
||||||
# Define PYTHON_PATH to provide path to Python.
|
|
||||||
AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python])
|
|
||||||
AS_HELP_STRING([--without-python], [don't use python scripts])],
|
|
||||||
[if test "$withval" = "no"; then \
|
|
||||||
NO_PYTHON=YesPlease; \
|
|
||||||
elif test "$withval" = "yes"; then \
|
|
||||||
NO_PYTHON=; \
|
|
||||||
else \
|
|
||||||
NO_PYTHON=; \
|
|
||||||
PYTHON_PATH=$withval; \
|
|
||||||
fi; \
|
|
||||||
])
|
|
||||||
AC_SUBST(NO_PYTHON)
|
|
||||||
AC_SUBST(PYTHON_PATH)
|
|
||||||
|
|
||||||
|
|
||||||
## Checks for programs.
|
## Checks for programs.
|
||||||
@ -98,18 +84,6 @@ AC_PROG_CC([cc gcc])
|
|||||||
#AC_PROG_INSTALL # needs install-sh or install.sh in sources
|
#AC_PROG_INSTALL # needs install-sh or install.sh in sources
|
||||||
AC_CHECK_TOOL(AR, ar, :)
|
AC_CHECK_TOOL(AR, ar, :)
|
||||||
AC_CHECK_PROGS(TAR, [gtar tar])
|
AC_CHECK_PROGS(TAR, [gtar tar])
|
||||||
#
|
|
||||||
# Define PYTHON_PATH to provide path to Python.
|
|
||||||
if test -z "$NO_PYTHON"; then
|
|
||||||
if test -z "$PYTHON_PATH"; then
|
|
||||||
AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2])
|
|
||||||
fi
|
|
||||||
if test -n "$PYTHON_PATH"; then
|
|
||||||
GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@])
|
|
||||||
NO_PYTHON=""
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
## Checks for libraries.
|
## Checks for libraries.
|
||||||
AC_MSG_NOTICE([CHECKS for libraries])
|
AC_MSG_NOTICE([CHECKS for libraries])
|
||||||
@ -262,22 +236,9 @@ AC_SUBST(NO_SETENV)
|
|||||||
# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
|
# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
|
||||||
# Enable it on Windows. By default, symrefs are still used.
|
# Enable it on Windows. By default, symrefs are still used.
|
||||||
#
|
#
|
||||||
# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
|
|
||||||
AC_CACHE_CHECK([for subprocess.py],
|
|
||||||
[ac_cv_python_has_subprocess_py],
|
|
||||||
[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then
|
|
||||||
ac_cv_python_has_subprocess_py=yes
|
|
||||||
else
|
|
||||||
ac_cv_python_has_subprocess_py=no
|
|
||||||
fi])
|
|
||||||
if test $ac_cv_python_has_subprocess_py != yes; then
|
|
||||||
GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease])
|
|
||||||
fi
|
|
||||||
#
|
|
||||||
# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
|
# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
|
||||||
# a missing newline at the end of the file.
|
# a missing newline at the end of the file.
|
||||||
|
|
||||||
|
|
||||||
## Site configuration (override autodetection)
|
## Site configuration (override autodetection)
|
||||||
## --with-PACKAGE[=ARG] and --without-PACKAGE
|
## --with-PACKAGE[=ARG] and --without-PACKAGE
|
||||||
AC_MSG_NOTICE([CHECKS for site configuration])
|
AC_MSG_NOTICE([CHECKS for site configuration])
|
||||||
|
@ -1,944 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# Copyright (C) 2005 Fredrik Kuivinen
|
|
||||||
#
|
|
||||||
|
|
||||||
import sys
|
|
||||||
sys.path.append('''@@GIT_PYTHON_PATH@@''')
|
|
||||||
|
|
||||||
import math, random, os, re, signal, tempfile, stat, errno, traceback
|
|
||||||
from heapq import heappush, heappop
|
|
||||||
from sets import Set
|
|
||||||
|
|
||||||
from gitMergeCommon import *
|
|
||||||
|
|
||||||
outputIndent = 0
|
|
||||||
def output(*args):
|
|
||||||
sys.stdout.write(' '*outputIndent)
|
|
||||||
printList(args)
|
|
||||||
|
|
||||||
originalIndexFile = os.environ.get('GIT_INDEX_FILE',
|
|
||||||
os.environ.get('GIT_DIR', '.git') + '/index')
|
|
||||||
temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
|
|
||||||
'/merge-recursive-tmp-index'
|
|
||||||
def setupIndex(temporary):
|
|
||||||
try:
|
|
||||||
os.unlink(temporaryIndexFile)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
if temporary:
|
|
||||||
newIndex = temporaryIndexFile
|
|
||||||
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, ancestor=None):
|
|
||||||
'''Merge the commits h1 and h2, return the resulting virtual
|
|
||||||
commit object and a flag indicating the cleanness of the merge.'''
|
|
||||||
assert(isinstance(h1, Commit) and isinstance(h2, Commit))
|
|
||||||
|
|
||||||
global outputIndent
|
|
||||||
|
|
||||||
output('Merging:')
|
|
||||||
output(h1)
|
|
||||||
output(h2)
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
if ancestor:
|
|
||||||
ca = [ancestor]
|
|
||||||
else:
|
|
||||||
assert(isinstance(graph, Graph))
|
|
||||||
ca = getCommonAncestors(graph, h1, h2)
|
|
||||||
output('found', len(ca), 'common ancestor(s):')
|
|
||||||
for x in ca:
|
|
||||||
output(x)
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
mergedCA = ca[0]
|
|
||||||
for h in ca[1:]:
|
|
||||||
outputIndent = callDepth+1
|
|
||||||
[mergedCA, dummy] = merge(mergedCA, h,
|
|
||||||
'Temporary merge branch 1',
|
|
||||||
'Temporary merge branch 2',
|
|
||||||
graph, callDepth+1)
|
|
||||||
outputIndent = callDepth
|
|
||||||
assert(isinstance(mergedCA, Commit))
|
|
||||||
|
|
||||||
global cacheOnly
|
|
||||||
if callDepth == 0:
|
|
||||||
setupIndex(False)
|
|
||||||
cacheOnly = False
|
|
||||||
else:
|
|
||||||
setupIndex(True)
|
|
||||||
runProgram(['git-read-tree', h1.tree()])
|
|
||||||
cacheOnly = True
|
|
||||||
|
|
||||||
[shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
|
|
||||||
branch1Name, branch2Name)
|
|
||||||
|
|
||||||
if graph and (clean or cacheOnly):
|
|
||||||
res = Commit(None, [h1, h2], tree=shaRes)
|
|
||||||
graph.addNode(res)
|
|
||||||
else:
|
|
||||||
res = None
|
|
||||||
|
|
||||||
return [res, clean]
|
|
||||||
|
|
||||||
getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
|
|
||||||
def getFilesAndDirs(tree):
|
|
||||||
files = Set()
|
|
||||||
dirs = Set()
|
|
||||||
out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree])
|
|
||||||
for l in out.split('\0'):
|
|
||||||
m = getFilesRE.match(l)
|
|
||||||
if m:
|
|
||||||
if m.group(2) == 'tree':
|
|
||||||
dirs.add(m.group(4))
|
|
||||||
elif m.group(2) == 'blob':
|
|
||||||
files.add(m.group(4))
|
|
||||||
|
|
||||||
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:
|
|
||||||
output('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()
|
|
||||||
try:
|
|
||||||
[out, code] = runProgram(['merge',
|
|
||||||
'-L', branch1Name + '/' + aPath,
|
|
||||||
'-L', 'orig/' + oPath,
|
|
||||||
'-L', branch2Name + '/' + bPath,
|
|
||||||
src1, orig, src2], returnCode=True)
|
|
||||||
except ProgramError, e:
|
|
||||||
print >>sys.stderr, e
|
|
||||||
die("Failed to execute 'merge'. merge(1) is used as the "
|
|
||||||
"file-level merge tool. Is 'merge' in your path?")
|
|
||||||
|
|
||||||
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 OSError:
|
|
||||||
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 setIndexStages(path,
|
|
||||||
oSHA1, oMode,
|
|
||||||
aSHA1, aMode,
|
|
||||||
bSHA1, bMode,
|
|
||||||
clear=True):
|
|
||||||
istring = []
|
|
||||||
if clear:
|
|
||||||
istring.append("0 " + ("0" * 40) + "\t" + path + "\0")
|
|
||||||
if oMode:
|
|
||||||
istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path))
|
|
||||||
if aMode:
|
|
||||||
istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path))
|
|
||||||
if bMode:
|
|
||||||
istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path))
|
|
||||||
|
|
||||||
runProgram(['git-update-index', '-z', '--index-info'],
|
|
||||||
input="".join(istring))
|
|
||||||
|
|
||||||
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
|
|
||||||
try:
|
|
||||||
os.removedirs(os.path.dirname(path))
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
branch = branch.replace('/', '_')
|
|
||||||
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(), 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
|
|
||||||
objects. The dictionary contains one entry for every path with a
|
|
||||||
non-zero stage entry.'''
|
|
||||||
|
|
||||||
lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
|
|
||||||
lines.pop()
|
|
||||||
|
|
||||||
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))
|
|
||||||
path = m.group(4)
|
|
||||||
|
|
||||||
e = res.get(path)
|
|
||||||
if not e:
|
|
||||||
e = CacheEntry(path)
|
|
||||||
res.add(e)
|
|
||||||
|
|
||||||
e.stages[stage].mode = mode
|
|
||||||
e.stages[stage].sha1 = sha1
|
|
||||||
else:
|
|
||||||
die('Error: Merge program failed: Unexpected output from',
|
|
||||||
'git-ls-files:', l)
|
|
||||||
return res
|
|
||||||
|
|
||||||
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.'''
|
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
res = CacheEntry(path)
|
|
||||||
|
|
||||||
[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]))
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
# 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:
|
|
||||||
endIndex = x
|
|
||||||
break
|
|
||||||
|
|
||||||
if len(path) > 0:
|
|
||||||
return '/'.join(path) + \
|
|
||||||
'/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
|
|
||||||
'/'.join(dstPath[endIndex:]) + '}'
|
|
||||||
else:
|
|
||||||
return src + ' => ' + dst
|
|
||||||
|
|
||||||
def processRenames(renamesA, renamesB, branchNameA, branchNameB):
|
|
||||||
srcNames = Set()
|
|
||||||
for x in renamesA:
|
|
||||||
srcNames.add(x.srcName)
|
|
||||||
for x in renamesB:
|
|
||||||
srcNames.add(x.srcName)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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:
|
|
||||||
output('CONFLICT (rename/rename): Rename',
|
|
||||||
fmtRename(path, ren1.dstName), 'in branch', branchName1,
|
|
||||||
'rename', fmtRename(path, ren2.dstName), 'in',
|
|
||||||
branchName2)
|
|
||||||
cleanMerge = False
|
|
||||||
|
|
||||||
if ren1.dstName in currentDirectorySet:
|
|
||||||
dstName1 = uniquePath(ren1.dstName, branchName1)
|
|
||||||
output(ren1.dstName, 'is a directory in', branchName2,
|
|
||||||
'adding as', dstName1, 'instead.')
|
|
||||||
removeFile(False, ren1.dstName)
|
|
||||||
else:
|
|
||||||
dstName1 = ren1.dstName
|
|
||||||
|
|
||||||
if ren2.dstName in currentDirectorySet:
|
|
||||||
dstName2 = uniquePath(ren2.dstName, branchName2)
|
|
||||||
output(ren2.dstName, 'is a directory in', branchName1,
|
|
||||||
'adding as', dstName2, 'instead.')
|
|
||||||
removeFile(False, ren2.dstName)
|
|
||||||
else:
|
|
||||||
dstName2 = ren2.dstName
|
|
||||||
setIndexStages(dstName1,
|
|
||||||
None, None,
|
|
||||||
ren1.dstSha, ren1.dstMode,
|
|
||||||
None, None)
|
|
||||||
setIndexStages(dstName2,
|
|
||||||
None, None,
|
|
||||||
None, None,
|
|
||||||
ren2.dstSha, ren2.dstMode)
|
|
||||||
|
|
||||||
else:
|
|
||||||
removeFile(True, ren1.srcName)
|
|
||||||
|
|
||||||
[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 or not clean:
|
|
||||||
output('Renaming', fmtRename(path, ren1.dstName))
|
|
||||||
|
|
||||||
if merge:
|
|
||||||
output('Auto-merging', ren1.dstName)
|
|
||||||
|
|
||||||
if not clean:
|
|
||||||
output('CONFLICT (content): merge conflict in',
|
|
||||||
ren1.dstName)
|
|
||||||
cleanMerge = False
|
|
||||||
|
|
||||||
if not cacheOnly:
|
|
||||||
setIndexStages(ren1.dstName,
|
|
||||||
ren1.srcSha, ren1.srcMode,
|
|
||||||
ren1.dstSha, ren1.dstMode,
|
|
||||||
ren2.dstSha, ren2.dstMode)
|
|
||||||
|
|
||||||
updateFile(clean, resSha, resMode, ren1.dstName)
|
|
||||||
else:
|
|
||||||
removeFile(True, ren1.srcName)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
output('CONFLICT (rename/directory): Rename',
|
|
||||||
fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,
|
|
||||||
'directory', ren1.dstName, 'added in', branchName2)
|
|
||||||
output('Renaming', ren1.srcName, 'to', newPath, 'instead')
|
|
||||||
cleanMerge = False
|
|
||||||
removeFile(False, ren1.dstName)
|
|
||||||
updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
|
|
||||||
elif srcShaOtherBranch == None:
|
|
||||||
output('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)
|
|
||||||
output('CONFLICT (rename/add): Rename',
|
|
||||||
fmtRename(ren1.srcName, ren1.dstName), 'in',
|
|
||||||
branchName1 + '.', ren1.dstName, 'added in', branchName2)
|
|
||||||
output('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)
|
|
||||||
output('CONFLICT (rename/rename): Rename',
|
|
||||||
fmtRename(ren1.srcName, ren1.dstName), 'in',
|
|
||||||
branchName1+'. Rename',
|
|
||||||
fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2)
|
|
||||||
output('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
|
|
||||||
|
|
||||||
if tryMerge:
|
|
||||||
|
|
||||||
oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode
|
|
||||||
aName, bName = ren1.dstName, ren1.srcName
|
|
||||||
aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch
|
|
||||||
aMode, bMode = ren1.dstMode, srcModeOtherBranch
|
|
||||||
aBranch, bBranch = branchName1, branchName2
|
|
||||||
|
|
||||||
if renamesA != renames1:
|
|
||||||
aName, bName = bName, aName
|
|
||||||
aSHA1, bSHA1 = bSHA1, aSHA1
|
|
||||||
aMode, bMode = bMode, aMode
|
|
||||||
aBranch, bBranch = bBranch, aBranch
|
|
||||||
|
|
||||||
[resSha, resMode, clean, merge] = \
|
|
||||||
mergeFile(oName, oSHA1, oMode,
|
|
||||||
aName, aSHA1, aMode,
|
|
||||||
bName, bSHA1, bMode,
|
|
||||||
aBranch, bBranch);
|
|
||||||
|
|
||||||
if merge or not clean:
|
|
||||||
output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
|
|
||||||
|
|
||||||
if merge:
|
|
||||||
output('Auto-merging', ren1.dstName)
|
|
||||||
|
|
||||||
if not clean:
|
|
||||||
output('CONFLICT (rename/modify): Merge conflict in',
|
|
||||||
ren1.dstName)
|
|
||||||
cleanMerge = False
|
|
||||||
|
|
||||||
if not cacheOnly:
|
|
||||||
setIndexStages(ren1.dstName,
|
|
||||||
oSHA1, oMode,
|
|
||||||
aSHA1, aMode,
|
|
||||||
bSHA1, bMode)
|
|
||||||
|
|
||||||
updateFile(clean, resSha, resMode, ren1.dstName)
|
|
||||||
|
|
||||||
return cleanMerge
|
|
||||||
|
|
||||||
# Per entry merge function
|
|
||||||
# ------------------------
|
|
||||||
|
|
||||||
def processEntry(entry, branch1Name, branch2Name):
|
|
||||||
'''Merge one cache entry.'''
|
|
||||||
|
|
||||||
debug('processing', entry.path, 'clean cache:', cacheOnly)
|
|
||||||
|
|
||||||
cleanMerge = True
|
|
||||||
|
|
||||||
path = entry.path
|
|
||||||
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))
|
|
||||||
assert(bSha == None or isSha(bSha))
|
|
||||||
|
|
||||||
assert(oMode == None or type(oMode) is int)
|
|
||||||
assert(aMode == None or type(aMode) is int)
|
|
||||||
assert(bMode == None or type(bMode) is int)
|
|
||||||
|
|
||||||
if (oSha and (not aSha or not bSha)):
|
|
||||||
#
|
|
||||||
# Case A: Deleted in one
|
|
||||||
#
|
|
||||||
if (not aSha and not bSha) or \
|
|
||||||
(aSha == oSha and not bSha) or \
|
|
||||||
(not aSha and bSha == oSha):
|
|
||||||
# Deleted in both or deleted in one and unchanged in the other
|
|
||||||
if aSha:
|
|
||||||
output('Removing', path)
|
|
||||||
removeFile(True, path)
|
|
||||||
else:
|
|
||||||
# Deleted in one and changed in the other
|
|
||||||
cleanMerge = False
|
|
||||||
if not aSha:
|
|
||||||
output('CONFLICT (delete/modify):', path, 'deleted in',
|
|
||||||
branch1Name, 'and modified in', branch2Name + '.',
|
|
||||||
'Version', branch2Name, 'of', path, 'left in tree.')
|
|
||||||
mode = bMode
|
|
||||||
sha = bSha
|
|
||||||
else:
|
|
||||||
output('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):
|
|
||||||
#
|
|
||||||
# Case B: Added in one.
|
|
||||||
#
|
|
||||||
if aSha:
|
|
||||||
addBranch = branch1Name
|
|
||||||
otherBranch = branch2Name
|
|
||||||
mode = aMode
|
|
||||||
sha = aSha
|
|
||||||
conf = 'file/directory'
|
|
||||||
else:
|
|
||||||
addBranch = branch2Name
|
|
||||||
otherBranch = branch1Name
|
|
||||||
mode = bMode
|
|
||||||
sha = bSha
|
|
||||||
conf = 'directory/file'
|
|
||||||
|
|
||||||
if path in currentDirectorySet:
|
|
||||||
cleanMerge = False
|
|
||||||
newPath = uniquePath(path, addBranch)
|
|
||||||
output('CONFLICT (' + conf + '):',
|
|
||||||
'There is a directory with name', path, 'in',
|
|
||||||
otherBranch + '. Adding', path, 'as', newPath)
|
|
||||||
|
|
||||||
removeFile(False, path)
|
|
||||||
updateFile(False, sha, mode, newPath)
|
|
||||||
else:
|
|
||||||
output('Adding', path)
|
|
||||||
updateFile(True, sha, mode, path)
|
|
||||||
|
|
||||||
elif not oSha and aSha and bSha:
|
|
||||||
#
|
|
||||||
# Case C: Added in both (check for same permissions).
|
|
||||||
#
|
|
||||||
if aSha == bSha:
|
|
||||||
if aMode != bMode:
|
|
||||||
cleanMerge = False
|
|
||||||
output('CONFLICT: File', path,
|
|
||||||
'added identically in both branches, but permissions',
|
|
||||||
'conflict', '0%o' % aMode, '->', '0%o' % bMode)
|
|
||||||
output('CONFLICT: adding with permission:', '0%o' % aMode)
|
|
||||||
|
|
||||||
updateFile(False, aSha, aMode, path)
|
|
||||||
else:
|
|
||||||
# This case is handled by git-read-tree
|
|
||||||
assert(False)
|
|
||||||
else:
|
|
||||||
cleanMerge = False
|
|
||||||
newPath1 = uniquePath(path, branch1Name)
|
|
||||||
newPath2 = uniquePath(path, branch2Name)
|
|
||||||
output('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)
|
|
||||||
|
|
||||||
elif oSha and aSha and bSha:
|
|
||||||
#
|
|
||||||
# case D: Modified in both, but differently.
|
|
||||||
#
|
|
||||||
output('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:
|
|
||||||
cleanMerge = False
|
|
||||||
output('CONFLICT (content): Merge conflict in', path)
|
|
||||||
|
|
||||||
if cacheOnly:
|
|
||||||
updateFile(False, sha, mode, path)
|
|
||||||
else:
|
|
||||||
updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
|
|
||||||
else:
|
|
||||||
die("ERROR: Fatal merge failure, shouldn't happen.")
|
|
||||||
|
|
||||||
return cleanMerge
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
|
|
||||||
|
|
||||||
# main entry point as merge strategy module
|
|
||||||
# The first parameters up to -- are merge bases, and the rest are heads.
|
|
||||||
|
|
||||||
if len(sys.argv) < 4:
|
|
||||||
usage()
|
|
||||||
|
|
||||||
bases = []
|
|
||||||
for nextArg in xrange(1, len(sys.argv)):
|
|
||||||
if sys.argv[nextArg] == '--':
|
|
||||||
if len(sys.argv) != nextArg + 3:
|
|
||||||
die('Not handling anything other than two heads merge.')
|
|
||||||
try:
|
|
||||||
h1 = firstBranch = sys.argv[nextArg + 1]
|
|
||||||
h2 = secondBranch = sys.argv[nextArg + 2]
|
|
||||||
except IndexError:
|
|
||||||
usage()
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
bases.append(sys.argv[nextArg])
|
|
||||||
|
|
||||||
print 'Merging', h1, 'with', h2
|
|
||||||
|
|
||||||
try:
|
|
||||||
h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
|
|
||||||
h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
|
|
||||||
|
|
||||||
if len(bases) == 1:
|
|
||||||
base = runProgram(['git-rev-parse', '--verify',
|
|
||||||
bases[0] + '^0']).rstrip()
|
|
||||||
ancestor = Commit(base, None)
|
|
||||||
[dummy, clean] = merge(Commit(h1, None), Commit(h2, None),
|
|
||||||
firstBranch, secondBranch, None, 0,
|
|
||||||
ancestor)
|
|
||||||
else:
|
|
||||||
graph = buildGraph([h1, h2])
|
|
||||||
[dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
|
|
||||||
firstBranch, secondBranch, graph)
|
|
||||||
|
|
||||||
print ''
|
|
||||||
except:
|
|
||||||
if isinstance(sys.exc_info()[1], SystemExit):
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
traceback.print_exc(None, sys.stderr)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if clean:
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
sys.exit(1)
|
|
86
git-merge.sh
86
git-merge.sh
@ -3,22 +3,20 @@
|
|||||||
# Copyright (c) 2005 Junio C Hamano
|
# Copyright (c) 2005 Junio C Hamano
|
||||||
#
|
#
|
||||||
|
|
||||||
USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <remote>+'
|
USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [--reflog-action=<action>] [-m=<merge-message>] <commit>+'
|
||||||
|
|
||||||
. git-sh-setup
|
. git-sh-setup
|
||||||
|
|
||||||
LF='
|
LF='
|
||||||
'
|
'
|
||||||
|
|
||||||
all_strategies='recur recursive recursive-old octopus resolve stupid ours'
|
all_strategies='recur recursive octopus resolve stupid ours'
|
||||||
default_twohead_strategies='recursive'
|
default_twohead_strategies='recursive'
|
||||||
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
|
|
||||||
all_strategies='recur recursive resolve octopus stupid ours'
|
|
||||||
fi
|
|
||||||
|
|
||||||
dropsave() {
|
dropsave() {
|
||||||
rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
|
rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
|
||||||
@ -95,7 +93,7 @@ finish () {
|
|||||||
|
|
||||||
case "$#" in 0) usage ;; esac
|
case "$#" in 0) usage ;; esac
|
||||||
|
|
||||||
rloga=
|
rloga= have_message=
|
||||||
while case "$#" in 0) break ;; esac
|
while case "$#" in 0) break ;; esac
|
||||||
do
|
do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@ -128,17 +126,81 @@ do
|
|||||||
--reflog-action=*)
|
--reflog-action=*)
|
||||||
rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
|
rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
|
||||||
;;
|
;;
|
||||||
|
-m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
|
||||||
|
merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'`
|
||||||
|
have_message=t
|
||||||
|
;;
|
||||||
|
-m|--m|--me|--mes|--mess|--messa|--messag|--message)
|
||||||
|
shift
|
||||||
|
case "$#" in
|
||||||
|
1) usage ;;
|
||||||
|
esac
|
||||||
|
merge_msg="$1"
|
||||||
|
have_message=t
|
||||||
|
;;
|
||||||
-*) usage ;;
|
-*) usage ;;
|
||||||
*) break ;;
|
*) break ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
merge_msg="$1"
|
# This could be traditional "merge <msg> HEAD <commit>..." and the
|
||||||
shift
|
# way we can tell it is to see if the second token is HEAD, but some
|
||||||
head_arg="$1"
|
# people might have misused the interface and used a committish that
|
||||||
head=$(git-rev-parse --verify "$1"^0) || usage
|
# is the same as HEAD there instead. Traditional format never would
|
||||||
shift
|
# have "-m" so it is an additional safety measure to check for it.
|
||||||
|
|
||||||
|
if test -z "$have_message" &&
|
||||||
|
second_token=$(git-rev-parse --verify "$2^0" 2>/dev/null) &&
|
||||||
|
head_commit=$(git-rev-parse --verify "HEAD" 2>/dev/null) &&
|
||||||
|
test "$second_token" = "$head_commit"
|
||||||
|
then
|
||||||
|
merge_msg="$1"
|
||||||
|
shift
|
||||||
|
head_arg="$1"
|
||||||
|
shift
|
||||||
|
elif ! git-rev-parse --verify HEAD >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
# If the merged head is a valid one there is no reason to
|
||||||
|
# forbid "git merge" into a branch yet to be born. We do
|
||||||
|
# the same for "git pull".
|
||||||
|
if test 1 -ne $#
|
||||||
|
then
|
||||||
|
echo >&2 "Can merge only exactly one commit into empty head"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rh=$(git rev-parse --verify "$1^0") ||
|
||||||
|
die "$1 - not something we can merge"
|
||||||
|
|
||||||
|
git-update-ref -m "initial pull" HEAD "$rh" "" &&
|
||||||
|
git-read-tree --reset -u HEAD
|
||||||
|
exit
|
||||||
|
|
||||||
|
else
|
||||||
|
# We are invoked directly as the first-class UI.
|
||||||
|
head_arg=HEAD
|
||||||
|
|
||||||
|
# All the rest are the commits being merged; prepare
|
||||||
|
# the standard merge summary message to be appended to
|
||||||
|
# the given message. If remote is invalid we will die
|
||||||
|
# later in the common codepath so we discard the error
|
||||||
|
# in this loop.
|
||||||
|
merge_name=$(for remote
|
||||||
|
do
|
||||||
|
rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) &&
|
||||||
|
if git show-ref -q --verify "refs/heads/$remote"
|
||||||
|
then
|
||||||
|
what=branch
|
||||||
|
else
|
||||||
|
what=commit
|
||||||
|
fi &&
|
||||||
|
echo "$rh $what '$remote'"
|
||||||
|
done | git-fmt-merge-msg
|
||||||
|
)
|
||||||
|
merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
|
||||||
|
fi
|
||||||
|
head=$(git-rev-parse --verify "$head_arg"^0) || usage
|
||||||
|
|
||||||
# All the rest are remote heads
|
# All the rest are remote heads
|
||||||
test "$#" = 0 && usage ;# we need at least one remote head.
|
test "$#" = 0 && usage ;# we need at least one remote head.
|
||||||
@ -147,7 +209,7 @@ test "$rloga" = '' && rloga="merge: $@"
|
|||||||
remoteheads=
|
remoteheads=
|
||||||
for remote
|
for remote
|
||||||
do
|
do
|
||||||
remotehead=$(git-rev-parse --verify "$remote"^0) ||
|
remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
|
||||||
die "$remote - not something we can merge"
|
die "$remote - not something we can merge"
|
||||||
remoteheads="${remoteheads}$remotehead "
|
remoteheads="${remoteheads}$remotehead "
|
||||||
done
|
done
|
||||||
|
@ -302,15 +302,6 @@ then
|
|||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old"
|
|
||||||
then
|
|
||||||
die 'The recursive-old merge strategy is written in Python,
|
|
||||||
which this installation of git was not configured with. Please consider
|
|
||||||
a different merge strategy (e.g. recursive, resolve, or stupid)
|
|
||||||
or install Python and git with Python support.'
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# start doing a rebase with git-merge
|
# start doing a rebase with git-merge
|
||||||
# this is rename-aware if the recursive (default) strategy is used
|
# this is rename-aware if the recursive (default) strategy is used
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages.
|
|||||||
%package core
|
%package core
|
||||||
Summary: Core git tools
|
Summary: Core git tools
|
||||||
Group: Development/Tools
|
Group: Development/Tools
|
||||||
Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat
|
Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, expat
|
||||||
%description core
|
%description core
|
||||||
This is a stupid (but extremely fast) directory content manager. It
|
This is a stupid (but extremely fast) directory content manager. It
|
||||||
doesn't do a whole lot, but what it _does_ do is track directory
|
doesn't do a whole lot, but what it _does_ do is track directory
|
||||||
|
@ -1,275 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (C) 2005 Fredrik Kuivinen
|
|
||||||
#
|
|
||||||
|
|
||||||
import sys, re, os, traceback
|
|
||||||
from sets import Set
|
|
||||||
|
|
||||||
def die(*args):
|
|
||||||
printList(args, sys.stderr)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
def printList(list, file=sys.stdout):
|
|
||||||
for x in list:
|
|
||||||
file.write(str(x))
|
|
||||||
file.write(' ')
|
|
||||||
file.write('\n')
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
# Debugging machinery
|
|
||||||
# -------------------
|
|
||||||
|
|
||||||
DEBUG = 0
|
|
||||||
functionsToDebug = Set()
|
|
||||||
|
|
||||||
def addDebug(func):
|
|
||||||
if type(func) == str:
|
|
||||||
functionsToDebug.add(func)
|
|
||||||
else:
|
|
||||||
functionsToDebug.add(func.func_name)
|
|
||||||
|
|
||||||
def debug(*args):
|
|
||||||
if DEBUG:
|
|
||||||
funcName = traceback.extract_stack()[-2][2]
|
|
||||||
if funcName in functionsToDebug:
|
|
||||||
printList(args)
|
|
||||||
|
|
||||||
# Program execution
|
|
||||||
# -----------------
|
|
||||||
|
|
||||||
class ProgramError(Exception):
|
|
||||||
def __init__(self, progStr, error):
|
|
||||||
self.progStr = progStr
|
|
||||||
self.error = error
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.progStr + ': ' + self.error
|
|
||||||
|
|
||||||
addDebug('runProgram')
|
|
||||||
def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True):
|
|
||||||
debug('runProgram prog:', str(prog), 'input:', str(input))
|
|
||||||
if type(prog) is str:
|
|
||||||
progStr = prog
|
|
||||||
else:
|
|
||||||
progStr = ' '.join(prog)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if pipeOutput:
|
|
||||||
stderr = subprocess.STDOUT
|
|
||||||
stdout = subprocess.PIPE
|
|
||||||
else:
|
|
||||||
stderr = None
|
|
||||||
stdout = None
|
|
||||||
pop = subprocess.Popen(prog,
|
|
||||||
shell = type(prog) is str,
|
|
||||||
stderr=stderr,
|
|
||||||
stdout=stdout,
|
|
||||||
stdin=subprocess.PIPE,
|
|
||||||
env=env)
|
|
||||||
except OSError, e:
|
|
||||||
debug('strerror:', e.strerror)
|
|
||||||
raise ProgramError(progStr, e.strerror)
|
|
||||||
|
|
||||||
if input != None:
|
|
||||||
pop.stdin.write(input)
|
|
||||||
pop.stdin.close()
|
|
||||||
|
|
||||||
if pipeOutput:
|
|
||||||
out = pop.stdout.read()
|
|
||||||
else:
|
|
||||||
out = ''
|
|
||||||
|
|
||||||
code = pop.wait()
|
|
||||||
if returnCode:
|
|
||||||
ret = [out, code]
|
|
||||||
else:
|
|
||||||
ret = out
|
|
||||||
if code != 0 and not returnCode:
|
|
||||||
debug('error output:', out)
|
|
||||||
debug('prog:', prog)
|
|
||||||
raise ProgramError(progStr, out)
|
|
||||||
# debug('output:', out.replace('\0', '\n'))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# Code for computing common ancestors
|
|
||||||
# -----------------------------------
|
|
||||||
|
|
||||||
currentId = 0
|
|
||||||
def getUniqueId():
|
|
||||||
global currentId
|
|
||||||
currentId += 1
|
|
||||||
return currentId
|
|
||||||
|
|
||||||
# The 'virtual' commit objects have SHAs which are integers
|
|
||||||
shaRE = re.compile('^[0-9a-f]{40}$')
|
|
||||||
def isSha(obj):
|
|
||||||
return (type(obj) is str and bool(shaRE.match(obj))) or \
|
|
||||||
(type(obj) is int and obj >= 1)
|
|
||||||
|
|
||||||
class Commit(object):
|
|
||||||
__slots__ = ['parents', 'firstLineMsg', 'children', '_tree', 'sha',
|
|
||||||
'virtual']
|
|
||||||
|
|
||||||
def __init__(self, sha, parents, tree=None):
|
|
||||||
self.parents = parents
|
|
||||||
self.firstLineMsg = None
|
|
||||||
self.children = []
|
|
||||||
|
|
||||||
if tree:
|
|
||||||
tree = tree.rstrip()
|
|
||||||
assert(isSha(tree))
|
|
||||||
self._tree = tree
|
|
||||||
|
|
||||||
if not sha:
|
|
||||||
self.sha = getUniqueId()
|
|
||||||
self.virtual = True
|
|
||||||
self.firstLineMsg = 'virtual commit'
|
|
||||||
assert(isSha(tree))
|
|
||||||
else:
|
|
||||||
self.virtual = False
|
|
||||||
self.sha = sha.rstrip()
|
|
||||||
assert(isSha(self.sha))
|
|
||||||
|
|
||||||
def tree(self):
|
|
||||||
self.getInfo()
|
|
||||||
assert(self._tree != None)
|
|
||||||
return self._tree
|
|
||||||
|
|
||||||
def shortInfo(self):
|
|
||||||
self.getInfo()
|
|
||||||
return str(self.sha) + ' ' + self.firstLineMsg
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.shortInfo()
|
|
||||||
|
|
||||||
def getInfo(self):
|
|
||||||
if self.virtual or self.firstLineMsg != None:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
info = runProgram(['git-cat-file', 'commit', self.sha])
|
|
||||||
info = info.split('\n')
|
|
||||||
msg = False
|
|
||||||
for l in info:
|
|
||||||
if msg:
|
|
||||||
self.firstLineMsg = l
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if l.startswith('tree'):
|
|
||||||
self._tree = l[5:].rstrip()
|
|
||||||
elif l == '':
|
|
||||||
msg = True
|
|
||||||
|
|
||||||
class Graph:
|
|
||||||
def __init__(self):
|
|
||||||
self.commits = []
|
|
||||||
self.shaMap = {}
|
|
||||||
|
|
||||||
def addNode(self, node):
|
|
||||||
assert(isinstance(node, Commit))
|
|
||||||
self.shaMap[node.sha] = node
|
|
||||||
self.commits.append(node)
|
|
||||||
for p in node.parents:
|
|
||||||
p.children.append(node)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def reachableNodes(self, n1, n2):
|
|
||||||
res = {}
|
|
||||||
def traverse(n):
|
|
||||||
res[n] = True
|
|
||||||
for p in n.parents:
|
|
||||||
traverse(p)
|
|
||||||
|
|
||||||
traverse(n1)
|
|
||||||
traverse(n2)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def fixParents(self, node):
|
|
||||||
for x in range(0, len(node.parents)):
|
|
||||||
node.parents[x] = self.shaMap[node.parents[x]]
|
|
||||||
|
|
||||||
# addDebug('buildGraph')
|
|
||||||
def buildGraph(heads):
|
|
||||||
debug('buildGraph heads:', heads)
|
|
||||||
for h in heads:
|
|
||||||
assert(isSha(h))
|
|
||||||
|
|
||||||
g = Graph()
|
|
||||||
|
|
||||||
out = runProgram(['git-rev-list', '--parents'] + heads)
|
|
||||||
for l in out.split('\n'):
|
|
||||||
if l == '':
|
|
||||||
continue
|
|
||||||
shas = l.split(' ')
|
|
||||||
|
|
||||||
# This is a hack, we temporarily use the 'parents' attribute
|
|
||||||
# to contain a list of SHA1:s. They are later replaced by proper
|
|
||||||
# Commit objects.
|
|
||||||
c = Commit(shas[0], shas[1:])
|
|
||||||
|
|
||||||
g.commits.append(c)
|
|
||||||
g.shaMap[c.sha] = c
|
|
||||||
|
|
||||||
for c in g.commits:
|
|
||||||
g.fixParents(c)
|
|
||||||
|
|
||||||
for c in g.commits:
|
|
||||||
for p in c.parents:
|
|
||||||
p.children.append(c)
|
|
||||||
return g
|
|
||||||
|
|
||||||
# Write the empty tree to the object database and return its SHA1
|
|
||||||
def writeEmptyTree():
|
|
||||||
tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index'
|
|
||||||
def delTmpIndex():
|
|
||||||
try:
|
|
||||||
os.unlink(tmpIndex)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
delTmpIndex()
|
|
||||||
newEnv = os.environ.copy()
|
|
||||||
newEnv['GIT_INDEX_FILE'] = tmpIndex
|
|
||||||
res = runProgram(['git-write-tree'], env=newEnv).rstrip()
|
|
||||||
delTmpIndex()
|
|
||||||
return res
|
|
||||||
|
|
||||||
def addCommonRoot(graph):
|
|
||||||
roots = []
|
|
||||||
for c in graph.commits:
|
|
||||||
if len(c.parents) == 0:
|
|
||||||
roots.append(c)
|
|
||||||
|
|
||||||
superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree())
|
|
||||||
graph.addNode(superRoot)
|
|
||||||
for r in roots:
|
|
||||||
r.parents = [superRoot]
|
|
||||||
superRoot.children = roots
|
|
||||||
return superRoot
|
|
||||||
|
|
||||||
def getCommonAncestors(graph, commit1, commit2):
|
|
||||||
'''Find the common ancestors for commit1 and commit2'''
|
|
||||||
assert(isinstance(commit1, Commit) and isinstance(commit2, Commit))
|
|
||||||
|
|
||||||
def traverse(start, set):
|
|
||||||
stack = [start]
|
|
||||||
while len(stack) > 0:
|
|
||||||
el = stack.pop()
|
|
||||||
set.add(el)
|
|
||||||
for p in el.parents:
|
|
||||||
if p not in set:
|
|
||||||
stack.append(p)
|
|
||||||
h1Set = Set()
|
|
||||||
h2Set = Set()
|
|
||||||
traverse(commit1, h1Set)
|
|
||||||
traverse(commit2, h2Set)
|
|
||||||
shared = h1Set.intersection(h2Set)
|
|
||||||
|
|
||||||
if len(shared) == 0:
|
|
||||||
shared = [addCommonRoot(graph)]
|
|
||||||
|
|
||||||
res = Set()
|
|
||||||
|
|
||||||
for s in shared:
|
|
||||||
if len([c for c in s.children if c in shared]) == 0:
|
|
||||||
res.add(s)
|
|
||||||
return list(res)
|
|
@ -13,10 +13,6 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
|
|||||||
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
|
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
|
||||||
TSVN = $(wildcard t91[0-9][0-9]-*.sh)
|
TSVN = $(wildcard t91[0-9][0-9]-*.sh)
|
||||||
|
|
||||||
ifdef NO_PYTHON
|
|
||||||
GIT_TEST_OPTS += --no-python
|
|
||||||
endif
|
|
||||||
|
|
||||||
all: $(T) clean
|
all: $(T) clean
|
||||||
|
|
||||||
$(T):
|
$(T):
|
||||||
|
@ -20,10 +20,10 @@ modification *should* take notice and update the test vectors here.
|
|||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# It appears that people are getting bitten by not installing
|
# It appears that people are getting bitten by not installing
|
||||||
# 'merge' (usually part of RCS package in binary distributions)
|
# 'merge' (usually part of RCS package in binary distributions).
|
||||||
# or have too old python without subprocess. Check them and error
|
# Check this and error out before running any tests. Also catch
|
||||||
# out before running any tests. Also catch the bogosity of trying
|
# the bogosity of trying to run tests without building while we
|
||||||
# to run tests without building while we are at it.
|
# are at it.
|
||||||
|
|
||||||
../git >/dev/null
|
../git >/dev/null
|
||||||
if test $? != 1
|
if test $? != 1
|
||||||
@ -42,12 +42,6 @@ fi
|
|||||||
|
|
||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
|
|
||||||
test "$no_python" || "$PYTHON" -c 'import subprocess' || {
|
|
||||||
echo >&2 'Your python seem to lack "subprocess" module.
|
|
||||||
Please check INSTALL document.'
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# init-db has been done in an empty repository.
|
# init-db has been done in an empty repository.
|
||||||
# make sure it is empty.
|
# make sure it is empty.
|
||||||
|
@ -76,7 +76,8 @@ do
|
|||||||
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
|
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
|
||||||
verbose=t; shift ;;
|
verbose=t; shift ;;
|
||||||
--no-python)
|
--no-python)
|
||||||
no_python=t; shift ;;
|
# noop now...
|
||||||
|
shift ;;
|
||||||
*)
|
*)
|
||||||
break ;;
|
break ;;
|
||||||
esac
|
esac
|
||||||
@ -210,18 +211,6 @@ GIT_EXEC_PATH=$(pwd)/..
|
|||||||
HOME=$(pwd)/trash
|
HOME=$(pwd)/trash
|
||||||
export PATH GIT_EXEC_PATH HOME
|
export PATH GIT_EXEC_PATH HOME
|
||||||
|
|
||||||
# Similarly use ../compat/subprocess.py if our python does not
|
|
||||||
# have subprocess.py on its own.
|
|
||||||
PYTHON=`sed -e '1{
|
|
||||||
s/^#!//
|
|
||||||
q
|
|
||||||
}' ../git-merge-recursive-old` || {
|
|
||||||
error "You haven't built things yet, have you?"
|
|
||||||
}
|
|
||||||
"$PYTHON" -c 'import subprocess' 2>/dev/null || {
|
|
||||||
PYTHONPATH=$(pwd)/../compat
|
|
||||||
export PYTHONPATH
|
|
||||||
}
|
|
||||||
GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
|
GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
|
||||||
export GITPERLLIB
|
export GITPERLLIB
|
||||||
test -d ../templates/blt || {
|
test -d ../templates/blt || {
|
||||||
|
Loading…
Reference in New Issue
Block a user