From: Shawn O. Pearce Date: Tue, 8 May 2007 03:36:31 +0000 (-0400) Subject: Merge branch 'master' of git://repo.or.cz/git-gui X-Git-Tag: v1.5.2-rc3~20 X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=5f5dbd719d0d8ec136f32a0a56674902bd85f72f;p=git.git Merge branch 'master' of git://repo.or.cz/git-gui * 'master' of git://repo.or.cz/git-gui: git-gui: Use vi-like keys in merge dialog git-gui: Include commit id/subject in merge choices git-gui: Show all possible branches for merge git-gui: Move merge support into a namespace git-gui: Allow vi keys to scroll the diff/blame regions git-gui: Move console procs into their own namespace git-gui: Refactor into multiple files to save my sanity git-gui: Track our own embedded values and rebuild when they change git-gui: Refactor to use our git proc more often git-gui: Use option database defaults to set the font git-gui: Cleanup common font handling for font_ui git-gui: Correct line wrapping for too many branch message git-gui: Warn users before making an octopus merge git-gui: Include the subject in the status bar after commit Also perform an evil merge change to update Git's main Makefile to pass the proper options down into git-gui now that it depends on reasonable values for 'sharedir' and 'TCL_PATH'. Signed-off-by: Shawn O. Pearce --- 5f5dbd719d0d8ec136f32a0a56674902bd85f72f diff --cc Makefile index e0a1308c0,ba1e33ba8..e89ccd496 --- a/Makefile +++ b/Makefile @@@ -3,745 -2,53 +3,753 @@@ all: # Define V=1 to have a more verbose compile. # +# Define NO_OPENSSL environment variable if you do not have OpenSSL. +# This also implies MOZILLA_SHA1. +# +# Define NO_CURL if you do not have curl installed. git-http-pull and +# git-http-push are not built, and you cannot use http:// and https:// +# transports. +# +# Define CURLDIR=/foo/bar if your curl header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +# +# Define NO_EXPAT if you do not have expat installed. git-http-push is +# not built, and you cannot push using http:// and https:// transports. +# +# Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. +# +# Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks +# d_type in struct dirent (latest Cygwin -- will be fixed soonish). +# +# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) +# do not support the 'size specifiers' introduced by C99, namely ll, hh, +# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). +# some C compilers supported these specifiers prior to C99 as an extension. +# +# Define NO_STRCASESTR if you don't have strcasestr. +# +# Define NO_STRLCPY if you don't have strlcpy. +# +# Define NO_STRTOUMAX if you don't have strtoumax in the C library. +# If your compiler also does not support long long or does not have +# strtoull, define NO_STRTOULL. +# +# Define NO_SETENV if you don't have setenv in the C library. +# +# 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. +# +# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability +# tests. These tests take up a significant amount of the total test time +# but are not needed unless you plan to talk to SVN repos. +# +# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink +# installed in /sw, but don't want GIT to link against any libraries +# installed there. If defined you may specify your own (or Fink's) +# include directories and library directories by defining CFLAGS +# and LDFLAGS appropriately. +# +# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, +# have DarwinPorts installed in /opt/local, but don't want GIT to +# link against any libraries installed there. If defined you may +# specify your own (or DarwinPort's) include directories and +# library directories by defining CFLAGS and LDFLAGS appropriately. +# +# Define PPC_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for PowerPC. +# +# Define ARM_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for ARM. +# +# Define MOZILLA_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast +# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default +# choice) has very fast version optimized for i586. +# +# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). +# +# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). +# +# Define NEEDS_SOCKET if linking with libc is not enough (SunOS, +# Patrick Mauritz). +# +# Define NO_MMAP if you want to avoid mmap. +# +# Define NO_PREAD if you have a problem with pread() system call (e.g. +# cygwin.dll before v1.5.22). +# +# Define NO_FAST_WORKING_DIRECTORY if accessing objects in pack files is +# generally faster on your platform than accessing the working directory. +# +# Define NO_TRUSTABLE_FILEMODE if your filesystem may claim to support +# the executable mode bit, but doesn't really do so. +# +# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). +# +# Define NO_SOCKADDR_STORAGE if your platform does not have struct +# sockaddr_storage. +# +# Define NO_ICONV if your libc does not properly support iconv. +# +# Define OLD_ICONV if your library has an old iconv(), where the second +# (input buffer pointer) parameter is declared with type (const char **). +# +# Define NO_R_TO_GCC if your gcc does not like "-R/path/lib" that +# tells runtime paths to dynamic libraries; "-Wl,-rpath=/path/lib" +# is used instead. +# +# Define USE_NSEC below if you want git to care about sub-second file mtimes +# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and +# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely +# randomly break unless your underlying filesystem supports those sub-second +# times (my ext3 doesn't). +# +# Define USE_STDEV below if you want git to care about the underlying device +# change being considered an inode change from the update-cache perspective. +# +# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's +# MakeMaker (e.g. using ActiveState under Cygwin). +# +# Define WITH_P4IMPORT to build and install Python git-p4import script. +# +# Define NO_TCLTK if you do not want Tcl/Tk GUI. +# - # The TCLTK_PATH variable governs the location of the Tck/Tk interpreter. ++# The TCL_PATH variable governs the location of the Tcl interpreter ++# used to optimize git-gui for your system. Only used if NO_TCLTK ++# is not set. Defaults to the bare 'tclsh'. ++# ++# The TCLTK_PATH variable governs the location of the Tcl/Tk interpreter. +# If not set it defaults to the bare 'wish'. If it is set to the empty +# string then NO_TCLTK will be forced (this is used by configure script). +# GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN -include GIT-VERSION-FILE -SCRIPT_SH = git-gui.sh -GITGUI_BUILT_INS = git-citool -ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) -ALL_LIBFILES = $(wildcard lib/*.tcl) +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') +uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') +uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') +uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') + +# CFLAGS and LDFLAGS are for the users to override from the command line. + +CFLAGS = -g -O2 -Wall +LDFLAGS = +ALL_CFLAGS = $(CFLAGS) +ALL_LDFLAGS = $(LDFLAGS) +STRIP ?= strip + +prefix = $(HOME) +bindir = $(prefix)/bin +gitexecdir = $(bindir) - template_dir = $(prefix)/share/git-core/templates/ ++sharedir = $(prefix)/share/ ++template_dir = $(sharedir)/git-core/templates/ +ifeq ($(prefix),/usr) +sysconfdir = /etc +else +sysconfdir = $(prefix)/etc +endif +ETC_GITCONFIG = $(sysconfdir)/gitconfig +# DESTDIR= + +# default configuration for gitweb +GITWEB_CONFIG = gitweb_config.perl +GITWEB_HOME_LINK_STR = projects +GITWEB_SITENAME = +GITWEB_PROJECTROOT = /pub/git +GITWEB_EXPORT_OK = +GITWEB_STRICT_EXPORT = +GITWEB_BASE_URL = +GITWEB_LIST = +GITWEB_HOMETEXT = indextext.html +GITWEB_CSS = gitweb.css +GITWEB_LOGO = git-logo.png +GITWEB_FAVICON = git-favicon.png +GITWEB_SITE_HEADER = +GITWEB_SITE_FOOTER = + - export prefix bindir gitexecdir template_dir sysconfdir ++export prefix bindir gitexecdir sharedir template_dir sysconfdir + +CC = gcc +AR = ar +TAR = tar +INSTALL = install +RPMBUILD = rpmbuild ++TCL_PATH = tclsh +TCLTK_PATH = wish + ++export TCL_PATH TCLTK_PATH ++ +# sparse is architecture-neutral, which means that we need to tell it +# explicitly what architecture to check for. Fix this up for yours.. +SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ + + + +### --- END CONFIGURATION SECTION --- + +# Those must not be GNU-specific; they are shared with perl/ which may +# be built by a different compiler. (Note that this is an artifact now +# but it still might be nice to keep that distinction.) +BASIC_CFLAGS = +BASIC_LDFLAGS = + +SCRIPT_SH = \ + git-bisect.sh git-checkout.sh \ + git-clean.sh git-clone.sh git-commit.sh \ + git-fetch.sh \ + git-ls-remote.sh \ + git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ + git-pull.sh git-rebase.sh \ + git-repack.sh git-request-pull.sh git-reset.sh \ + git-sh-setup.sh \ + git-tag.sh git-verify-tag.sh \ + git-applymbox.sh git-applypatch.sh git-am.sh \ + git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ + git-merge-resolve.sh git-merge-ours.sh \ + git-lost-found.sh git-quiltimport.sh + +SCRIPT_PERL = \ + git-add--interactive.perl \ + git-archimport.perl git-cvsimport.perl git-relink.perl \ + git-cvsserver.perl git-remote.perl \ + git-svnimport.perl git-cvsexportcommit.perl \ + git-send-email.perl git-svn.perl + +SCRIPT_PYTHON = \ + git-p4import.py + +ifdef WITH_P4IMPORT +SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ + $(patsubst %.perl,%,$(SCRIPT_PERL)) \ + $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ + git-status git-instaweb +else +SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ + $(patsubst %.perl,%,$(SCRIPT_PERL)) \ + git-status git-instaweb +endif + + +# ... and all the rest that could be moved out of bindir to gitexecdir +PROGRAMS = \ + git-convert-objects$X git-fetch-pack$X git-fsck$X \ + git-hash-object$X git-index-pack$X git-local-fetch$X \ + git-fast-import$X \ + git-merge-base$X \ + git-daemon$X \ + git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \ + git-peek-remote$X git-receive-pack$X \ + git-send-pack$X git-shell$X \ + git-show-index$X git-ssh-fetch$X \ + git-ssh-upload$X git-unpack-file$X \ + git-update-server-info$X \ + git-upload-pack$X git-verify-pack$X \ + git-pack-redundant$X git-var$X \ + git-merge-tree$X git-imap-send$X \ + git-merge-recursive$X \ + $(EXTRA_PROGRAMS) + +# Empty... +EXTRA_PROGRAMS = + +BUILT_INS = \ + git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \ + git-get-tar-commit-id$X git-init$X git-repo-config$X \ + git-fsck-objects$X git-cherry-pick$X \ + $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) +# what 'all' will build and 'install' will install, in gitexecdir +ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) + +ALL_PROGRAMS += git-merge-subtree$X + +# what 'all' will build but not install in gitexecdir +OTHER_PROGRAMS = git$X gitweb/gitweb.cgi +ifndef NO_TCLTK +OTHER_PROGRAMS += gitk-wish +endif + +# Backward compatibility -- to be removed after 1.0 +PROGRAMS += git-ssh-pull$X git-ssh-push$X + +# Set paths to tools early so that they can be used for version tests. ifndef SHELL_PATH SHELL_PATH = /bin/sh endif +ifndef PERL_PATH + PERL_PATH = /usr/bin/perl +endif +ifndef PYTHON_PATH + PYTHON_PATH = /usr/local/bin/python +endif + +export PERL_PATH + +LIB_FILE=libgit.a +XDIFF_LIB=xdiff/lib.a + +LIB_H = \ + archive.h blob.h cache.h commit.h csum-file.h delta.h grep.h \ + diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \ + run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ + tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \ + utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h mailmap.h + +DIFF_OBJS = \ + diff.o diff-lib.o diffcore-break.o diffcore-order.o \ + diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \ + diffcore-delta.o log-tree.o + +LIB_OBJS = \ + blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ + date.o diff-delta.o entry.o exec_cmd.o ident.o \ + interpolate.o \ + lockfile.o \ + patch-ids.o \ + object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \ + sideband.o reachable.o reflog-walk.o \ + quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ + server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ + tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ + revision.o pager.o tree-walk.o xdiff-interface.o \ + write_or_die.o trace.o list-objects.o grep.o match-trees.o \ + alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ + color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \ + convert.o attr.o decorate.o progress.o mailmap.o + +BUILTIN_OBJS = \ + builtin-add.o \ + builtin-annotate.o \ + builtin-apply.o \ + builtin-archive.o \ + builtin-blame.o \ + builtin-branch.o \ + builtin-bundle.o \ + builtin-cat-file.o \ + builtin-check-attr.o \ + builtin-checkout-index.o \ + builtin-check-ref-format.o \ + builtin-commit-tree.o \ + builtin-count-objects.o \ + builtin-describe.o \ + builtin-diff.o \ + builtin-diff-files.o \ + builtin-diff-index.o \ + builtin-diff-tree.o \ + builtin-fetch--tool.o \ + builtin-fmt-merge-msg.o \ + builtin-for-each-ref.o \ + builtin-fsck.o \ + builtin-gc.o \ + builtin-grep.o \ + builtin-init-db.o \ + builtin-log.o \ + builtin-ls-files.o \ + builtin-ls-tree.o \ + builtin-mailinfo.o \ + builtin-mailsplit.o \ + builtin-merge-base.o \ + builtin-merge-file.o \ + builtin-mv.o \ + builtin-name-rev.o \ + builtin-pack-objects.o \ + builtin-prune.o \ + builtin-prune-packed.o \ + builtin-push.o \ + builtin-read-tree.o \ + builtin-reflog.o \ + builtin-config.o \ + builtin-rerere.o \ + builtin-rev-list.o \ + builtin-rev-parse.o \ + builtin-revert.o \ + builtin-rm.o \ + builtin-runstatus.o \ + builtin-shortlog.o \ + builtin-show-branch.o \ + builtin-stripspace.o \ + builtin-symbolic-ref.o \ + builtin-tar-tree.o \ + builtin-unpack-objects.o \ + builtin-update-index.o \ + builtin-update-ref.o \ + builtin-upload-archive.o \ + builtin-verify-pack.o \ + builtin-write-tree.o \ + builtin-show-ref.o \ + builtin-pack-refs.o + +GITLIBS = $(LIB_FILE) $(XDIFF_LIB) +EXTLIBS = -lz + +# +# Platform specific tweaks +# + +# We choose to avoid "if .. else if .. else .. endif endif" +# because maintaining the nesting to match is a pain. If +# we had "elif" things would have been much nicer... + +ifeq ($(uname_S),Linux) + NO_STRLCPY = YesPlease +endif +ifeq ($(uname_S),GNU/kFreeBSD) + NO_STRLCPY = YesPlease +endif +ifeq ($(uname_S),Darwin) + NEEDS_SSL_WITH_CRYPTO = YesPlease + NEEDS_LIBICONV = YesPlease + OLD_ICONV = UnfortunatelyYes + NO_STRLCPY = YesPlease +endif +ifeq ($(uname_S),SunOS) + NEEDS_SOCKET = YesPlease + NEEDS_NSL = YesPlease + SHELL_PATH = /bin/bash + NO_STRCASESTR = YesPlease + ifeq ($(uname_R),5.8) + NEEDS_LIBICONV = YesPlease + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_C99_FORMAT = YesPlease + NO_STRTOUMAX = YesPlease + endif + ifeq ($(uname_R),5.9) + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_C99_FORMAT = YesPlease + NO_STRTOUMAX = YesPlease + endif + INSTALL = ginstall + TAR = gtar + BASIC_CFLAGS += -D__EXTENSIONS__ +endif +ifeq ($(uname_O),Cygwin) + NO_D_TYPE_IN_DIRENT = YesPlease + NO_D_INO_IN_DIRENT = YesPlease + NO_STRCASESTR = YesPlease + NO_SYMLINK_HEAD = YesPlease + NEEDS_LIBICONV = YesPlease + NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes + NO_TRUSTABLE_FILEMODE = UnfortunatelyYes + # There are conflicting reports about this. + # On some boxes NO_MMAP is needed, and not so elsewhere. + # Try commenting this out if you suspect MMAP is more efficient + NO_MMAP = YesPlease + NO_IPV6 = YesPlease + X = .exe +endif +ifeq ($(uname_S),FreeBSD) + NEEDS_LIBICONV = YesPlease + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib +endif +ifeq ($(uname_S),OpenBSD) + NO_STRCASESTR = YesPlease + NEEDS_LIBICONV = YesPlease + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib +endif +ifeq ($(uname_S),NetBSD) + ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) + NEEDS_LIBICONV = YesPlease + endif + BASIC_CFLAGS += -I/usr/pkg/include + BASIC_LDFLAGS += -L/usr/pkg/lib + ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib +endif +ifeq ($(uname_S),AIX) + NO_STRCASESTR=YesPlease + NO_STRLCPY = YesPlease + NEEDS_LIBICONV=YesPlease +endif +ifeq ($(uname_S),IRIX64) + NO_IPV6=YesPlease + NO_SETENV=YesPlease + NO_STRCASESTR=YesPlease + NO_STRLCPY = YesPlease + NO_SOCKADDR_STORAGE=YesPlease + SHELL_PATH=/usr/gnu/bin/bash + BASIC_CFLAGS += -DPATH_MAX=1024 + # for now, build 32-bit version + BASIC_LDFLAGS += -L/usr/lib32 +endif +ifneq (,$(findstring arm,$(uname_M))) + ARM_SHA1 = YesPlease +endif + +-include config.mak.autogen +-include config.mak + +ifeq ($(uname_S),Darwin) + ifndef NO_FINK + ifeq ($(shell test -d /sw/lib && echo y),y) + BASIC_CFLAGS += -I/sw/include + BASIC_LDFLAGS += -L/sw/lib + endif + endif + ifndef NO_DARWIN_PORTS + ifeq ($(shell test -d /opt/local/lib && echo y),y) + BASIC_CFLAGS += -I/opt/local/include + BASIC_LDFLAGS += -L/opt/local/lib + endif + endif +endif + +ifdef NO_R_TO_GCC_LINKER + # Some gcc does not accept and pass -R to the linker to specify + # the runtime dynamic library path. + CC_LD_DYNPATH = -Wl,-rpath= +else + CC_LD_DYNPATH = -R +endif + +ifndef NO_CURL + ifdef CURLDIR + # Try "-Wl,-rpath=$(CURLDIR)/lib" in such a case. + BASIC_CFLAGS += -I$(CURLDIR)/include + CURL_LIBCURL = -L$(CURLDIR)/lib $(CC_LD_DYNPATH)$(CURLDIR)/lib -lcurl + else + CURL_LIBCURL = -lcurl + endif + PROGRAMS += git-http-fetch$X + curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) + ifeq "$(curl_check)" "070908" + ifndef NO_EXPAT + PROGRAMS += git-http-push$X + endif + endif + ifndef NO_EXPAT + EXPAT_LIBEXPAT = -lexpat + endif +endif + +ifndef NO_OPENSSL + OPENSSL_LIBSSL = -lssl + ifdef OPENSSLDIR + BASIC_CFLAGS += -I$(OPENSSLDIR)/include + OPENSSL_LINK = -L$(OPENSSLDIR)/lib $(CC_LD_DYNPATH)$(OPENSSLDIR)/lib + else + OPENSSL_LINK = + endif +else + BASIC_CFLAGS += -DNO_OPENSSL + MOZILLA_SHA1 = 1 + OPENSSL_LIBSSL = +endif +ifdef NEEDS_SSL_WITH_CRYPTO + LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl +else + LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto +endif +ifdef NEEDS_LIBICONV + ifdef ICONVDIR + BASIC_CFLAGS += -I$(ICONVDIR)/include + ICONV_LINK = -L$(ICONVDIR)/lib $(CC_LD_DYNPATH)$(ICONVDIR)/lib + else + ICONV_LINK = + endif + EXTLIBS += $(ICONV_LINK) -liconv +endif +ifdef NEEDS_SOCKET + EXTLIBS += -lsocket +endif +ifdef NEEDS_NSL + EXTLIBS += -lnsl +endif +ifdef NO_D_TYPE_IN_DIRENT + BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT +endif +ifdef NO_D_INO_IN_DIRENT + BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT +endif +ifdef NO_C99_FORMAT + BASIC_CFLAGS += -DNO_C99_FORMAT +endif +ifdef NO_SYMLINK_HEAD + BASIC_CFLAGS += -DNO_SYMLINK_HEAD +endif +ifdef NO_STRCASESTR + COMPAT_CFLAGS += -DNO_STRCASESTR + COMPAT_OBJS += compat/strcasestr.o +endif +ifdef NO_STRLCPY + COMPAT_CFLAGS += -DNO_STRLCPY + COMPAT_OBJS += compat/strlcpy.o +endif +ifdef NO_STRTOUMAX + COMPAT_CFLAGS += -DNO_STRTOUMAX + COMPAT_OBJS += compat/strtoumax.o +endif +ifdef NO_STRTOULL + COMPAT_CFLAGS += -DNO_STRTOULL +endif +ifdef NO_SETENV + COMPAT_CFLAGS += -DNO_SETENV + COMPAT_OBJS += compat/setenv.o +endif +ifdef NO_UNSETENV + COMPAT_CFLAGS += -DNO_UNSETENV + COMPAT_OBJS += compat/unsetenv.o +endif +ifdef NO_MMAP + COMPAT_CFLAGS += -DNO_MMAP + COMPAT_OBJS += compat/mmap.o +endif +ifdef NO_PREAD + COMPAT_CFLAGS += -DNO_PREAD + COMPAT_OBJS += compat/pread.o +endif +ifdef NO_FAST_WORKING_DIRECTORY + BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY +endif +ifdef NO_TRUSTABLE_FILEMODE + BASIC_CFLAGS += -DNO_TRUSTABLE_FILEMODE +endif +ifdef NO_IPV6 + BASIC_CFLAGS += -DNO_IPV6 +endif +ifdef NO_SOCKADDR_STORAGE +ifdef NO_IPV6 + BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in +else + BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in6 +endif +endif +ifdef NO_INET_NTOP + LIB_OBJS += compat/inet_ntop.o +endif +ifdef NO_INET_PTON + LIB_OBJS += compat/inet_pton.o +endif -ifndef gitexecdir - gitexecdir := $(shell git --exec-path) +ifdef NO_ICONV + BASIC_CFLAGS += -DNO_ICONV endif -ifndef sharedir - sharedir := $(dir $(gitexecdir))/share +ifdef OLD_ICONV + BASIC_CFLAGS += -DOLD_ICONV endif -ifndef INSTALL - INSTALL = install +ifdef PPC_SHA1 + SHA1_HEADER = "ppc/sha1.h" + LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o +else +ifdef ARM_SHA1 + SHA1_HEADER = "arm/sha1.h" + LIB_OBJS += arm/sha1.o arm/sha1_arm.o +else +ifdef MOZILLA_SHA1 + SHA1_HEADER = "mozilla-sha1/sha1.h" + LIB_OBJS += mozilla-sha1/sha1.o +else + SHA1_HEADER = + EXTLIBS += $(LIB_4_CRYPTO) +endif +endif +endif +ifdef NO_PERL_MAKEMAKER + export NO_PERL_MAKEMAKER endif +ifeq ($(TCLTK_PATH),) +NO_TCLTK=NoThanks +endif + +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring $(MAKEFLAGS),w),w) +PRINT_DIR = --no-print-directory +else # "make -w" +NO_SUBDIR = : +endif + +ifneq ($(findstring $(MAKEFLAGS),s),s) ifndef V - QUIET_GEN = @echo ' ' GEN $@; + QUIET_CC = @echo ' ' CC $@; + QUIET_AR = @echo ' ' AR $@; + QUIET_LINK = @echo ' ' LINK $@; QUIET_BUILT_IN = @echo ' ' BUILTIN $@; - QUIET_INDEX = @echo ' ' INDEX $(dir $@); + QUIET_GEN = @echo ' ' GEN $@; + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir + export V + export QUIET_GEN + export QUIET_BUILT_IN +endif endif -TCL_PATH ?= tclsh -TCLTK_PATH ?= wish +# Shell quote (do not use $(call) to accommodate ancient setups); -ifeq ($(findstring $(MAKEFLAGS),s),s) -QUIET_GEN = -QUIET_BUILT_IN = -endif +SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER)) +ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG)) DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) +bindir_SQ = $(subst ','\'',$(bindir)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) +template_dir_SQ = $(subst ','\'',$(template_dir)) +prefix_SQ = $(subst ','\'',$(prefix)) + SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) -libdir ?= $(sharedir)/git-gui/lib -libdir_SQ = $(subst ','\'',$(libdir)) +LIBS = $(GITLIBS) $(EXTLIBS) + +BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \ + -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $(COMPAT_CFLAGS) +LIB_OBJS += $(COMPAT_OBJS) + +ALL_CFLAGS += $(BASIC_CFLAGS) +ALL_LDFLAGS += $(BASIC_LDFLAGS) + +export prefix gitexecdir TAR INSTALL DESTDIR SHELL_PATH template_dir + + +### Build rules + +all:: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) +ifneq (,$X) + $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), rm -f '$p';) +endif + +all:: +ifndef NO_TCLTK - $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) TCLTK_PATH='$(TCLTK_PATH_SQ)' all ++ $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) all +endif + $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all + $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) + +strip: $(PROGRAMS) git$X + $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X + +gitk-wish: gitk GIT-GUI-VARS + $(QUIET_GEN)rm -f $@ $@+ && \ + sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' $@+ && \ + chmod +x $@+ && \ + mv -f $@+ $@ + +git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS + $(QUIET_LINK)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ + $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \ + $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) + +help.o: common-cmds.h + +git-merge-subtree$X: git-merge-recursive$X + $(QUIET_BUILT_IN)rm -f $@ && ln git-merge-recursive$X $@ + +$(BUILT_INS): git$X + $(QUIET_BUILT_IN)rm -f $@ && ln git$X $@ + +common-cmds.h: $(wildcard Documentation/git-*.txt) + $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(QUIET_GEN)rm -f $@ $@+ && \ diff --cc git-gui/.gitignore index c714d382e,000000000..020b86dea mode 100644,000000..100644 --- a/git-gui/.gitignore +++ b/git-gui/.gitignore @@@ -1,3 -1,0 +1,5 @@@ +GIT-VERSION-FILE ++GIT-GUI-VARS +git-citool +git-gui ++lib/tclIndex diff --cc git-gui/Makefile index b29d7d1e6,000000000..ba1e33ba8 mode 100644,000000..100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@@ -1,73 -1,0 +1,105 @@@ +all:: + +# Define V=1 to have a more verbose compile. +# + +GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE + @$(SHELL_PATH) ./GIT-VERSION-GEN +-include GIT-VERSION-FILE + +SCRIPT_SH = git-gui.sh +GITGUI_BUILT_INS = git-citool +ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) ++ALL_LIBFILES = $(wildcard lib/*.tcl) + +ifndef SHELL_PATH + SHELL_PATH = /bin/sh +endif + +ifndef gitexecdir + gitexecdir := $(shell git --exec-path) +endif + ++ifndef sharedir ++ sharedir := $(dir $(gitexecdir))/share ++endif ++ +ifndef INSTALL + INSTALL = install +endif + +ifndef V + QUIET_GEN = @echo ' ' GEN $@; + QUIET_BUILT_IN = @echo ' ' BUILTIN $@; ++ QUIET_INDEX = @echo ' ' INDEX $(dir $@); +endif + ++TCL_PATH ?= tclsh +TCLTK_PATH ?= wish + +ifeq ($(findstring $(MAKEFLAGS),s),s) +QUIET_GEN = +QUIET_BUILT_IN = +endif + +DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) +gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) + ++libdir ?= $(sharedir)/git-gui/lib ++libdir_SQ = $(subst ','\'',$(libdir)) ++ +$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh + $(QUIET_GEN)rm -f $@ $@+ && \ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|^exec wish "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \ + -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ ++ -e 's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \ + $@.sh >$@+ && \ + chmod +x $@+ && \ + mv $@+ $@ + +$(GITGUI_BUILT_INS): git-gui + $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@ + ++lib/tclIndex: $(ALL_LIBFILES) ++ $(QUIET_INDEX)echo auto_mkindex lib '*.tcl' | $(TCL_PATH) ++ +# These can record GITGUI_VERSION - $(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE ++$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS ++ ++TRACK_VARS = \ ++ $(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \ ++ $(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \ ++ $(subst ','\'',libdir='$(libdir_SQ)') \ ++#end TRACK_VARS ++ ++GIT-GUI-VARS: .FORCE-GIT-GUI-VARS ++ @VARS='$(TRACK_VARS)'; \ ++ if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \ ++ echo 1>&2 " * new locations or Tcl/Tk interpreter"; \ ++ echo 1>$@ "$$VARS"; \ ++ fi + - all:: $(ALL_PROGRAMS) ++all:: $(ALL_PROGRAMS) lib/tclIndex + +install: all + $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' + $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)' + $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) ++ $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)' ++ $(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)' ++ $(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;) + +dist-version: + @mkdir -p $(TARDIR) + @echo $(GITGUI_VERSION) > $(TARDIR)/version + +clean:: - rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE ++ rm -f $(ALL_PROGRAMS) lib/tclIndex ++ rm -f GIT-VERSION-FILE GIT-GUI-VARS + +.PHONY: all install dist-version clean +.PHONY: .FORCE-GIT-VERSION-FILE ++.PHONY: .FORCE-GIT-GUI-VARS diff --cc git-gui/git-gui.sh index 0ad2815d1,000000000..46358258b mode 100755,000000..100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@@ -1,6066 -1,0 +1,2161 @@@ +#!/bin/sh +# Tcl ignores the next line -*- tcl -*- \ +exec wish "$0" -- "$@" + +set appvers {@@GITGUI_VERSION@@} +set copyright { +Copyright © 2006, 2007 Shawn Pearce, et. al. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA} + ++###################################################################### ++## ++## configure our library ++ ++set oguilib {@@GITGUI_LIBDIR@@} ++if {[string match @@* $oguilib]} { ++ set oguilib [file join [file dirname [file normalize $argv0]] lib] ++} ++set auto_path [concat [list $oguilib] $auto_path] ++ ++if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { ++ unset _verbose ++ rename auto_load real__auto_load ++ proc auto_load {name args} { ++ puts stderr "auto_load $name" ++ return [uplevel 1 real__auto_load $name $args] ++ } ++ rename source real__source ++ proc source {name} { ++ puts stderr "source $name" ++ uplevel 1 real__source $name ++ } ++} ++ +###################################################################### +## +## read only globals + +set _appname [lindex [file split $argv0] end] +set _gitdir {} +set _gitexec {} +set _reponame {} +set _iscygwin {} + +proc appname {} { + global _appname + return $_appname +} + +proc gitdir {args} { + global _gitdir + if {$args eq {}} { + return $_gitdir + } + return [eval [concat [list file join $_gitdir] $args]] +} + +proc gitexec {args} { + global _gitexec + if {$_gitexec eq {}} { + if {[catch {set _gitexec [git --exec-path]} err]} { + error "Git not installed?\n\n$err" + } + } + if {$args eq {}} { + return $_gitexec + } + return [eval [concat [list file join $_gitexec] $args]] +} + +proc reponame {} { + global _reponame + return $_reponame +} + +proc is_MacOSX {} { + global tcl_platform tk_library + if {[tk windowingsystem] eq {aqua}} { + return 1 + } + return 0 +} + +proc is_Windows {} { + global tcl_platform + if {$tcl_platform(platform) eq {windows}} { + return 1 + } + return 0 +} + +proc is_Cygwin {} { + global tcl_platform _iscygwin + if {$_iscygwin eq {}} { + if {$tcl_platform(platform) eq {windows}} { + if {[catch {set p [exec cygpath --windir]} err]} { + set _iscygwin 0 + } else { + set _iscygwin 1 + } + } else { + set _iscygwin 0 + } + } + return $_iscygwin +} + +proc is_enabled {option} { + global enabled_options + if {[catch {set on $enabled_options($option)}]} {return 0} + return $on +} + +proc enable_option {option} { + global enabled_options + set enabled_options($option) 1 +} + +proc disable_option {option} { + global enabled_options + set enabled_options($option) 0 +} + +###################################################################### +## +## config + +proc is_many_config {name} { + switch -glob -- $name { + remote.*.fetch - + remote.*.push + {return 1} + * + {return 0} + } +} + +proc is_config_true {name} { + global repo_config + if {[catch {set v $repo_config($name)}]} { + return 0 + } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} { + return 1 + } else { + return 0 + } +} + +proc load_config {include_global} { + global repo_config global_config default_config + + array unset global_config + if {$include_global} { + catch { + set fd_rc [open "| git config --global --list" r] + while {[gets $fd_rc line] >= 0} { + if {[regexp {^([^=]+)=(.*)$} $line line name value]} { + if {[is_many_config $name]} { + lappend global_config($name) $value + } else { + set global_config($name) $value + } + } + } + close $fd_rc + } + } + + array unset repo_config + catch { + set fd_rc [open "| git config --list" r] + while {[gets $fd_rc line] >= 0} { + if {[regexp {^([^=]+)=(.*)$} $line line name value]} { + if {[is_many_config $name]} { + lappend repo_config($name) $value + } else { + set repo_config($name) $value + } + } + } + close $fd_rc + } + + foreach name [array names default_config] { + if {[catch {set v $global_config($name)}]} { + set global_config($name) $default_config($name) + } + if {[catch {set v $repo_config($name)}]} { + set repo_config($name) $default_config($name) + } + } +} + - proc save_config {} { - global default_config font_descs - global repo_config global_config - global repo_config_new global_config_new - - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - font configure $font \ - -family $global_config_new(gui.$font^^family) \ - -size $global_config_new(gui.$font^^size) - font configure ${font}bold \ - -family $global_config_new(gui.$font^^family) \ - -size $global_config_new(gui.$font^^size) - set global_config_new(gui.$name) [font configure $font] - unset global_config_new(gui.$font^^family) - unset global_config_new(gui.$font^^size) - } - - foreach name [array names default_config] { - set value $global_config_new($name) - if {$value ne $global_config($name)} { - if {$value eq $default_config($name)} { - catch {git config --global --unset $name} - } else { - regsub -all "\[{}\]" $value {"} value - git config --global $name $value - } - set global_config($name) $value - if {$value eq $repo_config($name)} { - catch {git config --unset $name} - set repo_config($name) $value - } - } - } - - foreach name [array names default_config] { - set value $repo_config_new($name) - if {$value ne $repo_config($name)} { - if {$value eq $global_config($name)} { - catch {git config --unset $name} - } else { - regsub -all "\[{}\]" $value {"} value - git config $name $value - } - set repo_config($name) $value - } - } - } - +###################################################################### +## +## handy utils + +proc git {args} { + return [eval exec git $args] +} + - proc error_popup {msg} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui - set cmd [list tk_messageBox \ - -icon error \ - -type ok \ - -title "$title: error" \ - -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . - } - eval $cmd - } - - proc warn_popup {msg} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui - set cmd [list tk_messageBox \ - -icon warning \ - -type ok \ - -title "$title: warning" \ - -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . - } - eval $cmd - } - - proc info_popup {msg {parent .}} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui - tk_messageBox \ - -parent $parent \ - -icon info \ - -type ok \ - -title $title \ - -message $msg - } - - proc ask_popup {msg} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui - return [tk_messageBox \ - -parent . \ - -icon question \ - -type yesno \ - -title $title \ - -message $msg] ++auto_load tk_optionMenu ++rename tk_optionMenu real__tkOptionMenu ++proc tk_optionMenu {w varName args} { ++ set m [eval real__tkOptionMenu $w $varName $args] ++ $m configure -font font_ui ++ $w configure -font font_ui ++ return $m +} + +###################################################################### +## +## version check + +if {{--version} eq $argv || {version} eq $argv} { + puts "git-gui version $appvers" + exit +} + +set req_maj 1 +set req_min 5 + +if {[catch {set v [git --version]} err]} { + catch {wm withdraw .} + error_popup "Cannot determine Git version: + +$err + +[appname] requires Git $req_maj.$req_min or later." + exit 1 +} +if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} { + if {$act_maj < $req_maj + || ($act_maj == $req_maj && $act_min < $req_min)} { + catch {wm withdraw .} + error_popup "[appname] requires Git $req_maj.$req_min or later. + +You are using $v." + exit 1 + } +} else { + catch {wm withdraw .} + error_popup "Cannot parse Git version string:\n\n$v" + exit 1 +} +unset -nocomplain v _junk act_maj act_min req_maj req_min + +###################################################################### +## +## repository setup + +if { [catch {set _gitdir $env(GIT_DIR)}] + && [catch {set _gitdir [git rev-parse --git-dir]} err]} { + catch {wm withdraw .} + error_popup "Cannot find the git directory:\n\n$err" + exit 1 +} +if {![file isdirectory $_gitdir] && [is_Cygwin]} { + catch {set _gitdir [exec cygpath --unix $_gitdir]} +} +if {![file isdirectory $_gitdir]} { + catch {wm withdraw .} + error_popup "Git directory not found:\n\n$_gitdir" + exit 1 +} +if {[lindex [file split $_gitdir] end] ne {.git}} { + catch {wm withdraw .} + error_popup "Cannot use funny .git directory:\n\n$_gitdir" + exit 1 +} +if {[catch {cd [file dirname $_gitdir]} err]} { + catch {wm withdraw .} + error_popup "No working directory [file dirname $_gitdir]:\n\n$err" + exit 1 +} +set _reponame [lindex [file split \ + [file normalize [file dirname $_gitdir]]] \ + end] + +###################################################################### +## +## global init + +set current_diff_path {} +set current_diff_side {} +set diff_actions [list] +set ui_status_value {Initializing...} + +set HEAD {} +set PARENT {} +set MERGE_HEAD [list] +set commit_type {} +set empty_tree {} +set current_branch {} +set current_diff_path {} +set selected_commit_type new + +###################################################################### +## +## task management + +set rescan_active 0 +set diff_active 0 +set last_clicked {} + +set disable_on_lock [list] +set index_lock_type none + +proc lock_index {type} { + global index_lock_type disable_on_lock + + if {$index_lock_type eq {none}} { + set index_lock_type $type + foreach w $disable_on_lock { + uplevel #0 $w disabled + } + return 1 + } elseif {$index_lock_type eq "begin-$type"} { + set index_lock_type $type + return 1 + } + return 0 +} + +proc unlock_index {} { + global index_lock_type disable_on_lock + + set index_lock_type none + foreach w $disable_on_lock { + uplevel #0 $w normal + } +} + +###################################################################### +## +## status + +proc repository_state {ctvar hdvar mhvar} { + global current_branch + upvar $ctvar ct $hdvar hd $mhvar mh + + set mh [list] + + if {[catch {set current_branch [git symbolic-ref HEAD]}]} { + set current_branch {} + } else { + regsub ^refs/((heads|tags|remotes)/)? \ + $current_branch \ + {} \ + current_branch + } + + if {[catch {set hd [git rev-parse --verify HEAD]}]} { + set hd {} + set ct initial + return + } + + set merge_head [gitdir MERGE_HEAD] + if {[file exists $merge_head]} { + set ct merge + set fd_mh [open $merge_head r] + while {[gets $fd_mh line] >= 0} { + lappend mh $line + } + close $fd_mh + return + } + + set ct normal +} + +proc PARENT {} { + global PARENT empty_tree + + set p [lindex $PARENT 0] + if {$p ne {}} { + return $p + } + if {$empty_tree eq {}} { + set empty_tree [git mktree << {}] + } + return $empty_tree +} + +proc rescan {after {honor_trustmtime 1}} { + global HEAD PARENT MERGE_HEAD commit_type + global ui_index ui_workdir ui_status_value ui_comm + global rescan_active file_states + global repo_config + + if {$rescan_active > 0 || ![lock_index read]} return + + repository_state newType newHEAD newMERGE_HEAD + if {[string match amend* $commit_type] + && $newType eq {normal} + && $newHEAD eq $HEAD} { + } else { + set HEAD $newHEAD + set PARENT $newHEAD + set MERGE_HEAD $newMERGE_HEAD + set commit_type $newType + } + + array unset file_states + + if {![$ui_comm edit modified] + || [string trim [$ui_comm get 0.0 end]] eq {}} { + if {[load_message GITGUI_MSG]} { + } elseif {[load_message MERGE_MSG]} { + } elseif {[load_message SQUASH_MSG]} { + } + $ui_comm edit reset + $ui_comm edit modified false + } + + if {[is_enabled branch]} { + load_all_heads + populate_branch_menu + } + + if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} { + rescan_stage2 {} $after + } else { + set rescan_active 1 + set ui_status_value {Refreshing file status...} + set cmd [list git update-index] + lappend cmd -q + lappend cmd --unmerged + lappend cmd --ignore-missing + lappend cmd --refresh + set fd_rf [open "| $cmd" r] + fconfigure $fd_rf -blocking 0 -translation binary + fileevent $fd_rf readable \ + [list rescan_stage2 $fd_rf $after] + } +} + +proc rescan_stage2 {fd after} { + global ui_status_value + global rescan_active buf_rdi buf_rdf buf_rlo + + if {$fd ne {}} { + read $fd + if {![eof $fd]} return + close $fd + } + + set ls_others [list | git ls-files --others -z \ + --exclude-per-directory=.gitignore] + set info_exclude [gitdir info exclude] + if {[file readable $info_exclude]} { + lappend ls_others "--exclude-from=$info_exclude" + } + + set buf_rdi {} + set buf_rdf {} + set buf_rlo {} + + set rescan_active 3 + set ui_status_value {Scanning for modified files ...} + set fd_di [open "| git diff-index --cached -z [PARENT]" r] + set fd_df [open "| git diff-files -z" r] + set fd_lo [open $ls_others r] + + fconfigure $fd_di -blocking 0 -translation binary -encoding binary + fconfigure $fd_df -blocking 0 -translation binary -encoding binary + fconfigure $fd_lo -blocking 0 -translation binary -encoding binary + fileevent $fd_di readable [list read_diff_index $fd_di $after] + fileevent $fd_df readable [list read_diff_files $fd_df $after] + fileevent $fd_lo readable [list read_ls_others $fd_lo $after] +} + +proc load_message {file} { + global ui_comm + + set f [gitdir $file] + if {[file isfile $f]} { + if {[catch {set fd [open $f r]}]} { + return 0 + } + set content [string trim [read $fd]] + close $fd + regsub -all -line {[ \r\t]+$} $content {} content + $ui_comm delete 0.0 end + $ui_comm insert end $content + return 1 + } + return 0 +} + +proc read_diff_index {fd after} { + global buf_rdi + + append buf_rdi [read $fd] + set c 0 + set n [string length $buf_rdi] + while {$c < $n} { + set z1 [string first "\0" $buf_rdi $c] + if {$z1 == -1} break + incr z1 + set z2 [string first "\0" $buf_rdi $z1] + if {$z2 == -1} break + + incr c + set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }] + set p [string range $buf_rdi $z1 [expr {$z2 - 1}]] + merge_state \ + [encoding convertfrom $p] \ + [lindex $i 4]? \ + [list [lindex $i 0] [lindex $i 2]] \ + [list] + set c $z2 + incr c + } + if {$c < $n} { + set buf_rdi [string range $buf_rdi $c end] + } else { + set buf_rdi {} + } + + rescan_done $fd buf_rdi $after +} + +proc read_diff_files {fd after} { + global buf_rdf + + append buf_rdf [read $fd] + set c 0 + set n [string length $buf_rdf] + while {$c < $n} { + set z1 [string first "\0" $buf_rdf $c] + if {$z1 == -1} break + incr z1 + set z2 [string first "\0" $buf_rdf $z1] + if {$z2 == -1} break + + incr c + set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }] + set p [string range $buf_rdf $z1 [expr {$z2 - 1}]] + merge_state \ + [encoding convertfrom $p] \ + ?[lindex $i 4] \ + [list] \ + [list [lindex $i 0] [lindex $i 2]] + set c $z2 + incr c + } + if {$c < $n} { + set buf_rdf [string range $buf_rdf $c end] + } else { + set buf_rdf {} + } + + rescan_done $fd buf_rdf $after +} + +proc read_ls_others {fd after} { + global buf_rlo + + append buf_rlo [read $fd] + set pck [split $buf_rlo "\0"] + set buf_rlo [lindex $pck end] + foreach p [lrange $pck 0 end-1] { + merge_state [encoding convertfrom $p] ?O + } + rescan_done $fd buf_rlo $after +} + +proc rescan_done {fd buf after} { - global rescan_active ++ global rescan_active current_diff_path + global file_states repo_config + upvar $buf to_clear + + if {![eof $fd]} return + set to_clear {} + close $fd + if {[incr rescan_active -1] > 0} return + + prune_selection + unlock_index + display_all_files - reshow_diff ++ if {$current_diff_path ne {}} reshow_diff + uplevel #0 $after +} + +proc prune_selection {} { + global file_states selected_paths + + foreach path [array names selected_paths] { + if {[catch {set still_here $file_states($path)}]} { + unset selected_paths($path) + } + } +} + +###################################################################### +## - ## diff - - proc clear_diff {} { - global ui_diff current_diff_path current_diff_header - global ui_index ui_workdir - - $ui_diff conf -state normal - $ui_diff delete 0.0 end - $ui_diff conf -state disabled - - set current_diff_path {} - set current_diff_header {} ++## ui helpers + - $ui_index tag remove in_diff 0.0 end - $ui_workdir tag remove in_diff 0.0 end - } ++proc mapicon {w state path} { ++ global all_icons + - proc reshow_diff {} { - global ui_status_value file_states file_lists - global current_diff_path current_diff_side - - set p $current_diff_path - if {$p eq {}} { - # No diff is being shown. - } elseif {$current_diff_side eq {} - || [catch {set s $file_states($p)}] - || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { - clear_diff - } else { - show_diff $p $current_diff_side ++ if {[catch {set r $all_icons($state$w)}]} { ++ puts "error: no icon for $w state={$state} $path" ++ return file_plain + } ++ return $r +} + - proc handle_empty_diff {} { - global current_diff_path file_states file_lists - - set path $current_diff_path - set s $file_states($path) - if {[lindex $s 0] ne {_M}} return - - info_popup "No differences detected. - - [short_path $path] has no changes. ++proc mapdesc {state path} { ++ global all_descs + - The modification date of this file was updated by another application, but the content within the file was not changed. ++ if {[catch {set r $all_descs($state)}]} { ++ puts "error: no desc for state={$state} $path" ++ return $state ++ } ++ return $r ++} + - A rescan will be automatically started to find other files which may have the same state." ++proc escape_path {path} { ++ regsub -all {\\} $path "\\\\" path ++ regsub -all "\n" $path "\\n" path ++ return $path ++} + - clear_diff - display_file $path __ - rescan {set ui_status_value {Ready.}} 0 ++proc short_path {path} { ++ return [escape_path [lindex [file split $path] end]] +} + - proc show_diff {path w {lno {}}} { - global file_states file_lists - global is_3way_diff diff_active repo_config - global ui_diff ui_status_value ui_index ui_workdir - global current_diff_path current_diff_side current_diff_header ++set next_icon_id 0 ++set null_sha1 [string repeat 0 40] + - if {$diff_active || ![lock_index read]} return ++proc merge_state {path new_state {head_info {}} {index_info {}}} { ++ global file_states next_icon_id null_sha1 + - clear_diff - if {$lno == {}} { - set lno [lsearch -sorted -exact $file_lists($w) $path] - if {$lno >= 0} { - incr lno - } - } - if {$lno >= 1} { - $w tag add in_diff $lno.0 [expr {$lno + 1}].0 - } ++ set s0 [string index $new_state 0] ++ set s1 [string index $new_state 1] + - set s $file_states($path) - set m [lindex $s 0] - set is_3way_diff 0 - set diff_active 1 - set current_diff_path $path - set current_diff_side $w - set current_diff_header {} - set ui_status_value "Loading diff of [escape_path $path]..." - - # - Git won't give us the diff, there's nothing to compare to! - # - if {$m eq {_O}} { - set max_sz [expr {128 * 1024}] - if {[catch { - set fd [open $path r] - set content [read $fd $max_sz] - close $fd - set sz [file size $path] - } err ]} { - set diff_active 0 - unlock_index - set ui_status_value "Unable to display [escape_path $path]" - error_popup "Error loading file:\n\n$err" - return - } - $ui_diff conf -state normal - if {![catch {set type [exec file $path]}]} { - set n [string length $path] - if {[string equal -length $n $path $type]} { - set type [string range $type $n end] - regsub {^:?\s*} $type {} type - } - $ui_diff insert end "* $type\n" d_@ - } - if {[string first "\0" $content] != -1} { - $ui_diff insert end \ - "* Binary file (not showing content)." \ - d_@ - } else { - if {$sz > $max_sz} { - $ui_diff insert end \ - "* Untracked file is $sz bytes. - * Showing only first $max_sz bytes. - " d_@ - } - $ui_diff insert end $content - if {$sz > $max_sz} { - $ui_diff insert end " - * Untracked file clipped here by [appname]. - * To see the entire file, use an external editor. - " d_@ - } - } - $ui_diff conf -state disabled - set diff_active 0 - unlock_index - set ui_status_value {Ready.} - return ++ if {[catch {set info $file_states($path)}]} { ++ set state __ ++ set icon n[incr next_icon_id] ++ } else { ++ set state [lindex $info 0] ++ set icon [lindex $info 1] ++ if {$head_info eq {}} {set head_info [lindex $info 2]} ++ if {$index_info eq {}} {set index_info [lindex $info 3]} + } + - set cmd [list | git] - if {$w eq $ui_index} { - lappend cmd diff-index - lappend cmd --cached - } elseif {$w eq $ui_workdir} { - if {[string index $m 0] eq {U}} { - lappend cmd diff - } else { - lappend cmd diff-files - } - } ++ if {$s0 eq {?}} {set s0 [string index $state 0]} \ ++ elseif {$s0 eq {_}} {set s0 _} + - lappend cmd -p - lappend cmd --no-color - if {$repo_config(gui.diffcontext) > 0} { - lappend cmd "-U$repo_config(gui.diffcontext)" - } - if {$w eq $ui_index} { - lappend cmd [PARENT] - } - lappend cmd -- - lappend cmd $path - - if {[catch {set fd [open $cmd r]} err]} { - set diff_active 0 - unlock_index - set ui_status_value "Unable to display [escape_path $path]" - error_popup "Error loading diff:\n\n$err" - return ++ if {$s1 eq {?}} {set s1 [string index $state 1]} \ ++ elseif {$s1 eq {_}} {set s1 _} ++ ++ if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} { ++ set head_info [list 0 $null_sha1] ++ } elseif {$s0 ne {_} && [string index $state 0] eq {_} ++ && $head_info eq {}} { ++ set head_info $index_info + } + - fconfigure $fd \ - -blocking 0 \ - -encoding binary \ - -translation binary - fileevent $fd readable [list read_diff $fd] ++ set file_states($path) [list $s0$s1 $icon \ ++ $head_info $index_info \ ++ ] ++ return $state +} + - proc read_diff {fd} { - global ui_diff ui_status_value diff_active - global is_3way_diff current_diff_header - - $ui_diff conf -state normal - while {[gets $fd line] >= 0} { - # -- Cleanup uninteresting diff header lines. - # - if { [string match {diff --git *} $line] - || [string match {diff --cc *} $line] - || [string match {diff --combined *} $line] - || [string match {--- *} $line] - || [string match {+++ *} $line]} { - append current_diff_header $line "\n" - continue - } - if {[string match {index *} $line]} continue - if {$line eq {deleted file mode 120000}} { - set line "deleted symlink" - } - - # -- Automatically detect if this is a 3 way diff. - # - if {[string match {@@@ *} $line]} {set is_3way_diff 1} - - if {[string match {mode *} $line] - || [string match {new file *} $line] - || [string match {deleted file *} $line] - || [string match {Binary files * and * differ} $line] - || $line eq {\ No newline at end of file} - || [regexp {^\* Unmerged path } $line]} { - set tags {} - } elseif {$is_3way_diff} { - set op [string range $line 0 1] - switch -- $op { - { } {set tags {}} - {@@} {set tags d_@} - { +} {set tags d_s+} - { -} {set tags d_s-} - {+ } {set tags d_+s} - {- } {set tags d_-s} - {--} {set tags d_--} - {++} { - if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} { - set line [string replace $line 0 1 { }] - set tags d$op - } else { - set tags d_++ - } - } - default { - puts "error: Unhandled 3 way diff marker: {$op}" - set tags {} - } - } - } else { - set op [string index $line 0] - switch -- $op { - { } {set tags {}} - {@} {set tags d_@} - {-} {set tags d_-} - {+} { - if {[regexp {^\+([<>]{7} |={7})} $line _g op]} { - set line [string replace $line 0 0 { }] - set tags d$op - } else { - set tags d_+ - } - } - default { - puts "error: Unhandled 2 way diff marker: {$op}" - set tags {} - } - } - } - $ui_diff insert end $line $tags - if {[string index $line end] eq "\r"} { - $ui_diff tag add d_cr {end - 2c} - } - $ui_diff insert end "\n" $tags - } - $ui_diff conf -state disabled - - if {[eof $fd]} { - close $fd - set diff_active 0 - unlock_index - set ui_status_value {Ready.} ++proc display_file_helper {w path icon_name old_m new_m} { ++ global file_lists + - if {[$ui_diff index end] eq {2.0}} { - handle_empty_diff ++ if {$new_m eq {_}} { ++ set lno [lsearch -sorted -exact $file_lists($w) $path] ++ if {$lno >= 0} { ++ set file_lists($w) [lreplace $file_lists($w) $lno $lno] ++ incr lno ++ $w conf -state normal ++ $w delete $lno.0 [expr {$lno + 1}].0 ++ $w conf -state disabled + } ++ } elseif {$old_m eq {_} && $new_m ne {_}} { ++ lappend file_lists($w) $path ++ set file_lists($w) [lsort -unique $file_lists($w)] ++ set lno [lsearch -sorted -exact $file_lists($w) $path] ++ incr lno ++ $w conf -state normal ++ $w image create $lno.0 \ ++ -align center -padx 5 -pady 1 \ ++ -name $icon_name \ ++ -image [mapicon $w $new_m $path] ++ $w insert $lno.1 "[escape_path $path]\n" ++ $w conf -state disabled ++ } elseif {$old_m ne $new_m} { ++ $w conf -state normal ++ $w image conf $icon_name -image [mapicon $w $new_m $path] ++ $w conf -state disabled + } +} + - proc apply_hunk {x y} { - global current_diff_path current_diff_header current_diff_side - global ui_diff ui_index file_states - - if {$current_diff_path eq {} || $current_diff_header eq {}} return - if {![lock_index apply_hunk]} return - - set apply_cmd {git apply --cached --whitespace=nowarn} - set mi [lindex $file_states($current_diff_path) 0] - if {$current_diff_side eq $ui_index} { - set mode unstage - lappend apply_cmd --reverse - if {[string index $mi 0] ne {M}} { - unlock_index - return - } - } else { - set mode stage - if {[string index $mi 1] ne {M}} { - unlock_index - return - } - } ++proc display_file {path state} { ++ global file_states selected_paths ++ global ui_index ui_workdir + - set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0] - set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0] - if {$s_lno eq {}} { - unlock_index - return - } ++ set old_m [merge_state $path $state] ++ set s $file_states($path) ++ set new_m [lindex $s 0] ++ set icon_name [lindex $s 1] + - set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end] - if {$e_lno eq {}} { - set e_lno end ++ set o [string index $old_m 0] ++ set n [string index $new_m 0] ++ if {$o eq {U}} { ++ set o _ + } - - if {[catch { - set p [open "| $apply_cmd" w] - fconfigure $p -translation binary -encoding binary - puts -nonewline $p $current_diff_header - puts -nonewline $p [$ui_diff get $s_lno $e_lno] - close $p} err]} { - error_popup "Failed to $mode selected hunk.\n\n$err" - unlock_index - return ++ if {$n eq {U}} { ++ set n _ + } ++ display_file_helper $ui_index $path $icon_name $o $n + - $ui_diff conf -state normal - $ui_diff delete $s_lno $e_lno - $ui_diff conf -state disabled - - if {[$ui_diff get 1.0 end] eq "\n"} { - set o _ ++ if {[string index $old_m 0] eq {U}} { ++ set o U + } else { - set o ? ++ set o [string index $old_m 1] + } - - if {$current_diff_side eq $ui_index} { - set mi ${o}M - } elseif {[string index $mi 0] eq {_}} { - set mi M$o ++ if {[string index $new_m 0] eq {U}} { ++ set n U + } else { - set mi ?$o - } - unlock_index - display_file $current_diff_path $mi - if {$o eq {_}} { - clear_diff - } - } - - ###################################################################### - ## - ## commit - - proc load_last_commit {} { - global HEAD PARENT MERGE_HEAD commit_type ui_comm - global repo_config - - if {[llength $PARENT] == 0} { - error_popup {There is nothing to amend. - - You are about to create the initial commit. There is no commit before this to amend. - } - return - } - - repository_state curType curHEAD curMERGE_HEAD - if {$curType eq {merge}} { - error_popup {Cannot amend while merging. - - You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity. - } - return - } - - set msg {} - set parents [list] - if {[catch { - set fd [open "| git cat-file commit $curHEAD" r] - fconfigure $fd -encoding binary -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - while {[gets $fd line] > 0} { - if {[string match {parent *} $line]} { - lappend parents [string range $line 7 end] - } elseif {[string match {encoding *} $line]} { - set enc [string tolower [string range $line 9 end]] - } - } - set msg [encoding convertfrom $enc [read $fd]] - set msg [string trim $msg] - close $fd - } err]} { - error_popup "Error loading commit data for amend:\n\n$err" - return - } - - set HEAD $curHEAD - set PARENT $parents - set MERGE_HEAD [list] - switch -- [llength $parents] { - 0 {set commit_type amend-initial} - 1 {set commit_type amend} - default {set commit_type amend-merge} ++ set n [string index $new_m 1] + } ++ display_file_helper $ui_workdir $path $icon_name $o $n + - $ui_comm delete 0.0 end - $ui_comm insert end $msg - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value {Ready.}} - } - - proc create_new_commit {} { - global commit_type ui_comm - - set commit_type normal - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value {Ready.}} - } - - set GIT_COMMITTER_IDENT {} - - proc committer_ident {} { - global GIT_COMMITTER_IDENT - - if {$GIT_COMMITTER_IDENT eq {}} { - if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} { - error_popup "Unable to obtain your identity:\n\n$err" - return {} - } - if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \ - $me me GIT_COMMITTER_IDENT]} { - error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me" - return {} - } ++ if {$new_m eq {__}} { ++ unset file_states($path) ++ catch {unset selected_paths($path)} + } - - return $GIT_COMMITTER_IDENT +} + - proc commit_tree {} { - global HEAD commit_type file_states ui_comm repo_config - global ui_status_value pch_error ++proc display_all_files_helper {w path icon_name m} { ++ global file_lists + - if {[committer_ident] eq {}} return - if {![lock_index update]} return - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {[string match amend* $commit_type] - && $curType eq {normal} - && $curHEAD eq $HEAD} { - } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - - Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created. - - The rescan will be automatically started now. - } - unlock_index - rescan {set ui_status_value {Ready.}} - return - } - - # -- At least one file should differ in the index. - # - set files_ready 0 - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - _? {continue} - A? - - D? - - M? {set files_ready 1} - U? { - error_popup "Unmerged files cannot be committed. - - File [short_path $path] has merge conflicts. You must resolve them and add the file before committing. - " - unlock_index - return - } - default { - error_popup "Unknown file state [lindex $s 0] detected. - - File [short_path $path] cannot be committed by this program. - " - } - } - } - if {!$files_ready && ![string match *merge $curType]} { - info_popup {No changes to commit. - - You must add at least 1 file before you can commit. - } - unlock_index - return - } - - # -- A message is required. - # - set msg [string trim [$ui_comm get 1.0 end]] - regsub -all -line {[ \t\r]+$} $msg {} msg - if {$msg eq {}} { - error_popup {Please supply a commit message. - - A good commit message has the following format: - - - First line: Describe in one sentance what you did. - - Second line: Blank - - Remaining lines: Describe why this change is good. - } - unlock_index - return - } - - # -- Run the pre-commit hook. - # - set pchook [gitdir hooks pre-commit] - - # On Cygwin [file executable] might lie so we need to ask - # the shell if the hook is executable. Yes that's annoying. - # - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\" 2>&1;" \ - "fi"]] - } elseif {[file executable $pchook]} { - set pchook [list $pchook |& cat] - } else { - commit_writetree $curHEAD $msg - return - } - - set ui_status_value {Calling pre-commit hook...} - set pch_error {} - set fd_ph [open "| $pchook" r] - fconfigure $fd_ph -blocking 0 -translation binary - fileevent $fd_ph readable \ - [list commit_prehook_wait $fd_ph $curHEAD $msg] - } - - proc commit_prehook_wait {fd_ph curHEAD msg} { - global pch_error ui_status_value - - append pch_error [read $fd_ph] - fconfigure $fd_ph -blocking 1 - if {[eof $fd_ph]} { - if {[catch {close $fd_ph}]} { - set ui_status_value {Commit declined by pre-commit hook.} - hook_failed_popup pre-commit $pch_error - unlock_index - } else { - commit_writetree $curHEAD $msg - } - set pch_error {} - return - } - fconfigure $fd_ph -blocking 0 - } - - proc commit_writetree {curHEAD msg} { - global ui_status_value - - set ui_status_value {Committing changes...} - set fd_wt [open "| git write-tree" r] - fileevent $fd_wt readable \ - [list commit_committree $fd_wt $curHEAD $msg] - } - - proc commit_committree {fd_wt curHEAD msg} { - global HEAD PARENT MERGE_HEAD commit_type - global all_heads current_branch - global ui_status_value ui_comm selected_commit_type - global file_states selected_paths rescan_active - global repo_config - - gets $fd_wt tree_id - if {$tree_id eq {} || [catch {close $fd_wt} err]} { - error_popup "write-tree failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return - } - - # -- Verify this wasn't an empty change. - # - if {$commit_type eq {normal}} { - set old_tree [git rev-parse "$PARENT^{tree}"] - if {$tree_id eq $old_tree} { - info_popup {No changes to commit. - - No files were modified by this commit and it was not a merge commit. - - A rescan will be automatically started now. - } - unlock_index - rescan {set ui_status_value {No changes to commit.}} - return - } - } - - # -- Build the message. - # - set msg_p [gitdir COMMIT_EDITMSG] - set msg_wt [open $msg_p w] - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - fconfigure $msg_wt -encoding binary -translation binary - puts -nonewline $msg_wt [encoding convertto $enc $msg] - close $msg_wt - - # -- Create the commit. - # - set cmd [list git commit-tree $tree_id] - foreach p [concat $PARENT $MERGE_HEAD] { - lappend cmd -p $p - } - lappend cmd <$msg_p - if {[catch {set cmt_id [eval exec $cmd]} err]} { - error_popup "commit-tree failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return - } - - # -- Update the HEAD ref. - # - set reflogm commit - if {$commit_type ne {normal}} { - append reflogm " ($commit_type)" - } - set i [string first "\n" $msg] - if {$i >= 0} { - append reflogm {: } [string range $msg 0 [expr {$i - 1}]] - } else { - append reflogm {: } $msg - } - set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD] - if {[catch {eval exec $cmd} err]} { - error_popup "update-ref failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return - } - - # -- Cleanup after ourselves. - # - catch {file delete $msg_p} - catch {file delete [gitdir MERGE_HEAD]} - catch {file delete [gitdir MERGE_MSG]} - catch {file delete [gitdir SQUASH_MSG]} - catch {file delete [gitdir GITGUI_MSG]} - - # -- Let rerere do its thing. - # - if {[file isdirectory [gitdir rr-cache]]} { - catch {git rerere} - } - - # -- Run the post-commit hook. - # - set pchook [gitdir hooks post-commit] - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\";" \ - "fi"]] - } elseif {![file executable $pchook]} { - set pchook {} - } - if {$pchook ne {}} { - catch {exec $pchook &} - } - - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - - if {[is_enabled singlecommit]} do_quit - - # -- Make sure our current branch exists. - # - if {$commit_type eq {initial}} { - lappend all_heads $current_branch - set all_heads [lsort -unique $all_heads] - populate_branch_menu - } - - # -- Update in memory status - # - set selected_commit_type new - set commit_type normal - set HEAD $cmt_id - set PARENT $cmt_id - set MERGE_HEAD [list] - - foreach path [array names file_states] { - set s $file_states($path) - set m [lindex $s 0] - switch -glob -- $m { - _O - - _M - - _D {continue} - __ - - A_ - - M_ - - D_ { - unset file_states($path) - catch {unset selected_paths($path)} - } - DO { - set file_states($path) [list _O [lindex $s 1] {} {}] - } - AM - - AD - - MM - - MD { - set file_states($path) [list \ - _[string index $m 1] \ - [lindex $s 1] \ - [lindex $s 3] \ - {}] - } - } - } - - display_all_files - unlock_index - reshow_diff - set ui_status_value \ - "Changes committed as [string range $cmt_id 0 7]." - } - - ###################################################################### - ## - ## fetch push - - proc fetch_from {remote} { - set w [new_console \ - "fetch $remote" \ - "Fetching new changes from $remote"] - set cmd [list git fetch] - lappend cmd $remote - console_exec $w $cmd console_done - } - - proc push_to {remote} { - set w [new_console \ - "push $remote" \ - "Pushing changes to $remote"] - set cmd [list git push] - lappend cmd -v - lappend cmd $remote - console_exec $w $cmd console_done - } - - ###################################################################### - ## - ## ui helpers - - proc mapicon {w state path} { - global all_icons - - if {[catch {set r $all_icons($state$w)}]} { - puts "error: no icon for $w state={$state} $path" - return file_plain - } - return $r - } - - proc mapdesc {state path} { - global all_descs - - if {[catch {set r $all_descs($state)}]} { - puts "error: no desc for state={$state} $path" - return $state - } - return $r - } - - proc escape_path {path} { - regsub -all {\\} $path "\\\\" path - regsub -all "\n" $path "\\n" path - return $path - } - - proc short_path {path} { - return [escape_path [lindex [file split $path] end]] - } - - set next_icon_id 0 - set null_sha1 [string repeat 0 40] - - proc merge_state {path new_state {head_info {}} {index_info {}}} { - global file_states next_icon_id null_sha1 - - set s0 [string index $new_state 0] - set s1 [string index $new_state 1] - - if {[catch {set info $file_states($path)}]} { - set state __ - set icon n[incr next_icon_id] - } else { - set state [lindex $info 0] - set icon [lindex $info 1] - if {$head_info eq {}} {set head_info [lindex $info 2]} - if {$index_info eq {}} {set index_info [lindex $info 3]} - } - - if {$s0 eq {?}} {set s0 [string index $state 0]} \ - elseif {$s0 eq {_}} {set s0 _} - - if {$s1 eq {?}} {set s1 [string index $state 1]} \ - elseif {$s1 eq {_}} {set s1 _} - - if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} { - set head_info [list 0 $null_sha1] - } elseif {$s0 ne {_} && [string index $state 0] eq {_} - && $head_info eq {}} { - set head_info $index_info - } - - set file_states($path) [list $s0$s1 $icon \ - $head_info $index_info \ - ] - return $state - } - - proc display_file_helper {w path icon_name old_m new_m} { - global file_lists - - if {$new_m eq {_}} { - set lno [lsearch -sorted -exact $file_lists($w) $path] - if {$lno >= 0} { - set file_lists($w) [lreplace $file_lists($w) $lno $lno] - incr lno - $w conf -state normal - $w delete $lno.0 [expr {$lno + 1}].0 - $w conf -state disabled - } - } elseif {$old_m eq {_} && $new_m ne {_}} { - lappend file_lists($w) $path - set file_lists($w) [lsort -unique $file_lists($w)] - set lno [lsearch -sorted -exact $file_lists($w) $path] - incr lno - $w conf -state normal - $w image create $lno.0 \ - -align center -padx 5 -pady 1 \ - -name $icon_name \ - -image [mapicon $w $new_m $path] - $w insert $lno.1 "[escape_path $path]\n" - $w conf -state disabled - } elseif {$old_m ne $new_m} { - $w conf -state normal - $w image conf $icon_name -image [mapicon $w $new_m $path] - $w conf -state disabled - } - } - - proc display_file {path state} { - global file_states selected_paths - global ui_index ui_workdir - - set old_m [merge_state $path $state] - set s $file_states($path) - set new_m [lindex $s 0] - set icon_name [lindex $s 1] - - set o [string index $old_m 0] - set n [string index $new_m 0] - if {$o eq {U}} { - set o _ - } - if {$n eq {U}} { - set n _ - } - display_file_helper $ui_index $path $icon_name $o $n - - if {[string index $old_m 0] eq {U}} { - set o U - } else { - set o [string index $old_m 1] - } - if {[string index $new_m 0] eq {U}} { - set n U - } else { - set n [string index $new_m 1] - } - display_file_helper $ui_workdir $path $icon_name $o $n - - if {$new_m eq {__}} { - unset file_states($path) - catch {unset selected_paths($path)} - } - } - - proc display_all_files_helper {w path icon_name m} { - global file_lists - - lappend file_lists($w) $path - set lno [expr {[lindex [split [$w index end] .] 0] - 1}] - $w image create end \ - -align center -padx 5 -pady 1 \ - -name $icon_name \ - -image [mapicon $w $m $path] - $w insert end "[escape_path $path]\n" - } - - proc display_all_files {} { - global ui_index ui_workdir - global file_states file_lists - global last_clicked - - $ui_index conf -state normal - $ui_workdir conf -state normal - - $ui_index delete 0.0 end - $ui_workdir delete 0.0 end - set last_clicked {} - - set file_lists($ui_index) [list] - set file_lists($ui_workdir) [list] - - foreach path [lsort [array names file_states]] { - set s $file_states($path) - set m [lindex $s 0] - set icon_name [lindex $s 1] - - set s [string index $m 0] - if {$s ne {U} && $s ne {_}} { - display_all_files_helper $ui_index $path \ - $icon_name $s - } - - if {[string index $m 0] eq {U}} { - set s U - } else { - set s [string index $m 1] - } - if {$s ne {_}} { - display_all_files_helper $ui_workdir $path \ - $icon_name $s - } - } - - $ui_index conf -state disabled - $ui_workdir conf -state disabled - } - - proc update_indexinfo {msg pathList after} { - global update_index_cp ui_status_value - - if {![lock_index update]} return - - set update_index_cp 0 - set pathList [lsort $pathList] - set totalCnt [llength $pathList] - set batch [expr {int($totalCnt * .01) + 1}] - if {$batch > 25} {set batch 25} - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - 0.0] - set fd [open "| git update-index -z --index-info" w] - fconfigure $fd \ - -blocking 0 \ - -buffering full \ - -buffersize 512 \ - -encoding binary \ - -translation binary - fileevent $fd writable [list \ - write_update_indexinfo \ - $fd \ - $pathList \ - $totalCnt \ - $batch \ - $msg \ - $after \ - ] - } - - proc write_update_indexinfo {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value - global file_states current_diff_path - - if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after - return - } - - for {set i $batch} \ - {$update_index_cp < $totalCnt && $i > 0} \ - {incr i -1} { - set path [lindex $pathList $update_index_cp] - incr update_index_cp - - set s $file_states($path) - switch -glob -- [lindex $s 0] { - A? {set new _O} - M? {set new _M} - D_ {set new _D} - D? {set new _?} - ?? {continue} - } - set info [lindex $s 2] - if {$info eq {}} continue - - puts -nonewline $fd "$info\t[encoding convertto $path]\0" - display_file $path $new - } - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] - } - - proc update_index {msg pathList after} { - global update_index_cp ui_status_value - - if {![lock_index update]} return - - set update_index_cp 0 - set pathList [lsort $pathList] - set totalCnt [llength $pathList] - set batch [expr {int($totalCnt * .01) + 1}] - if {$batch > 25} {set batch 25} - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - 0.0] - set fd [open "| git update-index --add --remove -z --stdin" w] - fconfigure $fd \ - -blocking 0 \ - -buffering full \ - -buffersize 512 \ - -encoding binary \ - -translation binary - fileevent $fd writable [list \ - write_update_index \ - $fd \ - $pathList \ - $totalCnt \ - $batch \ - $msg \ - $after \ - ] - } - - proc write_update_index {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value - global file_states current_diff_path - - if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after - return - } - - for {set i $batch} \ - {$update_index_cp < $totalCnt && $i > 0} \ - {incr i -1} { - set path [lindex $pathList $update_index_cp] - incr update_index_cp - - switch -glob -- [lindex $file_states($path) 0] { - AD {set new __} - ?D {set new D_} - _O - - AM {set new A_} - U? { - if {[file exists $path]} { - set new M_ - } else { - set new D_ - } - } - ?M {set new M_} - ?? {continue} - } - puts -nonewline $fd "[encoding convertto $path]\0" - display_file $path $new - } - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] - } - - proc checkout_index {msg pathList after} { - global update_index_cp ui_status_value - - if {![lock_index update]} return - - set update_index_cp 0 - set pathList [lsort $pathList] - set totalCnt [llength $pathList] - set batch [expr {int($totalCnt * .01) + 1}] - if {$batch > 25} {set batch 25} - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - 0.0] - set cmd [list git checkout-index] - lappend cmd --index - lappend cmd --quiet - lappend cmd --force - lappend cmd -z - lappend cmd --stdin - set fd [open "| $cmd " w] - fconfigure $fd \ - -blocking 0 \ - -buffering full \ - -buffersize 512 \ - -encoding binary \ - -translation binary - fileevent $fd writable [list \ - write_checkout_index \ - $fd \ - $pathList \ - $totalCnt \ - $batch \ - $msg \ - $after \ - ] - } - - proc write_checkout_index {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value - global file_states current_diff_path - - if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after - return - } - - for {set i $batch} \ - {$update_index_cp < $totalCnt && $i > 0} \ - {incr i -1} { - set path [lindex $pathList $update_index_cp] - incr update_index_cp - switch -glob -- [lindex $file_states($path) 0] { - U? {continue} - ?M - - ?D { - puts -nonewline $fd "[encoding convertto $path]\0" - display_file $path ?_ - } - } - } - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] - } - - ###################################################################### - ## - ## branch management - - proc is_tracking_branch {name} { - global tracking_branches - - if {![catch {set info $tracking_branches($name)}]} { - return 1 - } - foreach t [array names tracking_branches] { - if {[string match {*/\*} $t] && [string match $t $name]} { - return 1 - } - } - return 0 - } - - proc load_all_heads {} { - global all_heads - - set all_heads [list] - set fd [open "| git for-each-ref --format=%(refname) refs/heads" r] - while {[gets $fd line] > 0} { - if {[is_tracking_branch $line]} continue - if {![regsub ^refs/heads/ $line {} name]} continue - lappend all_heads $name - } - close $fd - - set all_heads [lsort $all_heads] - } - - proc populate_branch_menu {} { - global all_heads disable_on_lock - - set m .mbar.branch - set last [$m index last] - for {set i 0} {$i <= $last} {incr i} { - if {[$m type $i] eq {separator}} { - $m delete $i last - set new_dol [list] - foreach a $disable_on_lock { - if {[lindex $a 0] ne $m || [lindex $a 2] < $i} { - lappend new_dol $a - } - } - set disable_on_lock $new_dol - break - } - } - - if {$all_heads ne {}} { - $m add separator - } - foreach b $all_heads { - $m add radiobutton \ - -label $b \ - -command [list switch_branch $b] \ - -variable current_branch \ - -value $b \ - -font font_ui - lappend disable_on_lock \ - [list $m entryconf [$m index last] -state] - } - } - - proc all_tracking_branches {} { - global tracking_branches - - set all_trackings {} - set cmd {} - foreach name [array names tracking_branches] { - if {[regsub {/\*$} $name {} name]} { - lappend cmd $name - } else { - regsub ^refs/(heads|remotes)/ $name {} name - lappend all_trackings $name - } - } - - if {$cmd ne {}} { - set fd [open "| git for-each-ref --format=%(refname) $cmd" r] - while {[gets $fd name] > 0} { - regsub ^refs/(heads|remotes)/ $name {} name - lappend all_trackings $name - } - close $fd - } - - return [lsort -unique $all_trackings] - } - - proc load_all_tags {} { - set all_tags [list] - set fd [open "| git for-each-ref --format=%(refname) refs/tags" r] - while {[gets $fd line] > 0} { - if {![regsub ^refs/tags/ $line {} name]} continue - lappend all_tags $name - } - close $fd - - return [lsort $all_tags] - } - - proc do_create_branch_action {w} { - global all_heads null_sha1 repo_config - global create_branch_checkout create_branch_revtype - global create_branch_head create_branch_trackinghead - global create_branch_name create_branch_revexp - global create_branch_tag - - set newbranch $create_branch_name - if {$newbranch eq {} - || $newbranch eq $repo_config(gui.newbranchtemplate)} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Please supply a branch name." - focus $w.desc.name_t - return - } - if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Branch '$newbranch' already exists." - focus $w.desc.name_t - return - } - if {[catch {git check-ref-format "heads/$newbranch"}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "We do not like '$newbranch' as a branch name." - focus $w.desc.name_t - return - } - - set rev {} - switch -- $create_branch_revtype { - head {set rev $create_branch_head} - tracking {set rev $create_branch_trackinghead} - tag {set rev $create_branch_tag} - expression {set rev $create_branch_revexp} - } - if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid starting revision: $rev" - return - } - set cmd [list git update-ref] - lappend cmd -m - lappend cmd "branch: Created from $rev" - lappend cmd "refs/heads/$newbranch" - lappend cmd $cmt - lappend cmd $null_sha1 - if {[catch {eval exec $cmd} err]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to create '$newbranch'.\n\n$err" - return - } - - lappend all_heads $newbranch - set all_heads [lsort $all_heads] - populate_branch_menu - destroy $w - if {$create_branch_checkout} { - switch_branch $newbranch - } - } - - proc radio_selector {varname value args} { - upvar #0 $varname var - set var $value - } - - trace add variable create_branch_head write \ - [list radio_selector create_branch_revtype head] - trace add variable create_branch_trackinghead write \ - [list radio_selector create_branch_revtype tracking] - trace add variable create_branch_tag write \ - [list radio_selector create_branch_revtype tag] - - trace add variable delete_branch_head write \ - [list radio_selector delete_branch_checktype head] - trace add variable delete_branch_trackinghead write \ - [list radio_selector delete_branch_checktype tracking] - - proc do_create_branch {} { - global all_heads current_branch repo_config - global create_branch_checkout create_branch_revtype - global create_branch_head create_branch_trackinghead - global create_branch_name create_branch_revexp - global create_branch_tag - - set w .branch_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Create New Branch} \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Create \ - -font font_ui \ - -default active \ - -command [list do_create_branch_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.desc \ - -text {Branch Description} \ - -font font_ui - label $w.desc.name_l -text {Name:} -font font_ui - entry $w.desc.name_t \ - -borderwidth 1 \ - -relief sunken \ - -width 40 \ - -textvariable create_branch_name \ - -font font_ui \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0} - return 1 - } - grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5} - grid columnconfigure $w.desc 1 -weight 1 - pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.from \ - -text {Starting Revision} \ - -font font_ui - radiobutton $w.from.head_r \ - -text {Local Branch:} \ - -value head \ - -variable create_branch_revtype \ - -font font_ui - set lbranchm [eval tk_optionMenu $w.from.head_m create_branch_head \ - $all_heads] - $lbranchm configure -font font_ui - $w.from.head_m configure -font font_ui - grid $w.from.head_r $w.from.head_m -sticky w - set all_trackings [all_tracking_branches] - if {$all_trackings ne {}} { - set create_branch_trackinghead [lindex $all_trackings 0] - radiobutton $w.from.tracking_r \ - -text {Tracking Branch:} \ - -value tracking \ - -variable create_branch_revtype \ - -font font_ui - set tbranchm [eval tk_optionMenu $w.from.tracking_m \ - create_branch_trackinghead \ - $all_trackings] - $tbranchm configure -font font_ui - $w.from.tracking_m configure -font font_ui - grid $w.from.tracking_r $w.from.tracking_m -sticky w - } - set all_tags [load_all_tags] - if {$all_tags ne {}} { - set create_branch_tag [lindex $all_tags 0] - radiobutton $w.from.tag_r \ - -text {Tag:} \ - -value tag \ - -variable create_branch_revtype \ - -font font_ui - set tagsm [eval tk_optionMenu $w.from.tag_m \ - create_branch_tag \ - $all_tags] - $tagsm configure -font font_ui - $w.from.tag_m configure -font font_ui - grid $w.from.tag_r $w.from.tag_m -sticky w - } - radiobutton $w.from.exp_r \ - -text {Revision Expression:} \ - -value expression \ - -variable create_branch_revtype \ - -font font_ui - entry $w.from.exp_t \ - -borderwidth 1 \ - -relief sunken \ - -width 50 \ - -textvariable create_branch_revexp \ - -font font_ui \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {\s} %S]} {return 0} - if {%d == 1 && [string length %S] > 0} { - set create_branch_revtype expression - } - return 1 - } - grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5} - grid columnconfigure $w.from 1 -weight 1 - pack $w.from -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.postActions \ - -text {Post Creation Actions} \ - -font font_ui - checkbutton $w.postActions.checkout \ - -text {Checkout after creation} \ - -variable create_branch_checkout \ - -font font_ui - pack $w.postActions.checkout -anchor nw - pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 - - set create_branch_checkout 1 - set create_branch_head $current_branch - set create_branch_revtype head - set create_branch_name $repo_config(gui.newbranchtemplate) - set create_branch_revexp {} - - bind $w " - grab $w - $w.desc.name_t icursor end - focus $w.desc.name_t - " - bind $w "destroy $w" - bind $w "do_create_branch_action $w;break" - wm title $w "[appname] ([reponame]): Create Branch" - tkwait window $w - } - - proc do_delete_branch_action {w} { - global all_heads - global delete_branch_checktype delete_branch_head delete_branch_trackinghead - - set check_rev {} - switch -- $delete_branch_checktype { - head {set check_rev $delete_branch_head} - tracking {set check_rev $delete_branch_trackinghead} - always {set check_rev {:none}} - } - if {$check_rev eq {:none}} { - set check_cmt {} - } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid check revision: $check_rev" - return - } - - set to_delete [list] - set not_merged [list] - foreach i [$w.list.l curselection] { - set b [$w.list.l get $i] - if {[catch {set o [git rev-parse --verify $b]}]} continue - if {$check_cmt ne {}} { - if {$b eq $check_rev} continue - if {[catch {set m [git merge-base $o $check_cmt]}]} continue - if {$o ne $m} { - lappend not_merged $b - continue - } - } - lappend to_delete [list $b $o] - } - if {$not_merged ne {}} { - set msg "The following branches are not completely merged into $check_rev: - - - [join $not_merged "\n - "]" - tk_messageBox \ - -icon info \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message $msg - } - if {$to_delete eq {}} return - if {$delete_branch_checktype eq {always}} { - set msg {Recovering deleted branches is difficult. - - Delete the selected branches?} - if {[tk_messageBox \ - -icon warning \ - -type yesno \ - -title [wm title $w] \ - -parent $w \ - -message $msg] ne yes} { - return - } - } - - set failed {} - foreach i $to_delete { - set b [lindex $i 0] - set o [lindex $i 1] - if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { - append failed " - $b: $err\n" - } else { - set x [lsearch -sorted -exact $all_heads $b] - if {$x >= 0} { - set all_heads [lreplace $all_heads $x $x] - } - } - } - - if {$failed ne {}} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to delete branches:\n$failed" - } - - set all_heads [lsort $all_heads] - populate_branch_menu - destroy $w - } - - proc do_delete_branch {} { - global all_heads tracking_branches current_branch - global delete_branch_checktype delete_branch_head delete_branch_trackinghead - - set w .branch_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Delete Local Branch} \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Delete \ - -font font_ui \ - -command [list do_delete_branch_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.list \ - -text {Local Branches} \ - -font font_ui - listbox $w.list.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.list.sby set] \ - -font font_ui - foreach h $all_heads { - if {$h ne $current_branch} { - $w.list.l insert end $h - } - } - scrollbar $w.list.sby -command [list $w.list.l yview] - pack $w.list.sby -side right -fill y - pack $w.list.l -side left -fill both -expand 1 - pack $w.list -fill both -expand 1 -pady 5 -padx 5 - - labelframe $w.validate \ - -text {Delete Only If} \ - -font font_ui - radiobutton $w.validate.head_r \ - -text {Merged Into Local Branch:} \ - -value head \ - -variable delete_branch_checktype \ - -font font_ui - set mergedlocalm [eval tk_optionMenu $w.validate.head_m \ - delete_branch_head \ - $all_heads] - $mergedlocalm configure -font font_ui - $w.validate.head_m configure -font font_ui - grid $w.validate.head_r $w.validate.head_m -sticky w - set all_trackings [all_tracking_branches] - if {$all_trackings ne {}} { - set delete_branch_trackinghead [lindex $all_trackings 0] - radiobutton $w.validate.tracking_r \ - -text {Merged Into Tracking Branch:} \ - -value tracking \ - -variable delete_branch_checktype \ - -font font_ui - set mergedtrackm [eval tk_optionMenu $w.validate.tracking_m \ - delete_branch_trackinghead \ - $all_trackings] - $mergedtrackm configure -font font_ui - $w.validate.tracking_m configure -font font_ui - grid $w.validate.tracking_r $w.validate.tracking_m -sticky w - } - radiobutton $w.validate.always_r \ - -text {Always (Do not perform merge checks)} \ - -value always \ - -variable delete_branch_checktype \ - -font font_ui - grid $w.validate.always_r -columnspan 2 -sticky w - grid columnconfigure $w.validate 1 -weight 1 - pack $w.validate -anchor nw -fill x -pady 5 -padx 5 - - set delete_branch_head $current_branch - set delete_branch_checktype head - - bind $w "grab $w; focus $w" - bind $w "destroy $w" - wm title $w "[appname] ([reponame]): Delete Branch" - tkwait window $w - } - - proc switch_branch {new_branch} { - global HEAD commit_type current_branch repo_config - - if {![lock_index switch]} return - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {[string match amend* $commit_type] - && $curType eq {normal} - && $curHEAD eq $HEAD} { - } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - - Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. - - The rescan will be automatically started now. - } - unlock_index - rescan {set ui_status_value {Ready.}} - return - } - - # -- Don't do a pointless switch. - # - if {$current_branch eq $new_branch} { - unlock_index - return - } - - if {$repo_config(gui.trustmtime) eq {true}} { - switch_branch_stage2 {} $new_branch - } else { - set ui_status_value {Refreshing file status...} - set cmd [list git update-index] - lappend cmd -q - lappend cmd --unmerged - lappend cmd --ignore-missing - lappend cmd --refresh - set fd_rf [open "| $cmd" r] - fconfigure $fd_rf -blocking 0 -translation binary - fileevent $fd_rf readable \ - [list switch_branch_stage2 $fd_rf $new_branch] - } - } - - proc switch_branch_stage2 {fd_rf new_branch} { - global ui_status_value HEAD - - if {$fd_rf ne {}} { - read $fd_rf - if {![eof $fd_rf]} return - close $fd_rf - } - - set ui_status_value "Updating working directory to '$new_branch'..." - set cmd [list git read-tree] - lappend cmd -m - lappend cmd -u - lappend cmd --exclude-per-directory=.gitignore - lappend cmd $HEAD - lappend cmd $new_branch - set fd_rt [open "| $cmd" r] - fconfigure $fd_rt -blocking 0 -translation binary - fileevent $fd_rt readable \ - [list switch_branch_readtree_wait $fd_rt $new_branch] - } - - proc switch_branch_readtree_wait {fd_rt new_branch} { - global selected_commit_type commit_type HEAD MERGE_HEAD PARENT - global current_branch - global ui_comm ui_status_value - - # -- We never get interesting output on stdout; only stderr. - # - read $fd_rt - fconfigure $fd_rt -blocking 1 - if {![eof $fd_rt]} { - fconfigure $fd_rt -blocking 0 - return - } - - # -- The working directory wasn't in sync with the index and - # we'd have to overwrite something to make the switch. A - # merge is required. - # - if {[catch {close $fd_rt} err]} { - regsub {^fatal: } $err {} err - warn_popup "File level merge required. - - $err - - Staying on branch '$current_branch'." - set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)." - unlock_index - return - } - - # -- Update the symbolic ref. Core git doesn't even check for failure - # here, it Just Works(tm). If it doesn't we are in some really ugly - # state that is difficult to recover from within git-gui. - # - if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { - error_popup "Failed to set current branch. - - This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. - - This should not have occurred. [appname] will now close and give up. - - $err" - do_quit - return - } - - # -- Update our repository state. If we were previously in amend mode - # we need to toss the current buffer and do a full rescan to update - # our file lists. If we weren't in amend mode our file lists are - # accurate and we can avoid the rescan. - # - unlock_index - set selected_commit_type new - if {[string match amend* $commit_type]} { - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value "Checked out branch '$current_branch'."} - } else { - repository_state commit_type HEAD MERGE_HEAD - set PARENT $HEAD - set ui_status_value "Checked out branch '$current_branch'." - } - } - - ###################################################################### - ## - ## remote management - - proc load_all_remotes {} { - global repo_config - global all_remotes tracking_branches - - set all_remotes [list] - array unset tracking_branches - - set rm_dir [gitdir remotes] - if {[file isdirectory $rm_dir]} { - set all_remotes [glob \ - -types f \ - -tails \ - -nocomplain \ - -directory $rm_dir *] - - foreach name $all_remotes { - catch { - set fd [open [file join $rm_dir $name] r] - while {[gets $fd line] >= 0} { - if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \ - $line line src dst]} continue - if {![regexp ^refs/ $dst]} { - set dst "refs/heads/$dst" - } - set tracking_branches($dst) [list $name $src] - } - close $fd - } - } - } - - foreach line [array names repo_config remote.*.url] { - if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue - lappend all_remotes $name - - if {[catch {set fl $repo_config(remote.$name.fetch)}]} { - set fl {} - } - foreach line $fl { - if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue - if {![regexp ^refs/ $dst]} { - set dst "refs/heads/$dst" - } - set tracking_branches($dst) [list $name $src] - } - } - - set all_remotes [lsort -unique $all_remotes] - } - - proc populate_fetch_menu {} { - global all_remotes repo_config - - set m .mbar.fetch - foreach r $all_remotes { - set enable 0 - if {![catch {set a $repo_config(remote.$r.url)}]} { - if {![catch {set a $repo_config(remote.$r.fetch)}]} { - set enable 1 - } - } else { - catch { - set fd [open [gitdir remotes $r] r] - while {[gets $fd n] >= 0} { - if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { - set enable 1 - break - } - } - close $fd - } - } - - if {$enable} { - $m add command \ - -label "Fetch from $r..." \ - -command [list fetch_from $r] \ - -font font_ui - } - } - } - - proc populate_push_menu {} { - global all_remotes repo_config - - set m .mbar.push - set fast_count 0 - foreach r $all_remotes { - set enable 0 - if {![catch {set a $repo_config(remote.$r.url)}]} { - if {![catch {set a $repo_config(remote.$r.push)}]} { - set enable 1 - } - } else { - catch { - set fd [open [gitdir remotes $r] r] - while {[gets $fd n] >= 0} { - if {[regexp {^Push:[ \t]*([^:]+):} $n]} { - set enable 1 - break - } - } - close $fd - } - } - - if {$enable} { - if {!$fast_count} { - $m add separator - } - $m add command \ - -label "Push to $r..." \ - -command [list push_to $r] \ - -font font_ui - incr fast_count - } - } - } - - proc start_push_anywhere_action {w} { - global push_urltype push_remote push_url push_thin push_tags - - set r_url {} - switch -- $push_urltype { - remote {set r_url $push_remote} - url {set r_url $push_url} - } - if {$r_url eq {}} return - - set cmd [list git push] - lappend cmd -v - if {$push_thin} { - lappend cmd --thin - } - if {$push_tags} { - lappend cmd --tags - } - lappend cmd $r_url - set cnt 0 - foreach i [$w.source.l curselection] { - set b [$w.source.l get $i] - lappend cmd "refs/heads/$b:refs/heads/$b" - incr cnt - } - if {$cnt == 0} { - return - } elseif {$cnt == 1} { - set unit branch - } else { - set unit branches - } - - set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"] - console_exec $cons $cmd console_done - destroy $w - } - - trace add variable push_remote write \ - [list radio_selector push_urltype remote] - - proc do_push_anywhere {} { - global all_heads all_remotes current_branch - global push_urltype push_remote push_url push_thin push_tags - - set w .push_setup - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Push Branches} -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Push \ - -font font_ui \ - -default active \ - -command [list start_push_anywhere_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ - -default normal \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.source \ - -text {Source Branches} \ - -font font_ui - listbox $w.source.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.source.sby set] \ - -font font_ui - foreach h $all_heads { - $w.source.l insert end $h - if {$h eq $current_branch} { - $w.source.l select set end - } - } - scrollbar $w.source.sby -command [list $w.source.l yview] - pack $w.source.sby -side right -fill y - pack $w.source.l -side left -fill both -expand 1 - pack $w.source -fill both -expand 1 -pady 5 -padx 5 - - labelframe $w.dest \ - -text {Destination Repository} \ - -font font_ui - if {$all_remotes ne {}} { - radiobutton $w.dest.remote_r \ - -text {Remote:} \ - -value remote \ - -variable push_urltype \ - -font font_ui - set remmenu [eval tk_optionMenu $w.dest.remote_m push_remote \ - $all_remotes] - $remmenu configure -font font_ui - $w.dest.remote_m configure -font font_ui - grid $w.dest.remote_r $w.dest.remote_m -sticky w - if {[lsearch -sorted -exact $all_remotes origin] != -1} { - set push_remote origin - } else { - set push_remote [lindex $all_remotes 0] - } - set push_urltype remote - } else { - set push_urltype url - } - radiobutton $w.dest.url_r \ - -text {Arbitrary URL:} \ - -value url \ - -variable push_urltype \ - -font font_ui - entry $w.dest.url_t \ - -borderwidth 1 \ - -relief sunken \ - -width 50 \ - -textvariable push_url \ - -font font_ui \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {\s} %S]} {return 0} - if {%d == 1 && [string length %S] > 0} { - set push_urltype url - } - return 1 - } - grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5} - grid columnconfigure $w.dest 1 -weight 1 - pack $w.dest -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.options \ - -text {Transfer Options} \ - -font font_ui - checkbutton $w.options.thin \ - -text {Use thin pack (for slow network connections)} \ - -variable push_thin \ - -font font_ui - grid $w.options.thin -columnspan 2 -sticky w - checkbutton $w.options.tags \ - -text {Include tags} \ - -variable push_tags \ - -font font_ui - grid $w.options.tags -columnspan 2 -sticky w - grid columnconfigure $w.options 1 -weight 1 - pack $w.options -anchor nw -fill x -pady 5 -padx 5 - - set push_url {} - set push_thin 0 - set push_tags 0 - - bind $w "grab $w; focus $w.buttons.create" - bind $w "destroy $w" - bind $w [list start_push_anywhere_action $w] - wm title $w "[appname] ([reponame]): Push" - tkwait window $w - } - - ###################################################################### - ## - ## merge - - proc can_merge {} { - global HEAD commit_type file_states - - if {[string match amend* $commit_type]} { - info_popup {Cannot merge while amending. - - You must finish amending this commit before starting any type of merge. - } - return 0 - } - - if {[committer_ident] eq {}} {return 0} - if {![lock_index merge]} {return 0} - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - - Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed. - - The rescan will be automatically started now. - } - unlock_index - rescan {set ui_status_value {Ready.}} - return 0 - } - - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - _O { - continue; # and pray it works! - } - U? { - error_popup "You are in the middle of a conflicted merge. - - File [short_path $path] has merge conflicts. - - You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge. - " - unlock_index - return 0 - } - ?? { - error_popup "You are in the middle of a change. - - File [short_path $path] is modified. - - You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise. - " - unlock_index - return 0 - } - } - } - - return 1 - } - - proc visualize_local_merge {w} { - set revs {} - foreach i [$w.source.l curselection] { - lappend revs [$w.source.l get $i] - } - if {$revs eq {}} return - lappend revs --not HEAD - do_gitk $revs - } - - proc start_local_merge_action {w} { - global HEAD ui_status_value current_branch - - set cmd [list git merge] - set names {} - set revcnt 0 - foreach i [$w.source.l curselection] { - set b [$w.source.l get $i] - lappend cmd $b - lappend names $b - incr revcnt - } - - if {$revcnt == 0} { - return - } elseif {$revcnt == 1} { - set unit branch - } elseif {$revcnt <= 15} { - set unit branches - } else { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Too many branches selected. - - You have requested to merge $revcnt branches - in an octopus merge. This exceeds Git's - internal limit of 15 branches per merge. - - Please select fewer branches. To merge more - than 15 branches, merge the branches in batches. - " - return - } - - set msg "Merging $current_branch, [join $names {, }]" - set ui_status_value "$msg..." - set cons [new_console "Merge" $msg] - console_exec $cons $cmd [list finish_merge $revcnt] - bind $w {} - destroy $w - } - - proc finish_merge {revcnt w ok} { - console_done $w $ok - if {$ok} { - set msg {Merge completed successfully.} - } else { - if {$revcnt != 1} { - info_popup "Octopus merge failed. - - Your merge of $revcnt branches has failed. - - There are file-level conflicts between the branches which must be resolved manually. - - The working directory will now be reset. - - You can attempt this merge again by merging only one branch at a time." $w - - set fd [open "| git read-tree --reset -u HEAD" r] - fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] - set ui_status_value {Aborting... please wait...} - return - } - - set msg {Merge failed. Conflict resolution is required.} - } - unlock_index - rescan [list set ui_status_value $msg] - } - - proc do_local_merge {} { - global current_branch - - if {![can_merge]} return - - set w .merge_setup - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header \ - -text "Merge Into $current_branch" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.visualize -text Visualize \ - -font font_ui \ - -command [list visualize_local_merge $w] - pack $w.buttons.visualize -side left - button $w.buttons.create -text Merge \ - -font font_ui \ - -command [list start_local_merge_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.source \ - -text {Source Branches} \ - -font font_ui - listbox $w.source.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.source.sby set] \ - -font font_ui - scrollbar $w.source.sby -command [list $w.source.l yview] - pack $w.source.sby -side right -fill y - pack $w.source.l -side left -fill both -expand 1 - pack $w.source -fill both -expand 1 -pady 5 -padx 5 - - set cmd [list git for-each-ref] - lappend cmd {--format=%(objectname) %(*objectname) %(refname)} - lappend cmd refs/heads - lappend cmd refs/remotes - lappend cmd refs/tags - set fr_fd [open "| $cmd" r] - fconfigure $fr_fd -translation binary - while {[gets $fr_fd line] > 0} { - set line [split $line { }] - set sha1([lindex $line 0]) [lindex $line 2] - set sha1([lindex $line 1]) [lindex $line 2] - } - close $fr_fd - - set to_show {} - set fr_fd [open "| git rev-list --all --not HEAD"] - while {[gets $fr_fd line] > 0} { - if {[catch {set ref $sha1($line)}]} continue - regsub ^refs/(heads|remotes|tags)/ $ref {} ref - lappend to_show $ref - } - close $fr_fd - - foreach ref [lsort -unique $to_show] { - $w.source.l insert end $ref - } - - bind $w "grab $w" - bind $w "unlock_index;destroy $w" - bind $w unlock_index - wm title $w "[appname] ([reponame]): Merge" - tkwait window $w - } - - proc do_reset_hard {} { - global HEAD commit_type file_states - - if {[string match amend* $commit_type]} { - info_popup {Cannot abort while amending. - - You must finish amending this commit. - } - return - } - - if {![lock_index abort]} return - - if {[string match *merge* $commit_type]} { - set op merge - } else { - set op commit - } - - if {[ask_popup "Abort $op? - - Aborting the current $op will cause *ALL* uncommitted changes to be lost. - - Continue with aborting the current $op?"] eq {yes}} { - set fd [open "| git read-tree --reset -u HEAD" r] - fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] - set ui_status_value {Aborting... please wait...} - } else { - unlock_index - } - } - - proc reset_hard_wait {fd} { - global ui_comm - - read $fd - if {[eof $fd]} { - close $fd - unlock_index - - $ui_comm delete 0.0 end - $ui_comm edit modified false - - catch {file delete [gitdir MERGE_HEAD]} - catch {file delete [gitdir rr-cache MERGE_RR]} - catch {file delete [gitdir SQUASH_MSG]} - catch {file delete [gitdir MERGE_MSG]} - catch {file delete [gitdir GITGUI_MSG]} - - rescan {set ui_status_value {Abort completed. Ready.}} - } - } - - ###################################################################### - ## - ## browser - - set next_browser_id 0 - - proc new_browser {commit} { - global next_browser_id cursor_ptr M1B - global browser_commit browser_status browser_stack browser_path browser_busy - - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set w_list $w.list.l - set browser_commit($w_list) $commit - set browser_status($w_list) {Starting...} - set browser_stack($w_list) {} - set browser_path($w_list) $browser_commit($w_list): - set browser_busy($w_list) 1 - - label $w.path -textvariable browser_path($w_list) \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken \ - -font font_uibold - pack $w.path -anchor w -side top -fill x - - frame $w.list - text $w_list -background white -borderwidth 0 \ - -cursor $cursor_ptr \ - -state disabled \ - -wrap none \ - -height 20 \ - -width 70 \ - -xscrollcommand [list $w.list.sbx set] \ - -yscrollcommand [list $w.list.sby set] \ - -font font_ui - $w_list tag conf in_sel \ - -background [$w_list cget -foreground] \ - -foreground [$w_list cget -background] - scrollbar $w.list.sbx -orient h -command [list $w_list xview] - scrollbar $w.list.sby -orient v -command [list $w_list yview] - pack $w.list.sbx -side bottom -fill x - pack $w.list.sby -side right -fill y - pack $w_list -side left -fill both -expand 1 - pack $w.list -side top -fill both -expand 1 - - label $w.status -textvariable browser_status($w_list) \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken \ - -font font_ui - pack $w.status -anchor w -side bottom -fill x - - bind $w_list "browser_click 0 $w_list @%x,%y;break" - bind $w_list "browser_click 1 $w_list @%x,%y;break" - bind $w_list <$M1B-Up> "browser_parent $w_list;break" - bind $w_list <$M1B-Left> "browser_parent $w_list;break" - bind $w_list "browser_move -1 $w_list;break" - bind $w_list "browser_move 1 $w_list;break" - bind $w_list <$M1B-Right> "browser_enter $w_list;break" - bind $w_list "browser_enter $w_list;break" - bind $w_list "browser_page -1 $w_list;break" - bind $w_list "browser_page 1 $w_list;break" - bind $w_list break - bind $w_list break - - bind $tl "focus $w" - bind $tl " - array unset browser_buffer $w_list - array unset browser_files $w_list - array unset browser_status $w_list - array unset browser_stack $w_list - array unset browser_path $w_list - array unset browser_commit $w_list - array unset browser_busy $w_list - " - wm title $tl "[appname] ([reponame]): File Browser" - ls_tree $w_list $browser_commit($w_list) {} - } - - proc browser_move {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return - set lno [lindex [split [$w index in_sel.first] .] 0] - incr lno $dir - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { - $w tag remove in_sel 0.0 end - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 - $w see $lno.0 - } - } - - proc browser_page {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return - $w yview scroll $dir pages - set lno [expr {int( - [lindex [$w yview] 0] - * [llength $browser_files($w)] - + 1)}] - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { - $w tag remove in_sel 0.0 end - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 - $w see $lno.0 - } - } - - proc browser_parent {w} { - global browser_files browser_status browser_path - global browser_stack browser_busy - - if {$browser_busy($w)} return - set info [lindex $browser_files($w) 0] - if {[lindex $info 0] eq {parent}} { - set parent [lindex $browser_stack($w) end-1] - set browser_stack($w) [lrange $browser_stack($w) 0 end-2] - if {$browser_stack($w) eq {}} { - regsub {:.*$} $browser_path($w) {:} browser_path($w) - } else { - regsub {/[^/]+$} $browser_path($w) {} browser_path($w) - } - set browser_status($w) "Loading $browser_path($w)..." - ls_tree $w [lindex $parent 0] [lindex $parent 1] - } - } - - proc browser_enter {w} { - global browser_files browser_status browser_path - global browser_commit browser_stack browser_busy - - if {$browser_busy($w)} return - set lno [lindex [split [$w index in_sel.first] .] 0] - set info [lindex $browser_files($w) [expr {$lno - 1}]] - if {$info ne {}} { - switch -- [lindex $info 0] { - parent { - browser_parent $w - } - tree { - set name [lindex $info 2] - set escn [escape_path $name] - set browser_status($w) "Loading $escn..." - append browser_path($w) $escn - ls_tree $w [lindex $info 1] $name - } - blob { - set name [lindex $info 2] - set p {} - foreach n $browser_stack($w) { - append p [lindex $n 1] - } - append p $name - show_blame $browser_commit($w) $p - } - } - } - } - - proc browser_click {was_double_click w pos} { - global browser_files browser_busy - - if {$browser_busy($w)} return - set lno [lindex [split [$w index $pos] .] 0] - focus $w - - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { - $w tag remove in_sel 0.0 end - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 - if {$was_double_click} { - browser_enter $w - } - } - } - - proc ls_tree {w tree_id name} { - global browser_buffer browser_files browser_stack browser_busy - - set browser_buffer($w) {} - set browser_files($w) {} - set browser_busy($w) 1 - - $w conf -state normal - $w tag remove in_sel 0.0 end - $w delete 0.0 end - if {$browser_stack($w) ne {}} { - $w image create end \ - -align center -padx 5 -pady 1 \ - -name icon0 \ - -image file_uplevel - $w insert end {[Up To Parent]} - lappend browser_files($w) parent - } - lappend browser_stack($w) [list $tree_id $name] - $w conf -state disabled - - set cmd [list git ls-tree -z $tree_id] - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation binary -encoding binary - fileevent $fd readable [list read_ls_tree $fd $w] - } - - proc read_ls_tree {fd w} { - global browser_buffer browser_files browser_status browser_busy - - if {![winfo exists $w]} { - catch {close $fd} - return - } - - append browser_buffer($w) [read $fd] - set pck [split $browser_buffer($w) "\0"] - set browser_buffer($w) [lindex $pck end] - - set n [llength $browser_files($w)] - $w conf -state normal - foreach p [lrange $pck 0 end-1] { - set info [split $p "\t"] - set path [lindex $info 1] - set info [split [lindex $info 0] { }] - set type [lindex $info 1] - set object [lindex $info 2] - - switch -- $type { - blob { - set image file_mod - } - tree { - set image file_dir - append path / - } - default { - set image file_question - } - } - - if {$n > 0} {$w insert end "\n"} - $w image create end \ - -align center -padx 5 -pady 1 \ - -name icon[incr n] \ - -image $image - $w insert end [escape_path $path] - lappend browser_files($w) [list $type $object $path] - } - $w conf -state disabled - - if {[eof $fd]} { - close $fd - set browser_status($w) Ready. - set browser_busy($w) 0 - array unset browser_buffer $w - if {$n > 0} { - $w tag add in_sel 1.0 2.0 - focus -force $w - } - } - } - - proc show_blame {commit path} { - global next_browser_id blame_status blame_data - - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set blame_status($w) {Loading current file content...} - - label $w.path -text "$commit:$path" \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken \ - -font font_uibold - pack $w.path -side top -fill x - - frame $w.out - text $w.out.loaded_t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 40 \ - -width 1 \ - -font font_diff - $w.out.loaded_t tag conf annotated -background grey - - text $w.out.linenumber_t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 40 \ - -width 5 \ - -font font_diff - $w.out.linenumber_t tag conf linenumber -justify right - - text $w.out.file_t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 40 \ - -width 80 \ - -xscrollcommand [list $w.out.sbx set] \ - -font font_diff - - scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview] - scrollbar $w.out.sby -orient v \ - -command [list scrollbar2many [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t \ - ] yview] - grid \ - $w.out.linenumber_t \ - $w.out.loaded_t \ - $w.out.file_t \ - $w.out.sby \ - -sticky nsew - grid conf $w.out.sbx -column 2 -sticky we - grid columnconfigure $w.out 2 -weight 1 - grid rowconfigure $w.out 0 -weight 1 - pack $w.out -fill both -expand 1 - - label $w.status -textvariable blame_status($w) \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken \ - -font font_ui - pack $w.status -side bottom -fill x - - frame $w.cm - text $w.cm.t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 10 \ - -width 80 \ - -xscrollcommand [list $w.cm.sbx set] \ - -yscrollcommand [list $w.cm.sby set] \ - -font font_diff - scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview] - scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview] - pack $w.cm.sby -side right -fill y - pack $w.cm.sbx -side bottom -fill x - pack $w.cm.t -expand 1 -fill both - pack $w.cm -side bottom -fill x - - menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy Commit" \ - -font font_ui \ - -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" - - foreach i [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t] { - $i tag conf in_sel \ - -background [$i cget -foreground] \ - -foreground [$i cget -background] - $i conf -yscrollcommand \ - [list many2scrollbar [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t \ - ] yview $w.out.sby] - bind $i " - blame_click {$w} \\ - $w.cm.t \\ - $w.out.linenumber_t \\ - $w.out.file_t \\ - $i @%x,%y - focus $i - " - bind_button3 $i " - set cursorX %x - set cursorY %y - set cursorW %W - tk_popup $w.ctxm %X %Y - " - } - - bind $w.cm.t "focus $w.cm.t" - bind $tl "focus $tl" - bind $tl " - array unset blame_status {$w} - array unset blame_data $w,* - " - wm title $tl "[appname] ([reponame]): File Viewer" - - set blame_data($w,commit_count) 0 - set blame_data($w,commit_list) {} - set blame_data($w,total_lines) 0 - set blame_data($w,blame_lines) 0 - set blame_data($w,highlight_commit) {} - set blame_data($w,highlight_line) -1 - - set cmd [list git cat-file blob "$commit:$path"] - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_catfile \ - $fd $w $commit $path \ - $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t] - } - - proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return - } - - set n $blame_data($w,total_lines) - $w_load conf -state normal - $w_line conf -state normal - $w_file conf -state normal - while {[gets $fd line] >= 0} { - regsub "\r\$" $line {} line - incr n - $w_load insert end "\n" - $w_line insert end "$n\n" linenumber - $w_file insert end "$line\n" - } - $w_load conf -state disabled - $w_line conf -state disabled - $w_file conf -state disabled - set blame_data($w,total_lines) $n - - if {[eof $fd]} { - close $fd - blame_incremental_status $w - set cmd [list git blame -M -C --incremental] - lappend cmd $commit -- $path - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_incremental $fd $w \ - $w_load $w_cmit $w_line $w_file] - } - } - - proc read_blame_incremental {fd w w_load w_cmit w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return - } - - while {[gets $fd line] >= 0} { - if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ - cmit original_line final_line line_count]} { - set blame_data($w,commit) $cmit - set blame_data($w,original_line) $original_line - set blame_data($w,final_line) $final_line - set blame_data($w,line_count) $line_count - - if {[catch {set g $blame_data($w,$cmit,order)}]} { - $w_line tag conf g$cmit - $w_file tag conf g$cmit - $w_line tag raise in_sel - $w_file tag raise in_sel - $w_file tag raise sel - set blame_data($w,$cmit,order) $blame_data($w,commit_count) - incr blame_data($w,commit_count) - lappend blame_data($w,commit_list) $cmit - } - } elseif {[string match {filename *} $line]} { - set file [string range $line 9 end] - set n $blame_data($w,line_count) - set lno $blame_data($w,final_line) - set cmit $blame_data($w,commit) - - while {$n > 0} { - if {[catch {set g g$blame_data($w,line$lno,commit)}]} { - $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" - } else { - $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" - $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" - } - - set blame_data($w,line$lno,commit) $cmit - set blame_data($w,line$lno,file) $file - $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - - if {$blame_data($w,highlight_line) == -1} { - if {[lindex [$w_file yview] 0] == 0} { - $w_file see $lno.0 - blame_showcommit $w $w_cmit $w_line $w_file $lno - } - } elseif {$blame_data($w,highlight_line) == $lno} { - blame_showcommit $w $w_cmit $w_line $w_file $lno - } - - incr n -1 - incr lno - incr blame_data($w,blame_lines) - } - - set hc $blame_data($w,highlight_commit) - if {$hc ne {} - && [expr {$blame_data($w,$hc,order) + 1}] - == $blame_data($w,$cmit,order)} { - blame_showcommit $w $w_cmit $w_line $w_file \ - $blame_data($w,highlight_line) - } - } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { - set blame_data($w,$blame_data($w,commit),$header) $data - } - } - - if {[eof $fd]} { - close $fd - set blame_status($w) {Annotation complete.} - } else { - blame_incremental_status $w - } - } - - proc blame_incremental_status {w} { - global blame_status blame_data - - set have $blame_data($w,blame_lines) - set total $blame_data($w,total_lines) - set pdone 0 - if {$total} {set pdone [expr {100 * $have / $total}]} - - set blame_status($w) [format \ - "Loading annotations... %i of %i lines annotated (%2i%%)" \ - $have $total $pdone] - } - - proc blame_click {w w_cmit w_line w_file cur_w pos} { - set lno [lindex [split [$cur_w index $pos] .] 0] - if {$lno eq {}} return - - $w_line tag remove in_sel 0.0 end - $w_file tag remove in_sel 0.0 end - $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" - $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" - - blame_showcommit $w $w_cmit $w_line $w_file $lno ++ lappend file_lists($w) $path ++ set lno [expr {[lindex [split [$w index end] .] 0] - 1}] ++ $w image create end \ ++ -align center -padx 5 -pady 1 \ ++ -name $icon_name \ ++ -image [mapicon $w $m $path] ++ $w insert end "[escape_path $path]\n" +} + - set blame_colors { - #ff4040 - #ff40ff - #4040ff - } ++proc display_all_files {} { ++ global ui_index ui_workdir ++ global file_states file_lists ++ global last_clicked + - proc blame_showcommit {w w_cmit w_line w_file lno} { - global blame_colors blame_data repo_config - - set cmit $blame_data($w,highlight_commit) - if {$cmit ne {}} { - set idx $blame_data($w,$cmit,order) - set i 0 - foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] - $w_line tag conf g$h -background white - $w_file tag conf g$h -background white - incr i - } - } ++ $ui_index conf -state normal ++ $ui_workdir conf -state normal + - $w_cmit conf -state normal - $w_cmit delete 0.0 end - if {[catch {set cmit $blame_data($w,line$lno,commit)}]} { - set cmit {} - $w_cmit insert end "Loading annotation..." - } else { - set idx $blame_data($w,$cmit,order) - set i 0 - foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] - $w_line tag conf g$h -background $c - $w_file tag conf g$h -background $c - incr i - } ++ $ui_index delete 0.0 end ++ $ui_workdir delete 0.0 end ++ set last_clicked {} + - set author_name {} - set author_email {} - set author_time {} - catch {set author_name $blame_data($w,$cmit,author)} - catch {set author_email $blame_data($w,$cmit,author-mail)} - catch {set author_time [clock format $blame_data($w,$cmit,author-time)]} - - set committer_name {} - set committer_email {} - set committer_time {} - catch {set committer_name $blame_data($w,$cmit,committer)} - catch {set committer_email $blame_data($w,$cmit,committer-mail)} - catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} - - if {[catch {set msg $blame_data($w,$cmit,message)}]} { - set msg {} - catch { - set fd [open "| git cat-file commit $cmit" r] - fconfigure $fd -encoding binary -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - while {[gets $fd line] > 0} { - if {[string match {encoding *} $line]} { - set enc [string tolower [string range $line 9 end]] - } - } - set msg [encoding convertfrom $enc [read $fd]] - set msg [string trim $msg] - close $fd ++ set file_lists($ui_index) [list] ++ set file_lists($ui_workdir) [list] + - set author_name [encoding convertfrom $enc $author_name] - set committer_name [encoding convertfrom $enc $committer_name] ++ foreach path [lsort [array names file_states]] { ++ set s $file_states($path) ++ set m [lindex $s 0] ++ set icon_name [lindex $s 1] + - set blame_data($w,$cmit,author) $author_name - set blame_data($w,$cmit,committer) $committer_name - } - set blame_data($w,$cmit,message) $msg ++ set s [string index $m 0] ++ if {$s ne {U} && $s ne {_}} { ++ display_all_files_helper $ui_index $path \ ++ $icon_name $s + } + - $w_cmit insert end "commit $cmit\n" - $w_cmit insert end "Author: $author_name $author_email $author_time\n" - $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" - $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n" - $w_cmit insert end "\n" - $w_cmit insert end $msg ++ if {[string index $m 0] eq {U}} { ++ set s U ++ } else { ++ set s [string index $m 1] ++ } ++ if {$s ne {_}} { ++ display_all_files_helper $ui_workdir $path \ ++ $icon_name $s ++ } + } - $w_cmit conf -state disabled - - set blame_data($w,highlight_line) $lno - set blame_data($w,highlight_commit) $cmit - } + - proc blame_copycommit {w i pos} { - global blame_data - set lno [lindex [split [$i index $pos] .] 0] - if {![catch {set commit $blame_data($w,line$lno,commit)}]} { - clipboard clear - clipboard append \ - -format STRING \ - -type STRING \ - -- $commit - } ++ $ui_index conf -state disabled ++ $ui_workdir conf -state disabled +} + +###################################################################### +## +## icons + +set filemask { +#define mask_width 14 +#define mask_height 15 +static unsigned char mask_bits[] = { + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f}; +} + +image create bitmap file_plain -background white -foreground black -data { +#define plain_width 14 +#define plain_height 15 +static unsigned char plain_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10, + 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, + 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_mod -background white -foreground blue -data { +#define mod_width 14 +#define mod_height 15 +static unsigned char mod_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_fulltick -background white -foreground "#007000" -data { +#define file_fulltick_width 14 +#define file_fulltick_height 15 +static unsigned char file_fulltick_bits[] = { + 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16, + 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10, + 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_parttick -background white -foreground "#005050" -data { +#define parttick_width 14 +#define parttick_height 15 +static unsigned char parttick_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, + 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10, + 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_question -background white -foreground black -data { +#define file_question_width 14 +#define file_question_height 15 +static unsigned char file_question_bits[] = { + 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13, + 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10, + 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_removed -background white -foreground red -data { +#define file_removed_width 14 +#define file_removed_height 15 +static unsigned char file_removed_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10, + 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13, + 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_merge -background white -foreground blue -data { +#define file_merge_width 14 +#define file_merge_height 15 +static unsigned char file_merge_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10, + 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +set file_dir_data { +#define file_width 18 +#define file_height 18 +static unsigned char file_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, + 0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00, + 0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00, + 0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +} +image create bitmap file_dir -background white -foreground blue \ + -data $file_dir_data -maskdata $file_dir_data +unset file_dir_data + +set file_uplevel_data { +#define up_width 15 +#define up_height 15 +static unsigned char up_bits[] = { + 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f, + 0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00}; +} +image create bitmap file_uplevel -background white -foreground red \ + -data $file_uplevel_data -maskdata $file_uplevel_data +unset file_uplevel_data + +set ui_index .vpane.files.index.list +set ui_workdir .vpane.files.workdir.list + +set all_icons(_$ui_index) file_plain +set all_icons(A$ui_index) file_fulltick +set all_icons(M$ui_index) file_fulltick +set all_icons(D$ui_index) file_removed +set all_icons(U$ui_index) file_merge + +set all_icons(_$ui_workdir) file_plain +set all_icons(M$ui_workdir) file_mod +set all_icons(D$ui_workdir) file_question +set all_icons(U$ui_workdir) file_merge +set all_icons(O$ui_workdir) file_plain + +set max_status_desc 0 +foreach i { + {__ "Unmodified"} + + {_M "Modified, not staged"} + {M_ "Staged for commit"} + {MM "Portions staged for commit"} + {MD "Staged for commit, missing"} + + {_O "Untracked, not staged"} + {A_ "Staged for commit"} + {AM "Portions staged for commit"} + {AD "Staged for commit, missing"} + + {_D "Missing"} + {D_ "Staged for removal"} + {DO "Staged for removal, still present"} + + {U_ "Requires merge resolution"} + {UU "Requires merge resolution"} + {UM "Requires merge resolution"} + {UD "Requires merge resolution"} + } { + if {$max_status_desc < [string length [lindex $i 1]]} { + set max_status_desc [string length [lindex $i 1]] + } + set all_descs([lindex $i 0]) [lindex $i 1] +} +unset i + +###################################################################### +## +## util + +proc bind_button3 {w cmd} { + bind $w $cmd + if {[is_MacOSX]} { + bind $w $cmd + } +} + +proc scrollbar2many {list mode args} { + foreach w $list {eval $w $mode $args} +} + +proc many2scrollbar {list mode sb top bottom} { + $sb set $top $bottom + foreach w $list {$w $mode moveto $top} +} + +proc incr_font_size {font {amt 1}} { + set sz [font configure $font -size] + incr sz $amt + font configure $font -size $sz + font configure ${font}bold -size $sz +} + - proc hook_failed_popup {hook msg} { - set w .hookfail - toplevel $w - - frame $w.m - label $w.m.l1 -text "$hook hook failed:" \ - -anchor w \ - -justify left \ - -font font_uibold - text $w.m.t \ - -background white -borderwidth 1 \ - -relief sunken \ - -width 80 -height 10 \ - -font font_diff \ - -yscrollcommand [list $w.m.sby set] - label $w.m.l2 \ - -text {You must correct the above errors before committing.} \ - -anchor w \ - -justify left \ - -font font_uibold - scrollbar $w.m.sby -command [list $w.m.t yview] - pack $w.m.l1 -side top -fill x - pack $w.m.l2 -side bottom -fill x - pack $w.m.sby -side right -fill y - pack $w.m.t -side left -fill both -expand 1 - pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 - - $w.m.t insert 1.0 $msg - $w.m.t conf -state disabled - - button $w.ok -text OK \ - -width 15 \ - -font font_ui \ - -command "destroy $w" - pack $w.ok -side bottom -anchor e -pady 10 -padx 10 - - bind $w "grab $w; focus $w" - bind $w "destroy $w" - wm title $w "[appname] ([reponame]): error" - tkwait window $w - } - - set next_console_id 0 - - proc new_console {short_title long_title} { - global next_console_id console_data - set w .console[incr next_console_id] - set console_data($w) [list $short_title $long_title] - return [console_init $w] - } - - proc console_init {w} { - global console_cr console_data M1B - - set console_cr($w) 1.0 - toplevel $w - frame $w.m - label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ - -anchor w \ - -justify left \ - -font font_uibold - text $w.m.t \ - -background white -borderwidth 1 \ - -relief sunken \ - -width 80 -height 10 \ - -font font_diff \ - -state disabled \ - -yscrollcommand [list $w.m.sby set] - label $w.m.s -text {Working... please wait...} \ - -anchor w \ - -justify left \ - -font font_uibold - scrollbar $w.m.sby -command [list $w.m.t yview] - pack $w.m.l1 -side top -fill x - pack $w.m.s -side bottom -fill x - pack $w.m.sby -side right -fill y - pack $w.m.t -side left -fill both -expand 1 - pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 - - menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy" \ - -font font_ui \ - -command "tk_textCopy $w.m.t" - $w.ctxm add command -label "Select All" \ - -font font_ui \ - -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" - $w.ctxm add command -label "Copy All" \ - -font font_ui \ - -command " - $w.m.t tag add sel 0.0 end - tk_textCopy $w.m.t - $w.m.t tag remove sel 0.0 end - " - - button $w.ok -text {Close} \ - -font font_ui \ - -state disabled \ - -command "destroy $w" - pack $w.ok -side bottom -anchor e -pady 10 -padx 10 - - bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y" - bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" - bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" - bind $w "focus $w" - wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]" - return $w - } - - proc console_exec {w cmd after} { - # -- Cygwin's Tcl tosses the enviroment when we exec our child. - # But most users need that so we have to relogin. :-( - # - if {[is_Cygwin]} { - set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] - } - - # -- Tcl won't let us redirect both stdout and stderr to - # the same pipe. So pass it through cat... - # - set cmd [concat | $cmd |& cat] - - set fd_f [open $cmd r] - fconfigure $fd_f -blocking 0 -translation binary - fileevent $fd_f readable [list console_read $w $fd_f $after] - } - - proc console_read {w fd after} { - global console_cr - - set buf [read $fd] - if {$buf ne {}} { - if {![winfo exists $w]} {console_init $w} - $w.m.t conf -state normal - set c 0 - set n [string length $buf] - while {$c < $n} { - set cr [string first "\r" $buf $c] - set lf [string first "\n" $buf $c] - if {$cr < 0} {set cr [expr {$n + 1}]} - if {$lf < 0} {set lf [expr {$n + 1}]} - - if {$lf < $cr} { - $w.m.t insert end [string range $buf $c $lf] - set console_cr($w) [$w.m.t index {end -1c}] - set c $lf - incr c - } else { - $w.m.t delete $console_cr($w) end - $w.m.t insert end "\n" - $w.m.t insert end [string range $buf $c $cr] - set c $cr - incr c - } - } - $w.m.t conf -state disabled - $w.m.t see end - } - - fconfigure $fd -blocking 1 - if {[eof $fd]} { - if {[catch {close $fd}]} { - set ok 0 - } else { - set ok 1 - } - uplevel #0 $after $w $ok - return - } - fconfigure $fd -blocking 0 - } - - proc console_chain {cmdlist w {ok 1}} { - if {$ok} { - if {[llength $cmdlist] == 0} { - console_done $w $ok - return - } - - set cmd [lindex $cmdlist 0] - set cmdlist [lrange $cmdlist 1 end] - - if {[lindex $cmd 0] eq {console_exec}} { - console_exec $w \ - [lindex $cmd 1] \ - [list console_chain $cmdlist] - } else { - uplevel #0 $cmd $cmdlist $w $ok - } - } else { - console_done $w $ok - } - } - - proc console_done {args} { - global console_cr console_data - - switch -- [llength $args] { - 2 { - set w [lindex $args 0] - set ok [lindex $args 1] - } - 3 { - set w [lindex $args 1] - set ok [lindex $args 2] - } - default { - error "wrong number of args: console_done ?ignored? w ok" - } - } - - if {$ok} { - if {[winfo exists $w]} { - $w.m.s conf -background green -text {Success} - $w.ok conf -state normal - focus $w.ok - } - } else { - if {![winfo exists $w]} { - console_init $w - } - $w.m.s conf -background red -text {Error: Command Failed} - $w.ok conf -state normal - focus $w.ok - } - - array unset console_cr $w - array unset console_data $w - } - +###################################################################### +## +## ui commands + +set starting_gitk_msg {Starting gitk... please wait...} + +proc do_gitk {revs} { + global env ui_status_value starting_gitk_msg + + # -- Always start gitk through whatever we were loaded with. This + # lets us bypass using shell process on Windows systems. + # + set cmd [list [info nameofexecutable]] + lappend cmd [gitexec gitk] + if {$revs ne {}} { + append cmd { } + append cmd $revs + } + + if {[catch {eval exec $cmd &} err]} { + error_popup "Failed to start gitk:\n\n$err" + } else { + set ui_status_value $starting_gitk_msg + after 10000 { + if {$ui_status_value eq $starting_gitk_msg} { + set ui_status_value {Ready.} + } + } + } +} + - proc do_stats {} { - set fd [open "| git count-objects -v" r] - while {[gets $fd line] > 0} { - if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { - set stats($name) $value - } - } - close $fd - - set packed_sz 0 - foreach p [glob -directory [gitdir objects pack] \ - -type f \ - -nocomplain -- *] { - incr packed_sz [file size $p] - } - if {$packed_sz > 0} { - set stats(size-pack) [expr {$packed_sz / 1024}] - } - - set w .stats_view - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Database Statistics} \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons -border 1 - button $w.buttons.close -text Close \ - -font font_ui \ - -default active \ - -command [list destroy $w] - button $w.buttons.gc -text {Compress Database} \ - -font font_ui \ - -default normal \ - -command "destroy $w;do_gc" - pack $w.buttons.close -side right - pack $w.buttons.gc -side left - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - frame $w.stat -borderwidth 1 -relief solid - foreach s { - {count {Number of loose objects}} - {size {Disk space used by loose objects} { KiB}} - {in-pack {Number of packed objects}} - {packs {Number of packs}} - {size-pack {Disk space used by packed objects} { KiB}} - {prune-packable {Packed objects waiting for pruning}} - {garbage {Garbage files}} - } { - set name [lindex $s 0] - set label [lindex $s 1] - if {[catch {set value $stats($name)}]} continue - if {[llength $s] > 2} { - set value "$value[lindex $s 2]" - } - - label $w.stat.l_$name -text "$label:" -anchor w -font font_ui - label $w.stat.v_$name -text $value -anchor w -font font_ui - grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} - } - pack $w.stat -pady 10 -padx 10 - - bind $w "grab $w; focus $w.buttons.close" - bind $w [list destroy $w] - bind $w [list destroy $w] - wm title $w "[appname] ([reponame]): Database Statistics" - tkwait window $w - } - - proc do_gc {} { - set w [new_console {gc} {Compressing the object database}] - console_chain { - {console_exec {git pack-refs --prune}} - {console_exec {git reflog expire --all}} - {console_exec {git repack -a -d -l}} - {console_exec {git rerere gc}} - } $w - } - - proc do_fsck_objects {} { - set w [new_console {fsck-objects} \ - {Verifying the object database with fsck-objects}] - set cmd [list git fsck-objects] - lappend cmd --full - lappend cmd --cache - lappend cmd --strict - console_exec $w $cmd console_done - } - +set is_quitting 0 + +proc do_quit {} { + global ui_comm is_quitting repo_config commit_type + + if {$is_quitting} return + set is_quitting 1 + + if {[winfo exists $ui_comm]} { + # -- Stash our current commit buffer. + # + set save [gitdir GITGUI_MSG] + set msg [string trim [$ui_comm get 0.0 end]] + regsub -all -line {[ \r\t]+$} $msg {} msg + if {(![string match amend* $commit_type] + || [$ui_comm edit modified]) + && $msg ne {}} { + catch { + set fd [open $save w] + puts -nonewline $fd $msg + close $fd + } + } else { + catch {file delete $save} + } + + # -- Stash our current window geometry into this repository. + # + set cfg_geometry [list] + lappend cfg_geometry [wm geometry .] + lappend cfg_geometry [lindex [.vpane sash coord 0] 1] + lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0] + if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { + set rc_geometry {} + } + if {$cfg_geometry ne $rc_geometry} { + catch {git config gui.geometry $cfg_geometry} + } + } + + destroy . +} + +proc do_rescan {} { + rescan {set ui_status_value {Ready.}} +} + - proc unstage_helper {txt paths} { - global file_states current_diff_path - - if {![lock_index begin-update]} return - - set pathList [list] - set after {} - foreach path $paths { - switch -glob -- [lindex $file_states($path) 0] { - A? - - M? - - D? { - lappend pathList $path - if {$path eq $current_diff_path} { - set after {reshow_diff;} - } - } - } - } - if {$pathList eq {}} { - unlock_index - } else { - update_indexinfo \ - $txt \ - $pathList \ - [concat $after {set ui_status_value {Ready.}}] - } - } - - proc do_unstage_selection {} { - global current_diff_path selected_paths - - if {[array size selected_paths] > 0} { - unstage_helper \ - {Unstaging selected files from commit} \ - [array names selected_paths] - } elseif {$current_diff_path ne {}} { - unstage_helper \ - "Unstaging [short_path $current_diff_path] from commit" \ - [list $current_diff_path] - } - } - - proc add_helper {txt paths} { - global file_states current_diff_path - - if {![lock_index begin-update]} return - - set pathList [list] - set after {} - foreach path $paths { - switch -glob -- [lindex $file_states($path) 0] { - _O - - ?M - - ?D - - U? { - lappend pathList $path - if {$path eq $current_diff_path} { - set after {reshow_diff;} - } - } - } - } - if {$pathList eq {}} { - unlock_index - } else { - update_index \ - $txt \ - $pathList \ - [concat $after {set ui_status_value {Ready to commit.}}] - } - } - - proc do_add_selection {} { - global current_diff_path selected_paths - - if {[array size selected_paths] > 0} { - add_helper \ - {Adding selected files} \ - [array names selected_paths] - } elseif {$current_diff_path ne {}} { - add_helper \ - "Adding [short_path $current_diff_path]" \ - [list $current_diff_path] - } - } - - proc do_add_all {} { - global file_states - - set paths [list] - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - U? {continue} - ?M - - ?D {lappend paths $path} - } - } - add_helper {Adding all changed files} $paths - } - - proc revert_helper {txt paths} { - global file_states current_diff_path - - if {![lock_index begin-update]} return - - set pathList [list] - set after {} - foreach path $paths { - switch -glob -- [lindex $file_states($path) 0] { - U? {continue} - ?M - - ?D { - lappend pathList $path - if {$path eq $current_diff_path} { - set after {reshow_diff;} - } - } - } - } - - set n [llength $pathList] - if {$n == 0} { - unlock_index - return - } elseif {$n == 1} { - set s "[short_path [lindex $pathList]]" - } else { - set s "these $n files" - } - - set reply [tk_dialog \ - .confirm_revert \ - "[appname] ([reponame])" \ - "Revert changes in $s? - - Any unadded changes will be permanently lost by the revert." \ - question \ - 1 \ - {Do Nothing} \ - {Revert Changes} \ - ] - if {$reply == 1} { - checkout_index \ - $txt \ - $pathList \ - [concat $after {set ui_status_value {Ready.}}] - } else { - unlock_index - } - } - - proc do_revert_selection {} { - global current_diff_path selected_paths - - if {[array size selected_paths] > 0} { - revert_helper \ - {Reverting selected files} \ - [array names selected_paths] - } elseif {$current_diff_path ne {}} { - revert_helper \ - "Reverting [short_path $current_diff_path]" \ - [list $current_diff_path] - } - } - - proc do_signoff {} { - global ui_comm - - set me [committer_ident] - if {$me eq {}} return - - set sob "Signed-off-by: $me" - set last [$ui_comm get {end -1c linestart} {end -1c}] - if {$last ne $sob} { - $ui_comm edit separator - if {$last ne {} - && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} { - $ui_comm insert end "\n" - } - $ui_comm insert end "\n$sob" - $ui_comm edit separator - $ui_comm see end - } - } - - proc do_select_commit_type {} { - global commit_type selected_commit_type - - if {$selected_commit_type eq {new} - && [string match amend* $commit_type]} { - create_new_commit - } elseif {$selected_commit_type eq {amend} - && ![string match amend* $commit_type]} { - load_last_commit - - # The amend request was rejected... - # - if {![string match amend* $commit_type]} { - set selected_commit_type new - } - } - } - +proc do_commit {} { + commit_tree +} + - proc do_about {} { - global appvers copyright - global tcl_patchLevel tk_patchLevel - - set w .about_dialog - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text "About [appname]" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.close -text {Close} \ - -font font_ui \ - -default active \ - -command [list destroy $w] - pack $w.buttons.close -side right - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - label $w.desc \ - -text "git-gui - a graphical user interface for Git. - $copyright" \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid \ - -font font_ui - pack $w.desc -side top -fill x -padx 5 -pady 5 - - set v {} - append v "git-gui version $appvers\n" - append v "[git version]\n" - append v "\n" - if {$tcl_patchLevel eq $tk_patchLevel} { - append v "Tcl/Tk version $tcl_patchLevel" - } else { - append v "Tcl version $tcl_patchLevel" - append v ", Tk version $tk_patchLevel" - } - - label $w.vers \ - -text $v \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid \ - -font font_ui - pack $w.vers -side top -fill x -padx 5 -pady 5 - - menu $w.ctxm -tearoff 0 - $w.ctxm add command \ - -label {Copy} \ - -font font_ui \ - -command " - clipboard clear - clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] - " - - bind $w "grab $w; focus $w.buttons.close" - bind $w "destroy $w" - bind $w "destroy $w" - bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" - wm title $w "About [appname]" - tkwait window $w - } - - proc do_options {} { - global repo_config global_config font_descs - global repo_config_new global_config_new - - array unset repo_config_new - array unset global_config_new - foreach name [array names repo_config] { - set repo_config_new($name) $repo_config($name) - } - load_config 1 - foreach name [array names repo_config] { - switch -- $name { - gui.diffcontext {continue} - } - set repo_config_new($name) $repo_config($name) - } - foreach name [array names global_config] { - set global_config_new($name) $global_config($name) - } - - set w .options_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text "Options" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.restore -text {Restore Defaults} \ - -font font_ui \ - -default normal \ - -command do_restore_defaults - pack $w.buttons.restore -side left - button $w.buttons.save -text Save \ - -font font_ui \ - -default active \ - -command [list do_save_config $w] - pack $w.buttons.save -side right - button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ - -default normal \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.repo -text "[reponame] Repository" \ - -font font_ui - labelframe $w.global -text {Global (All Repositories)} \ - -font font_ui - pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 - pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 - - set optid 0 - foreach option { - {t user.name {User Name}} - {t user.email {Email Address}} - - {b merge.summary {Summarize Merge Commits}} - {i-1..5 merge.verbosity {Merge Verbosity}} - - {b gui.trustmtime {Trust File Modification Timestamps}} - {i-1..99 gui.diffcontext {Number of Diff Context Lines}} - {t gui.newbranchtemplate {New Branch Name Template}} - } { - set type [lindex $option 0] - set name [lindex $option 1] - set text [lindex $option 2] - incr optid - foreach f {repo global} { - switch -glob -- $type { - b { - checkbutton $w.$f.$optid -text $text \ - -variable ${f}_config_new($name) \ - -onvalue true \ - -offvalue false \ - -font font_ui - pack $w.$f.$optid -side top -anchor w - } - i-* { - regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max - frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" -font font_ui - pack $w.$f.$optid.l -side left -anchor w -fill x - spinbox $w.$f.$optid.v \ - -textvariable ${f}_config_new($name) \ - -from $min \ - -to $max \ - -increment 1 \ - -width [expr {1 + [string length $max]}] \ - -font font_ui - bind $w.$f.$optid.v {%W selection range 0 end} - pack $w.$f.$optid.v -side right -anchor e -padx 5 - pack $w.$f.$optid -side top -anchor w -fill x - } - t { - frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" -font font_ui - entry $w.$f.$optid.v \ - -borderwidth 1 \ - -relief sunken \ - -width 20 \ - -textvariable ${f}_config_new($name) \ - -font font_ui - pack $w.$f.$optid.l -side left -anchor w - pack $w.$f.$optid.v -side left -anchor w \ - -fill x -expand 1 \ - -padx 5 - pack $w.$f.$optid -side top -anchor w -fill x - } - } - } - } - - set all_fonts [lsort [font families]] - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - set text [lindex $option 2] - - set global_config_new(gui.$font^^family) \ - [font configure $font -family] - set global_config_new(gui.$font^^size) \ - [font configure $font -size] - - frame $w.global.$name - label $w.global.$name.l -text "$text:" -font font_ui - pack $w.global.$name.l -side left -anchor w -fill x - set fontmenu [eval tk_optionMenu $w.global.$name.family \ - global_config_new(gui.$font^^family) \ - $all_fonts] - $w.global.$name.family configure -font font_ui - $fontmenu configure -font font_ui - spinbox $w.global.$name.size \ - -textvariable global_config_new(gui.$font^^size) \ - -from 2 -to 80 -increment 1 \ - -width 3 \ - -font font_ui - bind $w.global.$name.size {%W selection range 0 end} - pack $w.global.$name.size -side right -anchor e - pack $w.global.$name.family -side right -anchor e - pack $w.global.$name -side top -anchor w -fill x - } - - bind $w "grab $w; focus $w.buttons.save" - bind $w "destroy $w" - bind $w [list do_save_config $w] - wm title $w "[appname] ([reponame]): Options" - tkwait window $w - } - - proc do_restore_defaults {} { - global font_descs default_config repo_config - global repo_config_new global_config_new - - foreach name [array names default_config] { - set repo_config_new($name) $default_config($name) - set global_config_new($name) $default_config($name) - } - - foreach option $font_descs { - set name [lindex $option 0] - set repo_config(gui.$name) $default_config(gui.$name) - } - apply_config - - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - set global_config_new(gui.$font^^family) \ - [font configure $font -family] - set global_config_new(gui.$font^^size) \ - [font configure $font -size] - } - } - - proc do_save_config {w} { - if {[catch {save_config} err]} { - error_popup "Failed to completely save options:\n\n$err" - } - reshow_diff - destroy $w - } - - proc do_windows_shortcut {} { - global argv0 - - set fn [tk_getSaveFile \ - -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialfile "Git [reponame].bat"] - if {$fn != {}} { - if {[catch { - set fd [open $fn w] - puts $fd "@ECHO Entering [reponame]" - puts $fd "@ECHO Starting git-gui... please wait..." - puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%" - puts $fd "@SET GIT_DIR=[file normalize [gitdir]]" - puts -nonewline $fd "@\"[info nameofexecutable]\"" - puts $fd " \"[file normalize $argv0]\"" - close $fd - } err]} { - error_popup "Cannot write script:\n\n$err" - } - } - } - - proc do_cygwin_shortcut {} { - global argv0 - - if {[catch { - set desktop [exec cygpath \ - --windows \ - --absolute \ - --long-name \ - --desktop] - }]} { - set desktop . - } - set fn [tk_getSaveFile \ - -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialdir $desktop \ - -initialfile "Git [reponame].bat"] - if {$fn != {}} { - if {[catch { - set fd [open $fn w] - set sh [exec cygpath \ - --windows \ - --absolute \ - /bin/sh] - set me [exec cygpath \ - --unix \ - --absolute \ - $argv0] - set gd [exec cygpath \ - --unix \ - --absolute \ - [gitdir]] - set gw [exec cygpath \ - --windows \ - --absolute \ - [file dirname [gitdir]]] - regsub -all ' $me "'\\''" me - regsub -all ' $gd "'\\''" gd - puts $fd "@ECHO Entering $gw" - puts $fd "@ECHO Starting git-gui... please wait..." - puts -nonewline $fd "@\"$sh\" --login -c \"" - puts -nonewline $fd "GIT_DIR='$gd'" - puts -nonewline $fd " '$me'" - puts $fd "&\"" - close $fd - } err]} { - error_popup "Cannot write script:\n\n$err" - } - } - } - - proc do_macosx_app {} { - global argv0 env - - set fn [tk_getSaveFile \ - -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialdir [file join $env(HOME) Desktop] \ - -initialfile "Git [reponame].app"] - if {$fn != {}} { - if {[catch { - set Contents [file join $fn Contents] - set MacOS [file join $Contents MacOS] - set exe [file join $MacOS git-gui] - - file mkdir $MacOS - - set fd [open [file join $Contents Info.plist] w] - puts $fd { - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - git-gui - CFBundleIdentifier - org.spearce.git-gui - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleSignature - ???? - CFBundleVersion - 1.0 - NSPrincipalClass - NSApplication - - } - close $fd - - set fd [open $exe w] - set gd [file normalize [gitdir]] - set ep [file normalize [gitexec]] - regsub -all ' $gd "'\\''" gd - regsub -all ' $ep "'\\''" ep - puts $fd "#!/bin/sh" - foreach name [array names env] { - if {[string match GIT_* $name]} { - regsub -all ' $env($name) "'\\''" v - puts $fd "export $name='$v'" - } - } - puts $fd "export PATH='$ep':\$PATH" - puts $fd "export GIT_DIR='$gd'" - puts $fd "exec [file normalize $argv0]" - close $fd - - file attributes $exe -permissions u+x,g+x,o+x - } err]} { - error_popup "Cannot write icon:\n\n$err" - } - } - } - +proc toggle_or_diff {w x y} { + global file_states file_lists current_diff_path ui_index ui_workdir + global last_clicked selected_paths + + set pos [split [$w index @$x,$y] .] + set lno [lindex $pos 0] + set col [lindex $pos 1] + set path [lindex $file_lists($w) [expr {$lno - 1}]] + if {$path eq {}} { + set last_clicked {} + return + } + + set last_clicked [list $w $lno] + array unset selected_paths + $ui_index tag remove in_sel 0.0 end + $ui_workdir tag remove in_sel 0.0 end + + if {$col == 0} { + if {$current_diff_path eq $path} { + set after {reshow_diff;} + } else { + set after {} + } + if {$w eq $ui_index} { + update_indexinfo \ + "Unstaging [short_path $path] from commit" \ + [list $path] \ + [concat $after {set ui_status_value {Ready.}}] + } elseif {$w eq $ui_workdir} { + update_index \ + "Adding [short_path $path]" \ + [list $path] \ + [concat $after {set ui_status_value {Ready.}}] + } + } else { + show_diff $path $w $lno + } +} + +proc add_one_to_selection {w x y} { + global file_lists last_clicked selected_paths + + set lno [lindex [split [$w index @$x,$y] .] 0] + set path [lindex $file_lists($w) [expr {$lno - 1}]] + if {$path eq {}} { + set last_clicked {} + return + } + + if {$last_clicked ne {} + && [lindex $last_clicked 0] ne $w} { + array unset selected_paths + [lindex $last_clicked 0] tag remove in_sel 0.0 end + } + + set last_clicked [list $w $lno] + if {[catch {set in_sel $selected_paths($path)}]} { + set in_sel 0 + } + if {$in_sel} { + unset selected_paths($path) + $w tag remove in_sel $lno.0 [expr {$lno + 1}].0 + } else { + set selected_paths($path) 1 + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + } +} + +proc add_range_to_selection {w x y} { + global file_lists last_clicked selected_paths + + if {[lindex $last_clicked 0] ne $w} { + toggle_or_diff $w $x $y + return + } + + set lno [lindex [split [$w index @$x,$y] .] 0] + set lc [lindex $last_clicked 1] + if {$lc < $lno} { + set begin $lc + set end $lno + } else { + set begin $lno + set end $lc + } + + foreach path [lrange $file_lists($w) \ + [expr {$begin - 1}] \ + [expr {$end - 1}]] { + set selected_paths($path) 1 + } + $w tag add in_sel $begin.0 [expr {$end + 1}].0 +} + +###################################################################### +## +## config defaults + +set cursor_ptr arrow +font create font_diff -family Courier -size 10 +font create font_ui +catch { + label .dummy + eval font configure font_ui [font actual [.dummy cget -font]] + destroy .dummy +} + +font create font_uibold +font create font_diffbold + ++foreach class {Button Checkbutton Entry Label ++ Labelframe Listbox Menu Message ++ Radiobutton Text} { ++ option add *$class.font font_ui ++} ++unset class ++ +if {[is_Windows]} { + set M1B Control + set M1T Ctrl +} elseif {[is_MacOSX]} { + set M1B M1 + set M1T Cmd +} else { + set M1B M1 + set M1T M1 +} + +proc apply_config {} { + global repo_config font_descs + + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + if {[catch { + foreach {cn cv} $repo_config(gui.$name) { + font configure $font $cn $cv + } + } err]} { + error_popup "Invalid font specified in gui.$name:\n\n$err" + } + foreach {cn cv} [font configure $font] { + font configure ${font}bold $cn $cv + } + font configure ${font}bold -weight bold + } +} + +set default_config(merge.summary) false +set default_config(merge.verbosity) 2 +set default_config(user.name) {} +set default_config(user.email) {} + +set default_config(gui.trustmtime) false +set default_config(gui.diffcontext) 5 +set default_config(gui.newbranchtemplate) {} +set default_config(gui.fontui) [font configure font_ui] +set default_config(gui.fontdiff) [font configure font_diff] +set font_descs { + {fontui font_ui {Main Font}} + {fontdiff font_diff {Diff/Console Font}} +} +load_config 0 +apply_config + +###################################################################### +## +## feature option selection + +if {[regexp {^git-(.+)$} [appname] _junk subcommand]} { + unset _junk +} else { + set subcommand gui +} +if {$subcommand eq {gui.sh}} { + set subcommand gui +} +if {$subcommand eq {gui} && [llength $argv] > 0} { + set subcommand [lindex $argv 0] + set argv [lrange $argv 1 end] +} + +enable_option multicommit +enable_option branch +enable_option transport + +switch -- $subcommand { +browser - +blame { + disable_option multicommit + disable_option branch + disable_option transport +} +citool { + enable_option singlecommit + + disable_option multicommit + disable_option branch + disable_option transport +} +} + +###################################################################### +## +## ui construction + +set ui_comm {} + +# -- Menu Bar +# +menu .mbar -tearoff 0 - .mbar add cascade -label Repository -menu .mbar.repository -font font_ui - .mbar add cascade -label Edit -menu .mbar.edit -font font_ui ++.mbar add cascade -label Repository -menu .mbar.repository ++.mbar add cascade -label Edit -menu .mbar.edit +if {[is_enabled branch]} { - .mbar add cascade -label Branch -menu .mbar.branch -font font_ui ++ .mbar add cascade -label Branch -menu .mbar.branch +} +if {[is_enabled multicommit] || [is_enabled singlecommit]} { - .mbar add cascade -label Commit -menu .mbar.commit -font font_ui ++ .mbar add cascade -label Commit -menu .mbar.commit +} +if {[is_enabled transport]} { - .mbar add cascade -label Merge -menu .mbar.merge -font font_ui - .mbar add cascade -label Fetch -menu .mbar.fetch -font font_ui - .mbar add cascade -label Push -menu .mbar.push -font font_ui ++ .mbar add cascade -label Merge -menu .mbar.merge ++ .mbar add cascade -label Fetch -menu .mbar.fetch ++ .mbar add cascade -label Push -menu .mbar.push +} +. configure -menu .mbar + +# -- Repository Menu +# +menu .mbar.repository + +.mbar.repository add command \ + -label {Browse Current Branch} \ - -command {new_browser $current_branch} \ - -font font_ui ++ -command {new_browser $current_branch} +trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#" +.mbar.repository add separator + +.mbar.repository add command \ + -label {Visualize Current Branch} \ - -command {do_gitk $current_branch} \ - -font font_ui ++ -command {do_gitk $current_branch} +trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#" +.mbar.repository add command \ + -label {Visualize All Branches} \ - -command {do_gitk --all} \ - -font font_ui ++ -command {do_gitk --all} +.mbar.repository add separator + +if {[is_enabled multicommit]} { + .mbar.repository add command -label {Database Statistics} \ - -command do_stats \ - -font font_ui ++ -command do_stats + + .mbar.repository add command -label {Compress Database} \ - -command do_gc \ - -font font_ui ++ -command do_gc + + .mbar.repository add command -label {Verify Database} \ - -command do_fsck_objects \ - -font font_ui ++ -command do_fsck_objects + + .mbar.repository add separator + + if {[is_Cygwin]} { + .mbar.repository add command \ + -label {Create Desktop Icon} \ - -command do_cygwin_shortcut \ - -font font_ui ++ -command do_cygwin_shortcut + } elseif {[is_Windows]} { + .mbar.repository add command \ + -label {Create Desktop Icon} \ - -command do_windows_shortcut \ - -font font_ui ++ -command do_windows_shortcut + } elseif {[is_MacOSX]} { + .mbar.repository add command \ + -label {Create Desktop Icon} \ - -command do_macosx_app \ - -font font_ui ++ -command do_macosx_app + } +} + +.mbar.repository add command -label Quit \ + -command do_quit \ - -accelerator $M1T-Q \ - -font font_ui ++ -accelerator $M1T-Q + +# -- Edit Menu +# +menu .mbar.edit +.mbar.edit add command -label Undo \ + -command {catch {[focus] edit undo}} \ - -accelerator $M1T-Z \ - -font font_ui ++ -accelerator $M1T-Z +.mbar.edit add command -label Redo \ + -command {catch {[focus] edit redo}} \ - -accelerator $M1T-Y \ - -font font_ui ++ -accelerator $M1T-Y +.mbar.edit add separator +.mbar.edit add command -label Cut \ + -command {catch {tk_textCut [focus]}} \ - -accelerator $M1T-X \ - -font font_ui ++ -accelerator $M1T-X +.mbar.edit add command -label Copy \ + -command {catch {tk_textCopy [focus]}} \ - -accelerator $M1T-C \ - -font font_ui ++ -accelerator $M1T-C +.mbar.edit add command -label Paste \ + -command {catch {tk_textPaste [focus]; [focus] see insert}} \ - -accelerator $M1T-V \ - -font font_ui ++ -accelerator $M1T-V +.mbar.edit add command -label Delete \ + -command {catch {[focus] delete sel.first sel.last}} \ - -accelerator Del \ - -font font_ui ++ -accelerator Del +.mbar.edit add separator +.mbar.edit add command -label {Select All} \ + -command {catch {[focus] tag add sel 0.0 end}} \ - -accelerator $M1T-A \ - -font font_ui ++ -accelerator $M1T-A + +# -- Branch Menu +# +if {[is_enabled branch]} { + menu .mbar.branch + + .mbar.branch add command -label {Create...} \ + -command do_create_branch \ - -accelerator $M1T-N \ - -font font_ui ++ -accelerator $M1T-N + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] + + .mbar.branch add command -label {Delete...} \ - -command do_delete_branch \ - -font font_ui ++ -command do_delete_branch + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] + + .mbar.branch add command -label {Reset...} \ - -command do_reset_hard \ - -font font_ui ++ -command merge::reset_hard + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] +} + +# -- Commit Menu +# +if {[is_enabled multicommit] || [is_enabled singlecommit]} { + menu .mbar.commit + + .mbar.commit add radiobutton \ + -label {New Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ - -value new \ - -font font_ui ++ -value new + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add radiobutton \ + -label {Amend Last Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ - -value amend \ - -font font_ui ++ -value amend + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add separator + + .mbar.commit add command -label Rescan \ + -command do_rescan \ - -accelerator F5 \ - -font font_ui ++ -accelerator F5 + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add command -label {Add To Commit} \ - -command do_add_selection \ - -font font_ui ++ -command do_add_selection + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add command -label {Add Existing To Commit} \ + -command do_add_all \ - -accelerator $M1T-I \ - -font font_ui ++ -accelerator $M1T-I + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add command -label {Unstage From Commit} \ - -command do_unstage_selection \ - -font font_ui ++ -command do_unstage_selection + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add command -label {Revert Changes} \ - -command do_revert_selection \ - -font font_ui ++ -command do_revert_selection + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add separator + + .mbar.commit add command -label {Sign Off} \ + -command do_signoff \ - -accelerator $M1T-S \ - -font font_ui ++ -accelerator $M1T-S + + .mbar.commit add command -label Commit \ + -command do_commit \ - -accelerator $M1T-Return \ - -font font_ui ++ -accelerator $M1T-Return + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] +} + +# -- Merge Menu +# +if {[is_enabled branch]} { + menu .mbar.merge + .mbar.merge add command -label {Local Merge...} \ - -command do_local_merge \ - -font font_ui ++ -command merge::dialog + lappend disable_on_lock \ + [list .mbar.merge entryconf [.mbar.merge index last] -state] + .mbar.merge add command -label {Abort Merge...} \ - -command do_reset_hard \ - -font font_ui ++ -command merge::reset_hard + lappend disable_on_lock \ + [list .mbar.merge entryconf [.mbar.merge index last] -state] + +} + +# -- Transport Menu +# +if {[is_enabled transport]} { + menu .mbar.fetch + + menu .mbar.push + .mbar.push add command -label {Push...} \ - -command do_push_anywhere \ - -font font_ui ++ -command do_push_anywhere +} + +if {[is_MacOSX]} { + # -- Apple Menu (Mac OS X only) + # + .mbar add cascade -label Apple -menu .mbar.apple + menu .mbar.apple + + .mbar.apple add command -label "About [appname]" \ - -command do_about \ - -font font_ui ++ -command do_about + .mbar.apple add command -label "Options..." \ - -command do_options \ - -font font_ui ++ -command do_options +} else { + # -- Edit Menu + # + .mbar.edit add separator + .mbar.edit add command -label {Options...} \ - -command do_options \ - -font font_ui ++ -command do_options + + # -- Tools Menu + # + if {[file exists /usr/local/miga/lib/gui-miga] + && [file exists .pvcsrc]} { + proc do_miga {} { + global ui_status_value + if {![lock_index update]} return + set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""] + set miga_fd [open "|$cmd" r] + fconfigure $miga_fd -blocking 0 + fileevent $miga_fd readable [list miga_done $miga_fd] + set ui_status_value {Running miga...} + } + proc miga_done {fd} { + read $fd 512 + if {[eof $fd]} { + close $fd + unlock_index + rescan [list set ui_status_value {Ready.}] + } + } + .mbar add cascade -label Tools -menu .mbar.tools + menu .mbar.tools + .mbar.tools add command -label "Migrate" \ - -command do_miga \ - -font font_ui ++ -command do_miga + lappend disable_on_lock \ + [list .mbar.tools entryconf [.mbar.tools index last] -state] + } +} + +# -- Help Menu +# - .mbar add cascade -label Help -menu .mbar.help -font font_ui ++.mbar add cascade -label Help -menu .mbar.help +menu .mbar.help + +if {![is_MacOSX]} { + .mbar.help add command -label "About [appname]" \ - -command do_about \ - -font font_ui ++ -command do_about +} + +set browser {} +catch {set browser $repo_config(instaweb.browser)} +set doc_path [file dirname [gitexec]] +set doc_path [file join $doc_path Documentation index.html] + +if {[is_Cygwin]} { + set doc_path [exec cygpath --mixed $doc_path] +} + +if {$browser eq {}} { + if {[is_MacOSX]} { + set browser open + } elseif {[is_Cygwin]} { + set program_files [file dirname [exec cygpath --windir]] + set program_files [file join $program_files {Program Files}] + set firefox [file join $program_files {Mozilla Firefox} firefox.exe] + set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE] + if {[file exists $firefox]} { + set browser $firefox + } elseif {[file exists $ie]} { + set browser $ie + } + unset program_files firefox ie + } +} + +if {[file isfile $doc_path]} { + set doc_url "file:$doc_path" +} else { + set doc_url {http://www.kernel.org/pub/software/scm/git/docs/} +} + +if {$browser ne {}} { + .mbar.help add command -label {Online Documentation} \ - -command [list exec $browser $doc_url &] \ - -font font_ui ++ -command [list exec $browser $doc_url &] +} +unset browser doc_path doc_url + +# -- Standard bindings +# +bind . do_quit +bind all <$M1B-Key-q> do_quit +bind all <$M1B-Key-Q> do_quit +bind all <$M1B-Key-w> {destroy [winfo toplevel %W]} +bind all <$M1B-Key-W> {destroy [winfo toplevel %W]} + +# -- Not a normal commit type invocation? Do that instead! +# +switch -- $subcommand { +browser { + if {[llength $argv] != 1} { + puts stderr "usage: $argv0 browser commit" + exit 1 + } + set current_branch [lindex $argv 0] + new_browser $current_branch + return +} +blame { + if {[llength $argv] != 2} { + puts stderr "usage: $argv0 blame commit path" + exit 1 + } + set current_branch [lindex $argv 0] + show_blame $current_branch [lindex $argv 1] + return +} +citool - +gui { + if {[llength $argv] != 0} { + puts -nonewline stderr "usage: $argv0" + if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} { + puts -nonewline stderr " $subcommand" + } + puts stderr {} + exit 1 + } + # fall through to setup UI for commits +} +default { + puts stderr "usage: $argv0 \[{blame|browser|citool}\]" + exit 1 +} +} + +# -- Branch Control +# +frame .branch \ + -borderwidth 1 \ + -relief sunken +label .branch.l1 \ + -text {Current Branch:} \ + -anchor w \ - -justify left \ - -font font_ui ++ -justify left +label .branch.cb \ + -textvariable current_branch \ + -anchor w \ - -justify left \ - -font font_ui ++ -justify left +pack .branch.l1 -side left +pack .branch.cb -side left -fill x +pack .branch -side top -fill x + +# -- Main Window Layout +# +panedwindow .vpane -orient vertical +panedwindow .vpane.files -orient horizontal +.vpane add .vpane.files -sticky nsew -height 100 -width 200 +pack .vpane -anchor n -side top -fill both -expand 1 + +# -- Index File List +# +frame .vpane.files.index -height 100 -width 200 +label .vpane.files.index.title -text {Changes To Be Committed} \ - -background green \ - -font font_ui ++ -background green +text $ui_index -background white -borderwidth 0 \ + -width 20 -height 10 \ + -wrap none \ - -font font_ui \ + -cursor $cursor_ptr \ + -xscrollcommand {.vpane.files.index.sx set} \ + -yscrollcommand {.vpane.files.index.sy set} \ + -state disabled +scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview] +scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview] +pack .vpane.files.index.title -side top -fill x +pack .vpane.files.index.sx -side bottom -fill x +pack .vpane.files.index.sy -side right -fill y +pack $ui_index -side left -fill both -expand 1 +.vpane.files add .vpane.files.index -sticky nsew + +# -- Working Directory File List +# +frame .vpane.files.workdir -height 100 -width 200 +label .vpane.files.workdir.title -text {Changed But Not Updated} \ - -background red \ - -font font_ui ++ -background red +text $ui_workdir -background white -borderwidth 0 \ + -width 20 -height 10 \ + -wrap none \ - -font font_ui \ + -cursor $cursor_ptr \ + -xscrollcommand {.vpane.files.workdir.sx set} \ + -yscrollcommand {.vpane.files.workdir.sy set} \ + -state disabled +scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] +scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] +pack .vpane.files.workdir.title -side top -fill x +pack .vpane.files.workdir.sx -side bottom -fill x +pack .vpane.files.workdir.sy -side right -fill y +pack $ui_workdir -side left -fill both -expand 1 +.vpane.files add .vpane.files.workdir -sticky nsew + +foreach i [list $ui_index $ui_workdir] { + $i tag conf in_diff -font font_uibold + $i tag conf in_sel \ + -background [$i cget -foreground] \ + -foreground [$i cget -background] +} +unset i + +# -- Diff and Commit Area +# +frame .vpane.lower -height 300 -width 400 +frame .vpane.lower.commarea +frame .vpane.lower.diff -relief sunken -borderwidth 1 +pack .vpane.lower.commarea -side top -fill x +pack .vpane.lower.diff -side bottom -fill both -expand 1 +.vpane add .vpane.lower -sticky nsew + +# -- Commit Area Buttons +# +frame .vpane.lower.commarea.buttons +label .vpane.lower.commarea.buttons.l -text {} \ + -anchor w \ - -justify left \ - -font font_ui ++ -justify left +pack .vpane.lower.commarea.buttons.l -side top -fill x +pack .vpane.lower.commarea.buttons -side left -fill y + +button .vpane.lower.commarea.buttons.rescan -text {Rescan} \ - -command do_rescan \ - -font font_ui ++ -command do_rescan +pack .vpane.lower.commarea.buttons.rescan -side top -fill x +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.rescan conf -state} + +button .vpane.lower.commarea.buttons.incall -text {Add Existing} \ - -command do_add_all \ - -font font_ui ++ -command do_add_all +pack .vpane.lower.commarea.buttons.incall -side top -fill x +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.incall conf -state} + +button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \ - -command do_signoff \ - -font font_ui ++ -command do_signoff +pack .vpane.lower.commarea.buttons.signoff -side top -fill x + +button .vpane.lower.commarea.buttons.commit -text {Commit} \ - -command do_commit \ - -font font_ui ++ -command do_commit +pack .vpane.lower.commarea.buttons.commit -side top -fill x +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.commit conf -state} + +# -- Commit Message Buffer +# +frame .vpane.lower.commarea.buffer +frame .vpane.lower.commarea.buffer.header +set ui_comm .vpane.lower.commarea.buffer.t +set ui_coml .vpane.lower.commarea.buffer.header.l +radiobutton .vpane.lower.commarea.buffer.header.new \ + -text {New Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ - -value new \ - -font font_ui ++ -value new +lappend disable_on_lock \ + [list .vpane.lower.commarea.buffer.header.new conf -state] +radiobutton .vpane.lower.commarea.buffer.header.amend \ + -text {Amend Last Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ - -value amend \ - -font font_ui ++ -value amend +lappend disable_on_lock \ + [list .vpane.lower.commarea.buffer.header.amend conf -state] +label $ui_coml \ + -anchor w \ - -justify left \ - -font font_ui ++ -justify left +proc trace_commit_type {varname args} { + global ui_coml commit_type + switch -glob -- $commit_type { + initial {set txt {Initial Commit Message:}} + amend {set txt {Amended Commit Message:}} + amend-initial {set txt {Amended Initial Commit Message:}} + amend-merge {set txt {Amended Merge Commit Message:}} + merge {set txt {Merge Commit Message:}} + * {set txt {Commit Message:}} + } + $ui_coml conf -text $txt +} +trace add variable commit_type write trace_commit_type +pack $ui_coml -side left -fill x +pack .vpane.lower.commarea.buffer.header.amend -side right +pack .vpane.lower.commarea.buffer.header.new -side right + +text $ui_comm -background white -borderwidth 1 \ + -undo true \ + -maxundo 20 \ + -autoseparators true \ + -relief sunken \ + -width 75 -height 9 -wrap none \ + -font font_diff \ + -yscrollcommand {.vpane.lower.commarea.buffer.sby set} +scrollbar .vpane.lower.commarea.buffer.sby \ + -command [list $ui_comm yview] +pack .vpane.lower.commarea.buffer.header -side top -fill x +pack .vpane.lower.commarea.buffer.sby -side right -fill y +pack $ui_comm -side left -fill y +pack .vpane.lower.commarea.buffer -side left -fill y + +# -- Commit Message Buffer Context Menu +# +set ctxm .vpane.lower.commarea.buffer.ctxm +menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Cut} \ - -font font_ui \ + -command {tk_textCut $ui_comm} +$ctxm add command \ + -label {Copy} \ - -font font_ui \ + -command {tk_textCopy $ui_comm} +$ctxm add command \ + -label {Paste} \ - -font font_ui \ + -command {tk_textPaste $ui_comm} +$ctxm add command \ + -label {Delete} \ - -font font_ui \ + -command {$ui_comm delete sel.first sel.last} +$ctxm add separator +$ctxm add command \ + -label {Select All} \ - -font font_ui \ + -command {focus $ui_comm;$ui_comm tag add sel 0.0 end} +$ctxm add command \ + -label {Copy All} \ - -font font_ui \ + -command { + $ui_comm tag add sel 0.0 end + tk_textCopy $ui_comm + $ui_comm tag remove sel 0.0 end + } +$ctxm add separator +$ctxm add command \ + -label {Sign Off} \ - -font font_ui \ + -command do_signoff +bind_button3 $ui_comm "tk_popup $ctxm %X %Y" + +# -- Diff Header +# +proc trace_current_diff_path {varname args} { + global current_diff_path diff_actions file_states + if {$current_diff_path eq {}} { + set s {} + set f {} + set p {} + set o disabled + } else { + set p $current_diff_path + set s [mapdesc [lindex $file_states($p) 0] $p] + set f {File:} + set p [escape_path $p] + set o normal + } + + .vpane.lower.diff.header.status configure -text $s + .vpane.lower.diff.header.file configure -text $f + .vpane.lower.diff.header.path configure -text $p + foreach w $diff_actions { + uplevel #0 $w $o + } +} +trace add variable current_diff_path write trace_current_diff_path + +frame .vpane.lower.diff.header -background orange +label .vpane.lower.diff.header.status \ + -background orange \ + -width $max_status_desc \ + -anchor w \ - -justify left \ - -font font_ui ++ -justify left +label .vpane.lower.diff.header.file \ + -background orange \ + -anchor w \ - -justify left \ - -font font_ui ++ -justify left +label .vpane.lower.diff.header.path \ + -background orange \ + -anchor w \ - -justify left \ - -font font_ui ++ -justify left +pack .vpane.lower.diff.header.status -side left +pack .vpane.lower.diff.header.file -side left +pack .vpane.lower.diff.header.path -fill x +set ctxm .vpane.lower.diff.header.ctxm +menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Copy} \ - -font font_ui \ + -command { + clipboard clear + clipboard append \ + -format STRING \ + -type STRING \ + -- $current_diff_path + } +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" + +# -- Diff Body +# +frame .vpane.lower.diff.body +set ui_diff .vpane.lower.diff.body.t +text $ui_diff -background white -borderwidth 0 \ + -width 80 -height 15 -wrap none \ + -font font_diff \ + -xscrollcommand {.vpane.lower.diff.body.sbx set} \ + -yscrollcommand {.vpane.lower.diff.body.sby set} \ + -state disabled +scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ + -command [list $ui_diff xview] +scrollbar .vpane.lower.diff.body.sby -orient vertical \ + -command [list $ui_diff yview] +pack .vpane.lower.diff.body.sbx -side bottom -fill x +pack .vpane.lower.diff.body.sby -side right -fill y +pack $ui_diff -side left -fill both -expand 1 +pack .vpane.lower.diff.header -side top -fill x +pack .vpane.lower.diff.body -side bottom -fill both -expand 1 + +$ui_diff tag conf d_cr -elide true +$ui_diff tag conf d_@ -foreground blue -font font_diffbold +$ui_diff tag conf d_+ -foreground {#00a000} +$ui_diff tag conf d_- -foreground red + +$ui_diff tag conf d_++ -foreground {#00a000} +$ui_diff tag conf d_-- -foreground red +$ui_diff tag conf d_+s \ + -foreground {#00a000} \ + -background {#e2effa} +$ui_diff tag conf d_-s \ + -foreground red \ + -background {#e2effa} +$ui_diff tag conf d_s+ \ + -foreground {#00a000} \ + -background ivory1 +$ui_diff tag conf d_s- \ + -foreground red \ + -background ivory1 + +$ui_diff tag conf d<<<<<<< \ + -foreground orange \ + -font font_diffbold +$ui_diff tag conf d======= \ + -foreground orange \ + -font font_diffbold +$ui_diff tag conf d>>>>>>> \ + -foreground orange \ + -font font_diffbold + +$ui_diff tag raise sel + +# -- Diff Body Context Menu +# +set ctxm .vpane.lower.diff.body.ctxm +menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Refresh} \ - -font font_ui \ + -command reshow_diff +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Copy} \ - -font font_ui \ + -command {tk_textCopy $ui_diff} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Select All} \ - -font font_ui \ + -command {focus $ui_diff;$ui_diff tag add sel 0.0 end} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Copy All} \ - -font font_ui \ + -command { + $ui_diff tag add sel 0.0 end + tk_textCopy $ui_diff + $ui_diff tag remove sel 0.0 end + } +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add separator +$ctxm add command \ + -label {Apply/Reverse Hunk} \ - -font font_ui \ + -command {apply_hunk $cursorX $cursorY} +set ui_diff_applyhunk [$ctxm index last] +lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state] +$ctxm add separator +$ctxm add command \ + -label {Decrease Font Size} \ - -font font_ui \ + -command {incr_font_size font_diff -1} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Increase Font Size} \ - -font font_ui \ + -command {incr_font_size font_diff 1} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add separator +$ctxm add command \ + -label {Show Less Context} \ - -font font_ui \ + -command {if {$repo_config(gui.diffcontext) >= 2} { + incr repo_config(gui.diffcontext) -1 + reshow_diff + }} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Show More Context} \ - -font font_ui \ + -command { + incr repo_config(gui.diffcontext) + reshow_diff + } +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add separator +$ctxm add command -label {Options...} \ - -font font_ui \ + -command do_options +bind_button3 $ui_diff " + set cursorX %x + set cursorY %y + if {\$ui_index eq \$current_diff_side} { + $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit} + } else { + $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit} + } + tk_popup $ctxm %X %Y +" +unset ui_diff_applyhunk + +# -- Status Bar +# +label .status -textvariable ui_status_value \ + -anchor w \ + -justify left \ + -borderwidth 1 \ - -relief sunken \ - -font font_ui ++ -relief sunken +pack .status -anchor w -side bottom -fill x + +# -- Load geometry +# +catch { +set gm $repo_config(gui.geometry) +wm geometry . [lindex $gm 0] +.vpane sash place 0 \ + [lindex [.vpane sash coord 0] 0] \ + [lindex $gm 1] +.vpane.files sash place 0 \ + [lindex $gm 2] \ + [lindex [.vpane.files sash coord 0] 1] +unset gm +} + +# -- Key Bindings +# +bind $ui_comm <$M1B-Key-Return> {do_commit;break} +bind $ui_comm <$M1B-Key-i> {do_add_all;break} +bind $ui_comm <$M1B-Key-I> {do_add_all;break} +bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break} +bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break} +bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break} +bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break} +bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break} +bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break} +bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break} +bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break} + +bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-v> {break} +bind $ui_diff <$M1B-Key-V> {break} +bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break} +bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break} +bind $ui_diff {catch {%W yview scroll -1 units};break} +bind $ui_diff {catch {%W yview scroll 1 units};break} +bind $ui_diff {catch {%W xview scroll -1 units};break} +bind $ui_diff {catch {%W xview scroll 1 units};break} ++bind $ui_diff {catch {%W yview scroll -1 units};break} ++bind $ui_diff {catch {%W yview scroll 1 units};break} ++bind $ui_diff {catch {%W xview scroll -1 units};break} ++bind $ui_diff {catch {%W xview scroll 1 units};break} ++bind $ui_diff {catch {%W yview scroll -1 pages};break} ++bind $ui_diff {catch {%W yview scroll 1 pages};break} +bind $ui_diff {focus %W} + +if {[is_enabled branch]} { + bind . <$M1B-Key-n> do_create_branch + bind . <$M1B-Key-N> do_create_branch +} + +bind all do_rescan +bind all <$M1B-Key-r> do_rescan +bind all <$M1B-Key-R> do_rescan +bind . <$M1B-Key-s> do_signoff +bind . <$M1B-Key-S> do_signoff +bind . <$M1B-Key-i> do_add_all +bind . <$M1B-Key-I> do_add_all +bind . <$M1B-Key-Return> do_commit +foreach i [list $ui_index $ui_workdir] { + bind $i "toggle_or_diff $i %x %y; break" + bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break" + bind $i "add_range_to_selection $i %x %y; break" +} +unset i + +set file_lists($ui_index) [list] +set file_lists($ui_workdir) [list] + +wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]" +focus -force $ui_comm + +# -- Warn the user about environmental problems. Cygwin's Tcl +# does *not* pass its env array onto any processes it spawns. +# This means that git processes get none of our environment. +# +if {[is_Cygwin]} { + set ignored_env 0 + set suggest_user {} + set msg "Possible environment issues exist. + +The following environment variables are probably +going to be ignored by any Git subprocess run +by [appname]: + +" + foreach name [array names env] { + switch -regexp -- $name { + {^GIT_INDEX_FILE$} - + {^GIT_OBJECT_DIRECTORY$} - + {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} - + {^GIT_DIFF_OPTS$} - + {^GIT_EXTERNAL_DIFF$} - + {^GIT_PAGER$} - + {^GIT_TRACE$} - + {^GIT_CONFIG$} - + {^GIT_CONFIG_LOCAL$} - + {^GIT_(AUTHOR|COMMITTER)_DATE$} { + append msg " - $name\n" + incr ignored_env + } + {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} { + append msg " - $name\n" + incr ignored_env + set suggest_user $name + } + } + } + if {$ignored_env > 0} { + append msg " +This is due to a known issue with the +Tcl binary distributed by Cygwin." + + if {$suggest_user ne {}} { + append msg " + +A good replacement for $suggest_user +is placing values for the user.name and +user.email settings into your personal +~/.gitconfig file. +" + } + warn_popup $msg + } + unset ignored_env msg suggest_user name +} + +# -- Only initialize complex UI if we are going to stay running. +# +if {[is_enabled transport]} { + load_all_remotes + load_all_heads + + populate_branch_menu + populate_fetch_menu + populate_push_menu +} + +# -- Only suggest a gc run if we are going to stay running. +# +if {[is_enabled multicommit]} { + set object_limit 2000 + if {[is_Windows]} {set object_limit 200} + regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current + if {$objects_current >= $object_limit} { + if {[ask_popup \ + "This repository currently has $objects_current loose objects. + +To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist. + +Compress the database now?"] eq yes} { + do_gc + } + } + unset object_limit _junk objects_current +} + +lock_index begin-read +after 1 do_rescan diff --cc git-gui/lib/blame.tcl index 000000000,000000000..6d894e52d new file mode 100644 --- /dev/null +++ b/git-gui/lib/blame.tcl @@@ -1,0 -1,0 +1,407 @@@ ++# git-gui blame viewer ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc show_blame {commit path} { ++ global next_browser_id blame_status blame_data ++ ++ if {[winfo ismapped .]} { ++ set w .browser[incr next_browser_id] ++ set tl $w ++ toplevel $w ++ } else { ++ set w {} ++ set tl . ++ } ++ set blame_status($w) {Loading current file content...} ++ ++ label $w.path -text "$commit:$path" \ ++ -anchor w \ ++ -justify left \ ++ -borderwidth 1 \ ++ -relief sunken \ ++ -font font_uibold ++ pack $w.path -side top -fill x ++ ++ frame $w.out ++ text $w.out.loaded_t \ ++ -background white -borderwidth 0 \ ++ -state disabled \ ++ -wrap none \ ++ -height 40 \ ++ -width 1 \ ++ -font font_diff ++ $w.out.loaded_t tag conf annotated -background grey ++ ++ text $w.out.linenumber_t \ ++ -background white -borderwidth 0 \ ++ -state disabled \ ++ -wrap none \ ++ -height 40 \ ++ -width 5 \ ++ -font font_diff ++ $w.out.linenumber_t tag conf linenumber -justify right ++ ++ text $w.out.file_t \ ++ -background white -borderwidth 0 \ ++ -state disabled \ ++ -wrap none \ ++ -height 40 \ ++ -width 80 \ ++ -xscrollcommand [list $w.out.sbx set] \ ++ -font font_diff ++ ++ scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview] ++ scrollbar $w.out.sby -orient v \ ++ -command [list scrollbar2many [list \ ++ $w.out.loaded_t \ ++ $w.out.linenumber_t \ ++ $w.out.file_t \ ++ ] yview] ++ grid \ ++ $w.out.linenumber_t \ ++ $w.out.loaded_t \ ++ $w.out.file_t \ ++ $w.out.sby \ ++ -sticky nsew ++ grid conf $w.out.sbx -column 2 -sticky we ++ grid columnconfigure $w.out 2 -weight 1 ++ grid rowconfigure $w.out 0 -weight 1 ++ pack $w.out -fill both -expand 1 ++ ++ label $w.status -textvariable blame_status($w) \ ++ -anchor w \ ++ -justify left \ ++ -borderwidth 1 \ ++ -relief sunken ++ pack $w.status -side bottom -fill x ++ ++ frame $w.cm ++ text $w.cm.t \ ++ -background white -borderwidth 0 \ ++ -state disabled \ ++ -wrap none \ ++ -height 10 \ ++ -width 80 \ ++ -xscrollcommand [list $w.cm.sbx set] \ ++ -yscrollcommand [list $w.cm.sby set] \ ++ -font font_diff ++ scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview] ++ scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview] ++ pack $w.cm.sby -side right -fill y ++ pack $w.cm.sbx -side bottom -fill x ++ pack $w.cm.t -expand 1 -fill both ++ pack $w.cm -side bottom -fill x ++ ++ menu $w.ctxm -tearoff 0 ++ $w.ctxm add command -label "Copy Commit" \ ++ -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" ++ ++ foreach i [list \ ++ $w.out.loaded_t \ ++ $w.out.linenumber_t \ ++ $w.out.file_t] { ++ $i tag conf in_sel \ ++ -background [$i cget -foreground] \ ++ -foreground [$i cget -background] ++ $i conf -yscrollcommand \ ++ [list many2scrollbar [list \ ++ $w.out.loaded_t \ ++ $w.out.linenumber_t \ ++ $w.out.file_t \ ++ ] yview $w.out.sby] ++ bind $i " ++ blame_click {$w} \\ ++ $w.cm.t \\ ++ $w.out.linenumber_t \\ ++ $w.out.file_t \\ ++ $i @%x,%y ++ focus $i ++ " ++ bind_button3 $i " ++ set cursorX %x ++ set cursorY %y ++ set cursorW %W ++ tk_popup $w.ctxm %X %Y ++ " ++ } ++ ++ foreach i [list \ ++ $w.out.loaded_t \ ++ $w.out.linenumber_t \ ++ $w.out.file_t \ ++ $w.cm.t] { ++ bind $i {catch {%W yview scroll -1 units};break} ++ bind $i {catch {%W yview scroll 1 units};break} ++ bind $i {catch {%W xview scroll -1 units};break} ++ bind $i {catch {%W xview scroll 1 units};break} ++ bind $i {catch {%W yview scroll -1 units};break} ++ bind $i {catch {%W yview scroll 1 units};break} ++ bind $i {catch {%W xview scroll -1 units};break} ++ bind $i {catch {%W xview scroll 1 units};break} ++ bind $i {catch {%W yview scroll -1 pages};break} ++ bind $i {catch {%W yview scroll 1 pages};break} ++ } ++ ++ bind $w.cm.t "focus $w.cm.t" ++ bind $tl "focus $tl" ++ bind $tl " ++ array unset blame_status {$w} ++ array unset blame_data $w,* ++ " ++ wm title $tl "[appname] ([reponame]): File Viewer" ++ ++ set blame_data($w,commit_count) 0 ++ set blame_data($w,commit_list) {} ++ set blame_data($w,total_lines) 0 ++ set blame_data($w,blame_lines) 0 ++ set blame_data($w,highlight_commit) {} ++ set blame_data($w,highlight_line) -1 ++ ++ set cmd [list git cat-file blob "$commit:$path"] ++ set fd [open "| $cmd" r] ++ fconfigure $fd -blocking 0 -translation lf -encoding binary ++ fileevent $fd readable [list read_blame_catfile \ ++ $fd $w $commit $path \ ++ $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t] ++} ++ ++proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} { ++ global blame_status blame_data ++ ++ if {![winfo exists $w_file]} { ++ catch {close $fd} ++ return ++ } ++ ++ set n $blame_data($w,total_lines) ++ $w_load conf -state normal ++ $w_line conf -state normal ++ $w_file conf -state normal ++ while {[gets $fd line] >= 0} { ++ regsub "\r\$" $line {} line ++ incr n ++ $w_load insert end "\n" ++ $w_line insert end "$n\n" linenumber ++ $w_file insert end "$line\n" ++ } ++ $w_load conf -state disabled ++ $w_line conf -state disabled ++ $w_file conf -state disabled ++ set blame_data($w,total_lines) $n ++ ++ if {[eof $fd]} { ++ close $fd ++ blame_incremental_status $w ++ set cmd [list git blame -M -C --incremental] ++ lappend cmd $commit -- $path ++ set fd [open "| $cmd" r] ++ fconfigure $fd -blocking 0 -translation lf -encoding binary ++ fileevent $fd readable [list read_blame_incremental $fd $w \ ++ $w_load $w_cmit $w_line $w_file] ++ } ++} ++ ++proc read_blame_incremental {fd w w_load w_cmit w_line w_file} { ++ global blame_status blame_data ++ ++ if {![winfo exists $w_file]} { ++ catch {close $fd} ++ return ++ } ++ ++ while {[gets $fd line] >= 0} { ++ if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ ++ cmit original_line final_line line_count]} { ++ set blame_data($w,commit) $cmit ++ set blame_data($w,original_line) $original_line ++ set blame_data($w,final_line) $final_line ++ set blame_data($w,line_count) $line_count ++ ++ if {[catch {set g $blame_data($w,$cmit,order)}]} { ++ $w_line tag conf g$cmit ++ $w_file tag conf g$cmit ++ $w_line tag raise in_sel ++ $w_file tag raise in_sel ++ $w_file tag raise sel ++ set blame_data($w,$cmit,order) $blame_data($w,commit_count) ++ incr blame_data($w,commit_count) ++ lappend blame_data($w,commit_list) $cmit ++ } ++ } elseif {[string match {filename *} $line]} { ++ set file [string range $line 9 end] ++ set n $blame_data($w,line_count) ++ set lno $blame_data($w,final_line) ++ set cmit $blame_data($w,commit) ++ ++ while {$n > 0} { ++ if {[catch {set g g$blame_data($w,line$lno,commit)}]} { ++ $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" ++ } else { ++ $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" ++ $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" ++ } ++ ++ set blame_data($w,line$lno,commit) $cmit ++ set blame_data($w,line$lno,file) $file ++ $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" ++ $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" ++ ++ if {$blame_data($w,highlight_line) == -1} { ++ if {[lindex [$w_file yview] 0] == 0} { ++ $w_file see $lno.0 ++ blame_showcommit $w $w_cmit $w_line $w_file $lno ++ } ++ } elseif {$blame_data($w,highlight_line) == $lno} { ++ blame_showcommit $w $w_cmit $w_line $w_file $lno ++ } ++ ++ incr n -1 ++ incr lno ++ incr blame_data($w,blame_lines) ++ } ++ ++ set hc $blame_data($w,highlight_commit) ++ if {$hc ne {} ++ && [expr {$blame_data($w,$hc,order) + 1}] ++ == $blame_data($w,$cmit,order)} { ++ blame_showcommit $w $w_cmit $w_line $w_file \ ++ $blame_data($w,highlight_line) ++ } ++ } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { ++ set blame_data($w,$blame_data($w,commit),$header) $data ++ } ++ } ++ ++ if {[eof $fd]} { ++ close $fd ++ set blame_status($w) {Annotation complete.} ++ } else { ++ blame_incremental_status $w ++ } ++} ++ ++proc blame_incremental_status {w} { ++ global blame_status blame_data ++ ++ set have $blame_data($w,blame_lines) ++ set total $blame_data($w,total_lines) ++ set pdone 0 ++ if {$total} {set pdone [expr {100 * $have / $total}]} ++ ++ set blame_status($w) [format \ ++ "Loading annotations... %i of %i lines annotated (%2i%%)" \ ++ $have $total $pdone] ++} ++ ++proc blame_click {w w_cmit w_line w_file cur_w pos} { ++ set lno [lindex [split [$cur_w index $pos] .] 0] ++ if {$lno eq {}} return ++ ++ $w_line tag remove in_sel 0.0 end ++ $w_file tag remove in_sel 0.0 end ++ $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" ++ $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" ++ ++ blame_showcommit $w $w_cmit $w_line $w_file $lno ++} ++ ++set blame_colors { ++ #ff4040 ++ #ff40ff ++ #4040ff ++} ++ ++proc blame_showcommit {w w_cmit w_line w_file lno} { ++ global blame_colors blame_data repo_config ++ ++ set cmit $blame_data($w,highlight_commit) ++ if {$cmit ne {}} { ++ set idx $blame_data($w,$cmit,order) ++ set i 0 ++ foreach c $blame_colors { ++ set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] ++ $w_line tag conf g$h -background white ++ $w_file tag conf g$h -background white ++ incr i ++ } ++ } ++ ++ $w_cmit conf -state normal ++ $w_cmit delete 0.0 end ++ if {[catch {set cmit $blame_data($w,line$lno,commit)}]} { ++ set cmit {} ++ $w_cmit insert end "Loading annotation..." ++ } else { ++ set idx $blame_data($w,$cmit,order) ++ set i 0 ++ foreach c $blame_colors { ++ set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] ++ $w_line tag conf g$h -background $c ++ $w_file tag conf g$h -background $c ++ incr i ++ } ++ ++ set author_name {} ++ set author_email {} ++ set author_time {} ++ catch {set author_name $blame_data($w,$cmit,author)} ++ catch {set author_email $blame_data($w,$cmit,author-mail)} ++ catch {set author_time [clock format $blame_data($w,$cmit,author-time)]} ++ ++ set committer_name {} ++ set committer_email {} ++ set committer_time {} ++ catch {set committer_name $blame_data($w,$cmit,committer)} ++ catch {set committer_email $blame_data($w,$cmit,committer-mail)} ++ catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} ++ ++ if {[catch {set msg $blame_data($w,$cmit,message)}]} { ++ set msg {} ++ catch { ++ set fd [open "| git cat-file commit $cmit" r] ++ fconfigure $fd -encoding binary -translation lf ++ if {[catch {set enc $repo_config(i18n.commitencoding)}]} { ++ set enc utf-8 ++ } ++ while {[gets $fd line] > 0} { ++ if {[string match {encoding *} $line]} { ++ set enc [string tolower [string range $line 9 end]] ++ } ++ } ++ set msg [encoding convertfrom $enc [read $fd]] ++ set msg [string trim $msg] ++ close $fd ++ ++ set author_name [encoding convertfrom $enc $author_name] ++ set committer_name [encoding convertfrom $enc $committer_name] ++ ++ set blame_data($w,$cmit,author) $author_name ++ set blame_data($w,$cmit,committer) $committer_name ++ } ++ set blame_data($w,$cmit,message) $msg ++ } ++ ++ $w_cmit insert end "commit $cmit\n" ++ $w_cmit insert end "Author: $author_name $author_email $author_time\n" ++ $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" ++ $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n" ++ $w_cmit insert end "\n" ++ $w_cmit insert end $msg ++ } ++ $w_cmit conf -state disabled ++ ++ set blame_data($w,highlight_line) $lno ++ set blame_data($w,highlight_commit) $cmit ++} ++ ++proc blame_copycommit {w i pos} { ++ global blame_data ++ set lno [lindex [split [$i index $pos] .] 0] ++ if {![catch {set commit $blame_data($w,line$lno,commit)}]} { ++ clipboard clear ++ clipboard append \ ++ -format STRING \ ++ -type STRING \ ++ -- $commit ++ } ++} diff --cc git-gui/lib/branch.tcl index 000000000,000000000..caaee5cf1 new file mode 100644 --- /dev/null +++ b/git-gui/lib/branch.tcl @@@ -1,0 -1,0 +1,572 @@@ ++# git-gui branch (create/delete) support ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc load_all_heads {} { ++ global all_heads ++ ++ set all_heads [list] ++ set fd [open "| git for-each-ref --format=%(refname) refs/heads" r] ++ while {[gets $fd line] > 0} { ++ if {[is_tracking_branch $line]} continue ++ if {![regsub ^refs/heads/ $line {} name]} continue ++ lappend all_heads $name ++ } ++ close $fd ++ ++ set all_heads [lsort $all_heads] ++} ++ ++proc load_all_tags {} { ++ set all_tags [list] ++ set fd [open "| git for-each-ref --format=%(refname) refs/tags" r] ++ while {[gets $fd line] > 0} { ++ if {![regsub ^refs/tags/ $line {} name]} continue ++ lappend all_tags $name ++ } ++ close $fd ++ ++ return [lsort $all_tags] ++} ++ ++proc populate_branch_menu {} { ++ global all_heads disable_on_lock ++ ++ set m .mbar.branch ++ set last [$m index last] ++ for {set i 0} {$i <= $last} {incr i} { ++ if {[$m type $i] eq {separator}} { ++ $m delete $i last ++ set new_dol [list] ++ foreach a $disable_on_lock { ++ if {[lindex $a 0] ne $m || [lindex $a 2] < $i} { ++ lappend new_dol $a ++ } ++ } ++ set disable_on_lock $new_dol ++ break ++ } ++ } ++ ++ if {$all_heads ne {}} { ++ $m add separator ++ } ++ foreach b $all_heads { ++ $m add radiobutton \ ++ -label $b \ ++ -command [list switch_branch $b] \ ++ -variable current_branch \ ++ -value $b ++ lappend disable_on_lock \ ++ [list $m entryconf [$m index last] -state] ++ } ++} ++ ++proc do_create_branch_action {w} { ++ global all_heads null_sha1 repo_config ++ global create_branch_checkout create_branch_revtype ++ global create_branch_head create_branch_trackinghead ++ global create_branch_name create_branch_revexp ++ global create_branch_tag ++ ++ set newbranch $create_branch_name ++ if {$newbranch eq {} ++ || $newbranch eq $repo_config(gui.newbranchtemplate)} { ++ tk_messageBox \ ++ -icon error \ ++ -type ok \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message "Please supply a branch name." ++ focus $w.desc.name_t ++ return ++ } ++ if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} { ++ tk_messageBox \ ++ -icon error \ ++ -type ok \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message "Branch '$newbranch' already exists." ++ focus $w.desc.name_t ++ return ++ } ++ if {[catch {git check-ref-format "heads/$newbranch"}]} { ++ tk_messageBox \ ++ -icon error \ ++ -type ok \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message "We do not like '$newbranch' as a branch name." ++ focus $w.desc.name_t ++ return ++ } ++ ++ set rev {} ++ switch -- $create_branch_revtype { ++ head {set rev $create_branch_head} ++ tracking {set rev $create_branch_trackinghead} ++ tag {set rev $create_branch_tag} ++ expression {set rev $create_branch_revexp} ++ } ++ if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} { ++ tk_messageBox \ ++ -icon error \ ++ -type ok \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message "Invalid starting revision: $rev" ++ return ++ } ++ if {[catch { ++ git update-ref \ ++ -m "branch: Created from $rev" \ ++ "refs/heads/$newbranch" \ ++ $cmt \ ++ $null_sha1 ++ } err]} { ++ tk_messageBox \ ++ -icon error \ ++ -type ok \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message "Failed to create '$newbranch'.\n\n$err" ++ return ++ } ++ ++ lappend all_heads $newbranch ++ set all_heads [lsort $all_heads] ++ populate_branch_menu ++ destroy $w ++ if {$create_branch_checkout} { ++ switch_branch $newbranch ++ } ++} ++ ++proc radio_selector {varname value args} { ++ upvar #0 $varname var ++ set var $value ++} ++ ++trace add variable create_branch_head write \ ++ [list radio_selector create_branch_revtype head] ++trace add variable create_branch_trackinghead write \ ++ [list radio_selector create_branch_revtype tracking] ++trace add variable create_branch_tag write \ ++ [list radio_selector create_branch_revtype tag] ++ ++trace add variable delete_branch_head write \ ++ [list radio_selector delete_branch_checktype head] ++trace add variable delete_branch_trackinghead write \ ++ [list radio_selector delete_branch_checktype tracking] ++ ++proc do_create_branch {} { ++ global all_heads current_branch repo_config ++ global create_branch_checkout create_branch_revtype ++ global create_branch_head create_branch_trackinghead ++ global create_branch_name create_branch_revexp ++ global create_branch_tag ++ ++ set w .branch_editor ++ toplevel $w ++ wm geometry $w "+[winfo rootx .]+[winfo rooty .]" ++ ++ label $w.header -text {Create New Branch} \ ++ -font font_uibold ++ pack $w.header -side top -fill x ++ ++ frame $w.buttons ++ button $w.buttons.create -text Create \ ++ -default active \ ++ -command [list do_create_branch_action $w] ++ pack $w.buttons.create -side right ++ button $w.buttons.cancel -text {Cancel} \ ++ -command [list destroy $w] ++ pack $w.buttons.cancel -side right -padx 5 ++ pack $w.buttons -side bottom -fill x -pady 10 -padx 10 ++ ++ labelframe $w.desc -text {Branch Description} ++ label $w.desc.name_l -text {Name:} ++ entry $w.desc.name_t \ ++ -borderwidth 1 \ ++ -relief sunken \ ++ -width 40 \ ++ -textvariable create_branch_name \ ++ -validate key \ ++ -validatecommand { ++ if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0} ++ return 1 ++ } ++ grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5} ++ grid columnconfigure $w.desc 1 -weight 1 ++ pack $w.desc -anchor nw -fill x -pady 5 -padx 5 ++ ++ labelframe $w.from -text {Starting Revision} ++ radiobutton $w.from.head_r \ ++ -text {Local Branch:} \ ++ -value head \ ++ -variable create_branch_revtype ++ eval tk_optionMenu $w.from.head_m create_branch_head $all_heads ++ grid $w.from.head_r $w.from.head_m -sticky w ++ set all_trackings [all_tracking_branches] ++ if {$all_trackings ne {}} { ++ set create_branch_trackinghead [lindex $all_trackings 0] ++ radiobutton $w.from.tracking_r \ ++ -text {Tracking Branch:} \ ++ -value tracking \ ++ -variable create_branch_revtype ++ eval tk_optionMenu $w.from.tracking_m \ ++ create_branch_trackinghead \ ++ $all_trackings ++ grid $w.from.tracking_r $w.from.tracking_m -sticky w ++ } ++ set all_tags [load_all_tags] ++ if {$all_tags ne {}} { ++ set create_branch_tag [lindex $all_tags 0] ++ radiobutton $w.from.tag_r \ ++ -text {Tag:} \ ++ -value tag \ ++ -variable create_branch_revtype ++ eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags ++ grid $w.from.tag_r $w.from.tag_m -sticky w ++ } ++ radiobutton $w.from.exp_r \ ++ -text {Revision Expression:} \ ++ -value expression \ ++ -variable create_branch_revtype ++ entry $w.from.exp_t \ ++ -borderwidth 1 \ ++ -relief sunken \ ++ -width 50 \ ++ -textvariable create_branch_revexp \ ++ -validate key \ ++ -validatecommand { ++ if {%d == 1 && [regexp {\s} %S]} {return 0} ++ if {%d == 1 && [string length %S] > 0} { ++ set create_branch_revtype expression ++ } ++ return 1 ++ } ++ grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5} ++ grid columnconfigure $w.from 1 -weight 1 ++ pack $w.from -anchor nw -fill x -pady 5 -padx 5 ++ ++ labelframe $w.postActions -text {Post Creation Actions} ++ checkbutton $w.postActions.checkout \ ++ -text {Checkout after creation} \ ++ -variable create_branch_checkout ++ pack $w.postActions.checkout -anchor nw ++ pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 ++ ++ set create_branch_checkout 1 ++ set create_branch_head $current_branch ++ set create_branch_revtype head ++ set create_branch_name $repo_config(gui.newbranchtemplate) ++ set create_branch_revexp {} ++ ++ bind $w " ++ grab $w ++ $w.desc.name_t icursor end ++ focus $w.desc.name_t ++ " ++ bind $w "destroy $w" ++ bind $w "do_create_branch_action $w;break" ++ wm title $w "[appname] ([reponame]): Create Branch" ++ tkwait window $w ++} ++ ++proc do_delete_branch_action {w} { ++ global all_heads ++ global delete_branch_checktype delete_branch_head delete_branch_trackinghead ++ ++ set check_rev {} ++ switch -- $delete_branch_checktype { ++ head {set check_rev $delete_branch_head} ++ tracking {set check_rev $delete_branch_trackinghead} ++ always {set check_rev {:none}} ++ } ++ if {$check_rev eq {:none}} { ++ set check_cmt {} ++ } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} { ++ tk_messageBox \ ++ -icon error \ ++ -type ok \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message "Invalid check revision: $check_rev" ++ return ++ } ++ ++ set to_delete [list] ++ set not_merged [list] ++ foreach i [$w.list.l curselection] { ++ set b [$w.list.l get $i] ++ if {[catch {set o [git rev-parse --verify $b]}]} continue ++ if {$check_cmt ne {}} { ++ if {$b eq $check_rev} continue ++ if {[catch {set m [git merge-base $o $check_cmt]}]} continue ++ if {$o ne $m} { ++ lappend not_merged $b ++ continue ++ } ++ } ++ lappend to_delete [list $b $o] ++ } ++ if {$not_merged ne {}} { ++ set msg "The following branches are not completely merged into $check_rev: ++ ++ - [join $not_merged "\n - "]" ++ tk_messageBox \ ++ -icon info \ ++ -type ok \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message $msg ++ } ++ if {$to_delete eq {}} return ++ if {$delete_branch_checktype eq {always}} { ++ set msg {Recovering deleted branches is difficult. ++ ++Delete the selected branches?} ++ if {[tk_messageBox \ ++ -icon warning \ ++ -type yesno \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message $msg] ne yes} { ++ return ++ } ++ } ++ ++ set failed {} ++ foreach i $to_delete { ++ set b [lindex $i 0] ++ set o [lindex $i 1] ++ if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { ++ append failed " - $b: $err\n" ++ } else { ++ set x [lsearch -sorted -exact $all_heads $b] ++ if {$x >= 0} { ++ set all_heads [lreplace $all_heads $x $x] ++ } ++ } ++ } ++ ++ if {$failed ne {}} { ++ tk_messageBox \ ++ -icon error \ ++ -type ok \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message "Failed to delete branches:\n$failed" ++ } ++ ++ set all_heads [lsort $all_heads] ++ populate_branch_menu ++ destroy $w ++} ++ ++proc do_delete_branch {} { ++ global all_heads tracking_branches current_branch ++ global delete_branch_checktype delete_branch_head delete_branch_trackinghead ++ ++ set w .branch_editor ++ toplevel $w ++ wm geometry $w "+[winfo rootx .]+[winfo rooty .]" ++ ++ label $w.header -text {Delete Local Branch} \ ++ -font font_uibold ++ pack $w.header -side top -fill x ++ ++ frame $w.buttons ++ button $w.buttons.create -text Delete \ ++ -command [list do_delete_branch_action $w] ++ pack $w.buttons.create -side right ++ button $w.buttons.cancel -text {Cancel} \ ++ -command [list destroy $w] ++ pack $w.buttons.cancel -side right -padx 5 ++ pack $w.buttons -side bottom -fill x -pady 10 -padx 10 ++ ++ labelframe $w.list -text {Local Branches} ++ listbox $w.list.l \ ++ -height 10 \ ++ -width 70 \ ++ -selectmode extended \ ++ -yscrollcommand [list $w.list.sby set] ++ foreach h $all_heads { ++ if {$h ne $current_branch} { ++ $w.list.l insert end $h ++ } ++ } ++ scrollbar $w.list.sby -command [list $w.list.l yview] ++ pack $w.list.sby -side right -fill y ++ pack $w.list.l -side left -fill both -expand 1 ++ pack $w.list -fill both -expand 1 -pady 5 -padx 5 ++ ++ labelframe $w.validate -text {Delete Only If} ++ radiobutton $w.validate.head_r \ ++ -text {Merged Into Local Branch:} \ ++ -value head \ ++ -variable delete_branch_checktype ++ eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads ++ grid $w.validate.head_r $w.validate.head_m -sticky w ++ set all_trackings [all_tracking_branches] ++ if {$all_trackings ne {}} { ++ set delete_branch_trackinghead [lindex $all_trackings 0] ++ radiobutton $w.validate.tracking_r \ ++ -text {Merged Into Tracking Branch:} \ ++ -value tracking \ ++ -variable delete_branch_checktype ++ eval tk_optionMenu $w.validate.tracking_m \ ++ delete_branch_trackinghead \ ++ $all_trackings ++ grid $w.validate.tracking_r $w.validate.tracking_m -sticky w ++ } ++ radiobutton $w.validate.always_r \ ++ -text {Always (Do not perform merge checks)} \ ++ -value always \ ++ -variable delete_branch_checktype ++ grid $w.validate.always_r -columnspan 2 -sticky w ++ grid columnconfigure $w.validate 1 -weight 1 ++ pack $w.validate -anchor nw -fill x -pady 5 -padx 5 ++ ++ set delete_branch_head $current_branch ++ set delete_branch_checktype head ++ ++ bind $w "grab $w; focus $w" ++ bind $w "destroy $w" ++ wm title $w "[appname] ([reponame]): Delete Branch" ++ tkwait window $w ++} ++ ++proc switch_branch {new_branch} { ++ global HEAD commit_type current_branch repo_config ++ ++ if {![lock_index switch]} return ++ ++ # -- Our in memory state should match the repository. ++ # ++ repository_state curType curHEAD curMERGE_HEAD ++ if {[string match amend* $commit_type] ++ && $curType eq {normal} ++ && $curHEAD eq $HEAD} { ++ } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { ++ info_popup {Last scanned state does not match repository state. ++ ++Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. ++ ++The rescan will be automatically started now. ++} ++ unlock_index ++ rescan {set ui_status_value {Ready.}} ++ return ++ } ++ ++ # -- Don't do a pointless switch. ++ # ++ if {$current_branch eq $new_branch} { ++ unlock_index ++ return ++ } ++ ++ if {$repo_config(gui.trustmtime) eq {true}} { ++ switch_branch_stage2 {} $new_branch ++ } else { ++ set ui_status_value {Refreshing file status...} ++ set cmd [list git update-index] ++ lappend cmd -q ++ lappend cmd --unmerged ++ lappend cmd --ignore-missing ++ lappend cmd --refresh ++ set fd_rf [open "| $cmd" r] ++ fconfigure $fd_rf -blocking 0 -translation binary ++ fileevent $fd_rf readable \ ++ [list switch_branch_stage2 $fd_rf $new_branch] ++ } ++} ++ ++proc switch_branch_stage2 {fd_rf new_branch} { ++ global ui_status_value HEAD ++ ++ if {$fd_rf ne {}} { ++ read $fd_rf ++ if {![eof $fd_rf]} return ++ close $fd_rf ++ } ++ ++ set ui_status_value "Updating working directory to '$new_branch'..." ++ set cmd [list git read-tree] ++ lappend cmd -m ++ lappend cmd -u ++ lappend cmd --exclude-per-directory=.gitignore ++ lappend cmd $HEAD ++ lappend cmd $new_branch ++ set fd_rt [open "| $cmd" r] ++ fconfigure $fd_rt -blocking 0 -translation binary ++ fileevent $fd_rt readable \ ++ [list switch_branch_readtree_wait $fd_rt $new_branch] ++} ++ ++proc switch_branch_readtree_wait {fd_rt new_branch} { ++ global selected_commit_type commit_type HEAD MERGE_HEAD PARENT ++ global current_branch ++ global ui_comm ui_status_value ++ ++ # -- We never get interesting output on stdout; only stderr. ++ # ++ read $fd_rt ++ fconfigure $fd_rt -blocking 1 ++ if {![eof $fd_rt]} { ++ fconfigure $fd_rt -blocking 0 ++ return ++ } ++ ++ # -- The working directory wasn't in sync with the index and ++ # we'd have to overwrite something to make the switch. A ++ # merge is required. ++ # ++ if {[catch {close $fd_rt} err]} { ++ regsub {^fatal: } $err {} err ++ warn_popup "File level merge required. ++ ++$err ++ ++Staying on branch '$current_branch'." ++ set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)." ++ unlock_index ++ return ++ } ++ ++ # -- Update the symbolic ref. Core git doesn't even check for failure ++ # here, it Just Works(tm). If it doesn't we are in some really ugly ++ # state that is difficult to recover from within git-gui. ++ # ++ if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { ++ error_popup "Failed to set current branch. ++ ++This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. ++ ++This should not have occurred. [appname] will now close and give up. ++ ++$err" ++ do_quit ++ return ++ } ++ ++ # -- Update our repository state. If we were previously in amend mode ++ # we need to toss the current buffer and do a full rescan to update ++ # our file lists. If we weren't in amend mode our file lists are ++ # accurate and we can avoid the rescan. ++ # ++ unlock_index ++ set selected_commit_type new ++ if {[string match amend* $commit_type]} { ++ $ui_comm delete 0.0 end ++ $ui_comm edit reset ++ $ui_comm edit modified false ++ rescan {set ui_status_value "Checked out branch '$current_branch'."} ++ } else { ++ repository_state commit_type HEAD MERGE_HEAD ++ set PARENT $HEAD ++ set ui_status_value "Checked out branch '$current_branch'." ++ } ++} diff --cc git-gui/lib/browser.tcl index 000000000,000000000..631859ae7 new file mode 100644 --- /dev/null +++ b/git-gui/lib/browser.tcl @@@ -1,0 -1,0 +1,263 @@@ ++# git-gui tree browser ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++set next_browser_id 0 ++ ++proc new_browser {commit} { ++ global next_browser_id cursor_ptr M1B ++ global browser_commit browser_status browser_stack browser_path browser_busy ++ ++ if {[winfo ismapped .]} { ++ set w .browser[incr next_browser_id] ++ set tl $w ++ toplevel $w ++ } else { ++ set w {} ++ set tl . ++ } ++ set w_list $w.list.l ++ set browser_commit($w_list) $commit ++ set browser_status($w_list) {Starting...} ++ set browser_stack($w_list) {} ++ set browser_path($w_list) $browser_commit($w_list): ++ set browser_busy($w_list) 1 ++ ++ label $w.path -textvariable browser_path($w_list) \ ++ -anchor w \ ++ -justify left \ ++ -borderwidth 1 \ ++ -relief sunken \ ++ -font font_uibold ++ pack $w.path -anchor w -side top -fill x ++ ++ frame $w.list ++ text $w_list -background white -borderwidth 0 \ ++ -cursor $cursor_ptr \ ++ -state disabled \ ++ -wrap none \ ++ -height 20 \ ++ -width 70 \ ++ -xscrollcommand [list $w.list.sbx set] \ ++ -yscrollcommand [list $w.list.sby set] ++ $w_list tag conf in_sel \ ++ -background [$w_list cget -foreground] \ ++ -foreground [$w_list cget -background] ++ scrollbar $w.list.sbx -orient h -command [list $w_list xview] ++ scrollbar $w.list.sby -orient v -command [list $w_list yview] ++ pack $w.list.sbx -side bottom -fill x ++ pack $w.list.sby -side right -fill y ++ pack $w_list -side left -fill both -expand 1 ++ pack $w.list -side top -fill both -expand 1 ++ ++ label $w.status -textvariable browser_status($w_list) \ ++ -anchor w \ ++ -justify left \ ++ -borderwidth 1 \ ++ -relief sunken ++ pack $w.status -anchor w -side bottom -fill x ++ ++ bind $w_list "browser_click 0 $w_list @%x,%y;break" ++ bind $w_list "browser_click 1 $w_list @%x,%y;break" ++ bind $w_list <$M1B-Up> "browser_parent $w_list;break" ++ bind $w_list <$M1B-Left> "browser_parent $w_list;break" ++ bind $w_list "browser_move -1 $w_list;break" ++ bind $w_list "browser_move 1 $w_list;break" ++ bind $w_list <$M1B-Right> "browser_enter $w_list;break" ++ bind $w_list "browser_enter $w_list;break" ++ bind $w_list "browser_page -1 $w_list;break" ++ bind $w_list "browser_page 1 $w_list;break" ++ bind $w_list break ++ bind $w_list break ++ ++ bind $tl "focus $w" ++ bind $tl " ++ array unset browser_buffer $w_list ++ array unset browser_files $w_list ++ array unset browser_status $w_list ++ array unset browser_stack $w_list ++ array unset browser_path $w_list ++ array unset browser_commit $w_list ++ array unset browser_busy $w_list ++ " ++ wm title $tl "[appname] ([reponame]): File Browser" ++ ls_tree $w_list $browser_commit($w_list) {} ++} ++ ++proc browser_move {dir w} { ++ global browser_files browser_busy ++ ++ if {$browser_busy($w)} return ++ set lno [lindex [split [$w index in_sel.first] .] 0] ++ incr lno $dir ++ if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { ++ $w tag remove in_sel 0.0 end ++ $w tag add in_sel $lno.0 [expr {$lno + 1}].0 ++ $w see $lno.0 ++ } ++} ++ ++proc browser_page {dir w} { ++ global browser_files browser_busy ++ ++ if {$browser_busy($w)} return ++ $w yview scroll $dir pages ++ set lno [expr {int( ++ [lindex [$w yview] 0] ++ * [llength $browser_files($w)] ++ + 1)}] ++ if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { ++ $w tag remove in_sel 0.0 end ++ $w tag add in_sel $lno.0 [expr {$lno + 1}].0 ++ $w see $lno.0 ++ } ++} ++ ++proc browser_parent {w} { ++ global browser_files browser_status browser_path ++ global browser_stack browser_busy ++ ++ if {$browser_busy($w)} return ++ set info [lindex $browser_files($w) 0] ++ if {[lindex $info 0] eq {parent}} { ++ set parent [lindex $browser_stack($w) end-1] ++ set browser_stack($w) [lrange $browser_stack($w) 0 end-2] ++ if {$browser_stack($w) eq {}} { ++ regsub {:.*$} $browser_path($w) {:} browser_path($w) ++ } else { ++ regsub {/[^/]+$} $browser_path($w) {} browser_path($w) ++ } ++ set browser_status($w) "Loading $browser_path($w)..." ++ ls_tree $w [lindex $parent 0] [lindex $parent 1] ++ } ++} ++ ++proc browser_enter {w} { ++ global browser_files browser_status browser_path ++ global browser_commit browser_stack browser_busy ++ ++ if {$browser_busy($w)} return ++ set lno [lindex [split [$w index in_sel.first] .] 0] ++ set info [lindex $browser_files($w) [expr {$lno - 1}]] ++ if {$info ne {}} { ++ switch -- [lindex $info 0] { ++ parent { ++ browser_parent $w ++ } ++ tree { ++ set name [lindex $info 2] ++ set escn [escape_path $name] ++ set browser_status($w) "Loading $escn..." ++ append browser_path($w) $escn ++ ls_tree $w [lindex $info 1] $name ++ } ++ blob { ++ set name [lindex $info 2] ++ set p {} ++ foreach n $browser_stack($w) { ++ append p [lindex $n 1] ++ } ++ append p $name ++ show_blame $browser_commit($w) $p ++ } ++ } ++ } ++} ++ ++proc browser_click {was_double_click w pos} { ++ global browser_files browser_busy ++ ++ if {$browser_busy($w)} return ++ set lno [lindex [split [$w index $pos] .] 0] ++ focus $w ++ ++ if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { ++ $w tag remove in_sel 0.0 end ++ $w tag add in_sel $lno.0 [expr {$lno + 1}].0 ++ if {$was_double_click} { ++ browser_enter $w ++ } ++ } ++} ++ ++proc ls_tree {w tree_id name} { ++ global browser_buffer browser_files browser_stack browser_busy ++ ++ set browser_buffer($w) {} ++ set browser_files($w) {} ++ set browser_busy($w) 1 ++ ++ $w conf -state normal ++ $w tag remove in_sel 0.0 end ++ $w delete 0.0 end ++ if {$browser_stack($w) ne {}} { ++ $w image create end \ ++ -align center -padx 5 -pady 1 \ ++ -name icon0 \ ++ -image file_uplevel ++ $w insert end {[Up To Parent]} ++ lappend browser_files($w) parent ++ } ++ lappend browser_stack($w) [list $tree_id $name] ++ $w conf -state disabled ++ ++ set cmd [list git ls-tree -z $tree_id] ++ set fd [open "| $cmd" r] ++ fconfigure $fd -blocking 0 -translation binary -encoding binary ++ fileevent $fd readable [list read_ls_tree $fd $w] ++} ++ ++proc read_ls_tree {fd w} { ++ global browser_buffer browser_files browser_status browser_busy ++ ++ if {![winfo exists $w]} { ++ catch {close $fd} ++ return ++ } ++ ++ append browser_buffer($w) [read $fd] ++ set pck [split $browser_buffer($w) "\0"] ++ set browser_buffer($w) [lindex $pck end] ++ ++ set n [llength $browser_files($w)] ++ $w conf -state normal ++ foreach p [lrange $pck 0 end-1] { ++ set info [split $p "\t"] ++ set path [lindex $info 1] ++ set info [split [lindex $info 0] { }] ++ set type [lindex $info 1] ++ set object [lindex $info 2] ++ ++ switch -- $type { ++ blob { ++ set image file_mod ++ } ++ tree { ++ set image file_dir ++ append path / ++ } ++ default { ++ set image file_question ++ } ++ } ++ ++ if {$n > 0} {$w insert end "\n"} ++ $w image create end \ ++ -align center -padx 5 -pady 1 \ ++ -name icon[incr n] \ ++ -image $image ++ $w insert end [escape_path $path] ++ lappend browser_files($w) [list $type $object $path] ++ } ++ $w conf -state disabled ++ ++ if {[eof $fd]} { ++ close $fd ++ set browser_status($w) Ready. ++ set browser_busy($w) 0 ++ array unset browser_buffer $w ++ if {$n > 0} { ++ $w tag add in_sel 1.0 2.0 ++ focus -force $w ++ } ++ } ++} diff --cc git-gui/lib/commit.tcl index 000000000,000000000..f9791f64d new file mode 100644 --- /dev/null +++ b/git-gui/lib/commit.tcl @@@ -1,0 -1,0 +1,410 @@@ ++# git-gui misc. commit reading/writing support ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc load_last_commit {} { ++ global HEAD PARENT MERGE_HEAD commit_type ui_comm ++ global repo_config ++ ++ if {[llength $PARENT] == 0} { ++ error_popup {There is nothing to amend. ++ ++You are about to create the initial commit. There is no commit before this to amend. ++} ++ return ++ } ++ ++ repository_state curType curHEAD curMERGE_HEAD ++ if {$curType eq {merge}} { ++ error_popup {Cannot amend while merging. ++ ++You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity. ++} ++ return ++ } ++ ++ set msg {} ++ set parents [list] ++ if {[catch { ++ set fd [open "| git cat-file commit $curHEAD" r] ++ fconfigure $fd -encoding binary -translation lf ++ if {[catch {set enc $repo_config(i18n.commitencoding)}]} { ++ set enc utf-8 ++ } ++ while {[gets $fd line] > 0} { ++ if {[string match {parent *} $line]} { ++ lappend parents [string range $line 7 end] ++ } elseif {[string match {encoding *} $line]} { ++ set enc [string tolower [string range $line 9 end]] ++ } ++ } ++ set msg [encoding convertfrom $enc [read $fd]] ++ set msg [string trim $msg] ++ close $fd ++ } err]} { ++ error_popup "Error loading commit data for amend:\n\n$err" ++ return ++ } ++ ++ set HEAD $curHEAD ++ set PARENT $parents ++ set MERGE_HEAD [list] ++ switch -- [llength $parents] { ++ 0 {set commit_type amend-initial} ++ 1 {set commit_type amend} ++ default {set commit_type amend-merge} ++ } ++ ++ $ui_comm delete 0.0 end ++ $ui_comm insert end $msg ++ $ui_comm edit reset ++ $ui_comm edit modified false ++ rescan {set ui_status_value {Ready.}} ++} ++ ++set GIT_COMMITTER_IDENT {} ++ ++proc committer_ident {} { ++ global GIT_COMMITTER_IDENT ++ ++ if {$GIT_COMMITTER_IDENT eq {}} { ++ if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} { ++ error_popup "Unable to obtain your identity:\n\n$err" ++ return {} ++ } ++ if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \ ++ $me me GIT_COMMITTER_IDENT]} { ++ error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me" ++ return {} ++ } ++ } ++ ++ return $GIT_COMMITTER_IDENT ++} ++ ++proc do_signoff {} { ++ global ui_comm ++ ++ set me [committer_ident] ++ if {$me eq {}} return ++ ++ set sob "Signed-off-by: $me" ++ set last [$ui_comm get {end -1c linestart} {end -1c}] ++ if {$last ne $sob} { ++ $ui_comm edit separator ++ if {$last ne {} ++ && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} { ++ $ui_comm insert end "\n" ++ } ++ $ui_comm insert end "\n$sob" ++ $ui_comm edit separator ++ $ui_comm see end ++ } ++} ++ ++proc create_new_commit {} { ++ global commit_type ui_comm ++ ++ set commit_type normal ++ $ui_comm delete 0.0 end ++ $ui_comm edit reset ++ $ui_comm edit modified false ++ rescan {set ui_status_value {Ready.}} ++} ++ ++proc commit_tree {} { ++ global HEAD commit_type file_states ui_comm repo_config ++ global ui_status_value pch_error ++ ++ if {[committer_ident] eq {}} return ++ if {![lock_index update]} return ++ ++ # -- Our in memory state should match the repository. ++ # ++ repository_state curType curHEAD curMERGE_HEAD ++ if {[string match amend* $commit_type] ++ && $curType eq {normal} ++ && $curHEAD eq $HEAD} { ++ } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { ++ info_popup {Last scanned state does not match repository state. ++ ++Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created. ++ ++The rescan will be automatically started now. ++} ++ unlock_index ++ rescan {set ui_status_value {Ready.}} ++ return ++ } ++ ++ # -- At least one file should differ in the index. ++ # ++ set files_ready 0 ++ foreach path [array names file_states] { ++ switch -glob -- [lindex $file_states($path) 0] { ++ _? {continue} ++ A? - ++ D? - ++ M? {set files_ready 1} ++ U? { ++ error_popup "Unmerged files cannot be committed. ++ ++File [short_path $path] has merge conflicts. You must resolve them and add the file before committing. ++" ++ unlock_index ++ return ++ } ++ default { ++ error_popup "Unknown file state [lindex $s 0] detected. ++ ++File [short_path $path] cannot be committed by this program. ++" ++ } ++ } ++ } ++ if {!$files_ready && ![string match *merge $curType]} { ++ info_popup {No changes to commit. ++ ++You must add at least 1 file before you can commit. ++} ++ unlock_index ++ return ++ } ++ ++ # -- A message is required. ++ # ++ set msg [string trim [$ui_comm get 1.0 end]] ++ regsub -all -line {[ \t\r]+$} $msg {} msg ++ if {$msg eq {}} { ++ error_popup {Please supply a commit message. ++ ++A good commit message has the following format: ++ ++- First line: Describe in one sentance what you did. ++- Second line: Blank ++- Remaining lines: Describe why this change is good. ++} ++ unlock_index ++ return ++ } ++ ++ # -- Run the pre-commit hook. ++ # ++ set pchook [gitdir hooks pre-commit] ++ ++ # On Cygwin [file executable] might lie so we need to ask ++ # the shell if the hook is executable. Yes that's annoying. ++ # ++ if {[is_Cygwin] && [file isfile $pchook]} { ++ set pchook [list sh -c [concat \ ++ "if test -x \"$pchook\";" \ ++ "then exec \"$pchook\" 2>&1;" \ ++ "fi"]] ++ } elseif {[file executable $pchook]} { ++ set pchook [list $pchook |& cat] ++ } else { ++ commit_writetree $curHEAD $msg ++ return ++ } ++ ++ set ui_status_value {Calling pre-commit hook...} ++ set pch_error {} ++ set fd_ph [open "| $pchook" r] ++ fconfigure $fd_ph -blocking 0 -translation binary ++ fileevent $fd_ph readable \ ++ [list commit_prehook_wait $fd_ph $curHEAD $msg] ++} ++ ++proc commit_prehook_wait {fd_ph curHEAD msg} { ++ global pch_error ui_status_value ++ ++ append pch_error [read $fd_ph] ++ fconfigure $fd_ph -blocking 1 ++ if {[eof $fd_ph]} { ++ if {[catch {close $fd_ph}]} { ++ set ui_status_value {Commit declined by pre-commit hook.} ++ hook_failed_popup pre-commit $pch_error ++ unlock_index ++ } else { ++ commit_writetree $curHEAD $msg ++ } ++ set pch_error {} ++ return ++ } ++ fconfigure $fd_ph -blocking 0 ++} ++ ++proc commit_writetree {curHEAD msg} { ++ global ui_status_value ++ ++ set ui_status_value {Committing changes...} ++ set fd_wt [open "| git write-tree" r] ++ fileevent $fd_wt readable \ ++ [list commit_committree $fd_wt $curHEAD $msg] ++} ++ ++proc commit_committree {fd_wt curHEAD msg} { ++ global HEAD PARENT MERGE_HEAD commit_type ++ global all_heads current_branch ++ global ui_status_value ui_comm selected_commit_type ++ global file_states selected_paths rescan_active ++ global repo_config ++ ++ gets $fd_wt tree_id ++ if {$tree_id eq {} || [catch {close $fd_wt} err]} { ++ error_popup "write-tree failed:\n\n$err" ++ set ui_status_value {Commit failed.} ++ unlock_index ++ return ++ } ++ ++ # -- Verify this wasn't an empty change. ++ # ++ if {$commit_type eq {normal}} { ++ set old_tree [git rev-parse "$PARENT^{tree}"] ++ if {$tree_id eq $old_tree} { ++ info_popup {No changes to commit. ++ ++No files were modified by this commit and it was not a merge commit. ++ ++A rescan will be automatically started now. ++} ++ unlock_index ++ rescan {set ui_status_value {No changes to commit.}} ++ return ++ } ++ } ++ ++ # -- Build the message. ++ # ++ set msg_p [gitdir COMMIT_EDITMSG] ++ set msg_wt [open $msg_p w] ++ if {[catch {set enc $repo_config(i18n.commitencoding)}]} { ++ set enc utf-8 ++ } ++ fconfigure $msg_wt -encoding binary -translation binary ++ puts -nonewline $msg_wt [encoding convertto $enc $msg] ++ close $msg_wt ++ ++ # -- Create the commit. ++ # ++ set cmd [list commit-tree $tree_id] ++ foreach p [concat $PARENT $MERGE_HEAD] { ++ lappend cmd -p $p ++ } ++ lappend cmd <$msg_p ++ if {[catch {set cmt_id [eval git $cmd]} err]} { ++ error_popup "commit-tree failed:\n\n$err" ++ set ui_status_value {Commit failed.} ++ unlock_index ++ return ++ } ++ ++ # -- Update the HEAD ref. ++ # ++ set reflogm commit ++ if {$commit_type ne {normal}} { ++ append reflogm " ($commit_type)" ++ } ++ set i [string first "\n" $msg] ++ if {$i >= 0} { ++ set subject [string range $msg 0 [expr {$i - 1}]] ++ } else { ++ set subject $msg ++ } ++ append reflogm {: } $subject ++ if {[catch { ++ git update-ref -m $reflogm HEAD $cmt_id $curHEAD ++ } err]} { ++ error_popup "update-ref failed:\n\n$err" ++ set ui_status_value {Commit failed.} ++ unlock_index ++ return ++ } ++ ++ # -- Cleanup after ourselves. ++ # ++ catch {file delete $msg_p} ++ catch {file delete [gitdir MERGE_HEAD]} ++ catch {file delete [gitdir MERGE_MSG]} ++ catch {file delete [gitdir SQUASH_MSG]} ++ catch {file delete [gitdir GITGUI_MSG]} ++ ++ # -- Let rerere do its thing. ++ # ++ if {[file isdirectory [gitdir rr-cache]]} { ++ catch {git rerere} ++ } ++ ++ # -- Run the post-commit hook. ++ # ++ set pchook [gitdir hooks post-commit] ++ if {[is_Cygwin] && [file isfile $pchook]} { ++ set pchook [list sh -c [concat \ ++ "if test -x \"$pchook\";" \ ++ "then exec \"$pchook\";" \ ++ "fi"]] ++ } elseif {![file executable $pchook]} { ++ set pchook {} ++ } ++ if {$pchook ne {}} { ++ catch {exec $pchook &} ++ } ++ ++ $ui_comm delete 0.0 end ++ $ui_comm edit reset ++ $ui_comm edit modified false ++ ++ if {[is_enabled singlecommit]} do_quit ++ ++ # -- Make sure our current branch exists. ++ # ++ if {$commit_type eq {initial}} { ++ lappend all_heads $current_branch ++ set all_heads [lsort -unique $all_heads] ++ populate_branch_menu ++ } ++ ++ # -- Update in memory status ++ # ++ set selected_commit_type new ++ set commit_type normal ++ set HEAD $cmt_id ++ set PARENT $cmt_id ++ set MERGE_HEAD [list] ++ ++ foreach path [array names file_states] { ++ set s $file_states($path) ++ set m [lindex $s 0] ++ switch -glob -- $m { ++ _O - ++ _M - ++ _D {continue} ++ __ - ++ A_ - ++ M_ - ++ D_ { ++ unset file_states($path) ++ catch {unset selected_paths($path)} ++ } ++ DO { ++ set file_states($path) [list _O [lindex $s 1] {} {}] ++ } ++ AM - ++ AD - ++ MM - ++ MD { ++ set file_states($path) [list \ ++ _[string index $m 1] \ ++ [lindex $s 1] \ ++ [lindex $s 3] \ ++ {}] ++ } ++ } ++ } ++ ++ display_all_files ++ unlock_index ++ reshow_diff ++ set ui_status_value \ ++ "Created commit [string range $cmt_id 0 7]: $subject" ++} diff --cc git-gui/lib/console.tcl index 000000000,000000000..75f3e0463 new file mode 100644 --- /dev/null +++ b/git-gui/lib/console.tcl @@@ -1,0 -1,0 +1,201 @@@ ++# git-gui console support ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++namespace eval console { ++ ++variable next_console_id 0 ++variable console_data ++variable console_cr ++ ++proc new {short_title long_title} { ++ variable next_console_id ++ variable console_data ++ ++ set w .console[incr next_console_id] ++ set console_data($w) [list $short_title $long_title] ++ return [_init $w] ++} ++ ++proc _init {w} { ++ global M1B ++ variable console_cr ++ variable console_data ++ ++ set console_cr($w) 1.0 ++ toplevel $w ++ frame $w.m ++ label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ ++ -anchor w \ ++ -justify left \ ++ -font font_uibold ++ text $w.m.t \ ++ -background white -borderwidth 1 \ ++ -relief sunken \ ++ -width 80 -height 10 \ ++ -font font_diff \ ++ -state disabled \ ++ -yscrollcommand [list $w.m.sby set] ++ label $w.m.s -text {Working... please wait...} \ ++ -anchor w \ ++ -justify left \ ++ -font font_uibold ++ scrollbar $w.m.sby -command [list $w.m.t yview] ++ pack $w.m.l1 -side top -fill x ++ pack $w.m.s -side bottom -fill x ++ pack $w.m.sby -side right -fill y ++ pack $w.m.t -side left -fill both -expand 1 ++ pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 ++ ++ menu $w.ctxm -tearoff 0 ++ $w.ctxm add command -label "Copy" \ ++ -command "tk_textCopy $w.m.t" ++ $w.ctxm add command -label "Select All" \ ++ -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" ++ $w.ctxm add command -label "Copy All" \ ++ -command " ++ $w.m.t tag add sel 0.0 end ++ tk_textCopy $w.m.t ++ $w.m.t tag remove sel 0.0 end ++ " ++ ++ button $w.ok -text {Close} \ ++ -state disabled \ ++ -command "destroy $w" ++ pack $w.ok -side bottom -anchor e -pady 10 -padx 10 ++ ++ bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y" ++ bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" ++ bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" ++ bind $w "focus $w" ++ wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]" ++ return $w ++} ++ ++proc exec {w cmd {after {}}} { ++ # -- Cygwin's Tcl tosses the enviroment when we exec our child. ++ # But most users need that so we have to relogin. :-( ++ # ++ if {[is_Cygwin]} { ++ set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] ++ } ++ ++ # -- Tcl won't let us redirect both stdout and stderr to ++ # the same pipe. So pass it through cat... ++ # ++ set cmd [concat | $cmd |& cat] ++ ++ set fd_f [open $cmd r] ++ fconfigure $fd_f -blocking 0 -translation binary ++ fileevent $fd_f readable \ ++ [namespace code [list _read $w $fd_f $after]] ++} ++ ++proc _read {w fd after} { ++ variable console_cr ++ ++ set buf [read $fd] ++ if {$buf ne {}} { ++ if {![winfo exists $w]} {_init $w} ++ $w.m.t conf -state normal ++ set c 0 ++ set n [string length $buf] ++ while {$c < $n} { ++ set cr [string first "\r" $buf $c] ++ set lf [string first "\n" $buf $c] ++ if {$cr < 0} {set cr [expr {$n + 1}]} ++ if {$lf < 0} {set lf [expr {$n + 1}]} ++ ++ if {$lf < $cr} { ++ $w.m.t insert end [string range $buf $c $lf] ++ set console_cr($w) [$w.m.t index {end -1c}] ++ set c $lf ++ incr c ++ } else { ++ $w.m.t delete $console_cr($w) end ++ $w.m.t insert end "\n" ++ $w.m.t insert end [string range $buf $c $cr] ++ set c $cr ++ incr c ++ } ++ } ++ $w.m.t conf -state disabled ++ $w.m.t see end ++ } ++ ++ fconfigure $fd -blocking 1 ++ if {[eof $fd]} { ++ if {[catch {close $fd}]} { ++ set ok 0 ++ } else { ++ set ok 1 ++ } ++ if {$after ne {}} { ++ uplevel #0 $after $w $ok ++ } else { ++ done $w $ok ++ } ++ return ++ } ++ fconfigure $fd -blocking 0 ++} ++ ++proc chain {cmdlist w {ok 1}} { ++ if {$ok} { ++ if {[llength $cmdlist] == 0} { ++ done $w $ok ++ return ++ } ++ ++ set cmd [lindex $cmdlist 0] ++ set cmdlist [lrange $cmdlist 1 end] ++ ++ if {[lindex $cmd 0] eq {exec}} { ++ exec $w \ ++ [lindex $cmd 1] \ ++ [namespace code [list chain $cmdlist]] ++ } else { ++ uplevel #0 $cmd $cmdlist $w $ok ++ } ++ } else { ++ done $w $ok ++ } ++} ++ ++proc done {args} { ++ variable console_cr ++ variable console_data ++ ++ switch -- [llength $args] { ++ 2 { ++ set w [lindex $args 0] ++ set ok [lindex $args 1] ++ } ++ 3 { ++ set w [lindex $args 1] ++ set ok [lindex $args 2] ++ } ++ default { ++ error "wrong number of args: done ?ignored? w ok" ++ } ++ } ++ ++ if {$ok} { ++ if {[winfo exists $w]} { ++ $w.m.s conf -background green -text {Success} ++ $w.ok conf -state normal ++ focus $w.ok ++ } ++ } else { ++ if {![winfo exists $w]} { ++ _init $w ++ } ++ $w.m.s conf -background red -text {Error: Command Failed} ++ $w.ok conf -state normal ++ focus $w.ok ++ } ++ ++ array unset console_cr $w ++ array unset console_data $w ++} ++ ++} diff --cc git-gui/lib/database.tcl index 000000000,000000000..73058a826 new file mode 100644 --- /dev/null +++ b/git-gui/lib/database.tcl @@@ -1,0 -1,0 +1,89 @@@ ++# git-gui object database management support ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc do_stats {} { ++ set fd [open "| git count-objects -v" r] ++ while {[gets $fd line] > 0} { ++ if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { ++ set stats($name) $value ++ } ++ } ++ close $fd ++ ++ set packed_sz 0 ++ foreach p [glob -directory [gitdir objects pack] \ ++ -type f \ ++ -nocomplain -- *] { ++ incr packed_sz [file size $p] ++ } ++ if {$packed_sz > 0} { ++ set stats(size-pack) [expr {$packed_sz / 1024}] ++ } ++ ++ set w .stats_view ++ toplevel $w ++ wm geometry $w "+[winfo rootx .]+[winfo rooty .]" ++ ++ label $w.header -text {Database Statistics} ++ pack $w.header -side top -fill x ++ ++ frame $w.buttons -border 1 ++ button $w.buttons.close -text Close \ ++ -default active \ ++ -command [list destroy $w] ++ button $w.buttons.gc -text {Compress Database} \ ++ -default normal \ ++ -command "destroy $w;do_gc" ++ pack $w.buttons.close -side right ++ pack $w.buttons.gc -side left ++ pack $w.buttons -side bottom -fill x -pady 10 -padx 10 ++ ++ frame $w.stat -borderwidth 1 -relief solid ++ foreach s { ++ {count {Number of loose objects}} ++ {size {Disk space used by loose objects} { KiB}} ++ {in-pack {Number of packed objects}} ++ {packs {Number of packs}} ++ {size-pack {Disk space used by packed objects} { KiB}} ++ {prune-packable {Packed objects waiting for pruning}} ++ {garbage {Garbage files}} ++ } { ++ set name [lindex $s 0] ++ set label [lindex $s 1] ++ if {[catch {set value $stats($name)}]} continue ++ if {[llength $s] > 2} { ++ set value "$value[lindex $s 2]" ++ } ++ ++ label $w.stat.l_$name -text "$label:" -anchor w ++ label $w.stat.v_$name -text $value -anchor w ++ grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} ++ } ++ pack $w.stat -pady 10 -padx 10 ++ ++ bind $w "grab $w; focus $w.buttons.close" ++ bind $w [list destroy $w] ++ bind $w [list destroy $w] ++ wm title $w "[appname] ([reponame]): Database Statistics" ++ tkwait window $w ++} ++ ++proc do_gc {} { ++ set w [console::new {gc} {Compressing the object database}] ++ console::chain { ++ {exec {git pack-refs --prune}} ++ {exec {git reflog expire --all}} ++ {exec {git repack -a -d -l}} ++ {exec {git rerere gc}} ++ } $w ++} ++ ++proc do_fsck_objects {} { ++ set w [console::new {fsck-objects} \ ++ {Verifying the object database with fsck-objects}] ++ set cmd [list git fsck-objects] ++ lappend cmd --full ++ lappend cmd --cache ++ lappend cmd --strict ++ console::exec $w $cmd ++} diff --cc git-gui/lib/diff.tcl index 000000000,000000000..7e715a686 new file mode 100644 --- /dev/null +++ b/git-gui/lib/diff.tcl @@@ -1,0 -1,0 +1,336 @@@ ++# git-gui diff viewer ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc clear_diff {} { ++ global ui_diff current_diff_path current_diff_header ++ global ui_index ui_workdir ++ ++ $ui_diff conf -state normal ++ $ui_diff delete 0.0 end ++ $ui_diff conf -state disabled ++ ++ set current_diff_path {} ++ set current_diff_header {} ++ ++ $ui_index tag remove in_diff 0.0 end ++ $ui_workdir tag remove in_diff 0.0 end ++} ++ ++proc reshow_diff {} { ++ global ui_status_value file_states file_lists ++ global current_diff_path current_diff_side ++ ++ set p $current_diff_path ++ if {$p eq {}} { ++ # No diff is being shown. ++ } elseif {$current_diff_side eq {} ++ || [catch {set s $file_states($p)}] ++ || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { ++ clear_diff ++ } else { ++ show_diff $p $current_diff_side ++ } ++} ++ ++proc handle_empty_diff {} { ++ global current_diff_path file_states file_lists ++ ++ set path $current_diff_path ++ set s $file_states($path) ++ if {[lindex $s 0] ne {_M}} return ++ ++ info_popup "No differences detected. ++ ++[short_path $path] has no changes. ++ ++The modification date of this file was updated by another application, but the content within the file was not changed. ++ ++A rescan will be automatically started to find other files which may have the same state." ++ ++ clear_diff ++ display_file $path __ ++ rescan {set ui_status_value {Ready.}} 0 ++} ++ ++proc show_diff {path w {lno {}}} { ++ global file_states file_lists ++ global is_3way_diff diff_active repo_config ++ global ui_diff ui_status_value ui_index ui_workdir ++ global current_diff_path current_diff_side current_diff_header ++ ++ if {$diff_active || ![lock_index read]} return ++ ++ clear_diff ++ if {$lno == {}} { ++ set lno [lsearch -sorted -exact $file_lists($w) $path] ++ if {$lno >= 0} { ++ incr lno ++ } ++ } ++ if {$lno >= 1} { ++ $w tag add in_diff $lno.0 [expr {$lno + 1}].0 ++ } ++ ++ set s $file_states($path) ++ set m [lindex $s 0] ++ set is_3way_diff 0 ++ set diff_active 1 ++ set current_diff_path $path ++ set current_diff_side $w ++ set current_diff_header {} ++ set ui_status_value "Loading diff of [escape_path $path]..." ++ ++ # - Git won't give us the diff, there's nothing to compare to! ++ # ++ if {$m eq {_O}} { ++ set max_sz [expr {128 * 1024}] ++ if {[catch { ++ set fd [open $path r] ++ set content [read $fd $max_sz] ++ close $fd ++ set sz [file size $path] ++ } err ]} { ++ set diff_active 0 ++ unlock_index ++ set ui_status_value "Unable to display [escape_path $path]" ++ error_popup "Error loading file:\n\n$err" ++ return ++ } ++ $ui_diff conf -state normal ++ if {![catch {set type [exec file $path]}]} { ++ set n [string length $path] ++ if {[string equal -length $n $path $type]} { ++ set type [string range $type $n end] ++ regsub {^:?\s*} $type {} type ++ } ++ $ui_diff insert end "* $type\n" d_@ ++ } ++ if {[string first "\0" $content] != -1} { ++ $ui_diff insert end \ ++ "* Binary file (not showing content)." \ ++ d_@ ++ } else { ++ if {$sz > $max_sz} { ++ $ui_diff insert end \ ++"* Untracked file is $sz bytes. ++* Showing only first $max_sz bytes. ++" d_@ ++ } ++ $ui_diff insert end $content ++ if {$sz > $max_sz} { ++ $ui_diff insert end " ++* Untracked file clipped here by [appname]. ++* To see the entire file, use an external editor. ++" d_@ ++ } ++ } ++ $ui_diff conf -state disabled ++ set diff_active 0 ++ unlock_index ++ set ui_status_value {Ready.} ++ return ++ } ++ ++ set cmd [list | git] ++ if {$w eq $ui_index} { ++ lappend cmd diff-index ++ lappend cmd --cached ++ } elseif {$w eq $ui_workdir} { ++ if {[string index $m 0] eq {U}} { ++ lappend cmd diff ++ } else { ++ lappend cmd diff-files ++ } ++ } ++ ++ lappend cmd -p ++ lappend cmd --no-color ++ if {$repo_config(gui.diffcontext) > 0} { ++ lappend cmd "-U$repo_config(gui.diffcontext)" ++ } ++ if {$w eq $ui_index} { ++ lappend cmd [PARENT] ++ } ++ lappend cmd -- ++ lappend cmd $path ++ ++ if {[catch {set fd [open $cmd r]} err]} { ++ set diff_active 0 ++ unlock_index ++ set ui_status_value "Unable to display [escape_path $path]" ++ error_popup "Error loading diff:\n\n$err" ++ return ++ } ++ ++ fconfigure $fd \ ++ -blocking 0 \ ++ -encoding binary \ ++ -translation binary ++ fileevent $fd readable [list read_diff $fd] ++} ++ ++proc read_diff {fd} { ++ global ui_diff ui_status_value diff_active ++ global is_3way_diff current_diff_header ++ ++ $ui_diff conf -state normal ++ while {[gets $fd line] >= 0} { ++ # -- Cleanup uninteresting diff header lines. ++ # ++ if { [string match {diff --git *} $line] ++ || [string match {diff --cc *} $line] ++ || [string match {diff --combined *} $line] ++ || [string match {--- *} $line] ++ || [string match {+++ *} $line]} { ++ append current_diff_header $line "\n" ++ continue ++ } ++ if {[string match {index *} $line]} continue ++ if {$line eq {deleted file mode 120000}} { ++ set line "deleted symlink" ++ } ++ ++ # -- Automatically detect if this is a 3 way diff. ++ # ++ if {[string match {@@@ *} $line]} {set is_3way_diff 1} ++ ++ if {[string match {mode *} $line] ++ || [string match {new file *} $line] ++ || [string match {deleted file *} $line] ++ || [string match {Binary files * and * differ} $line] ++ || $line eq {\ No newline at end of file} ++ || [regexp {^\* Unmerged path } $line]} { ++ set tags {} ++ } elseif {$is_3way_diff} { ++ set op [string range $line 0 1] ++ switch -- $op { ++ { } {set tags {}} ++ {@@} {set tags d_@} ++ { +} {set tags d_s+} ++ { -} {set tags d_s-} ++ {+ } {set tags d_+s} ++ {- } {set tags d_-s} ++ {--} {set tags d_--} ++ {++} { ++ if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} { ++ set line [string replace $line 0 1 { }] ++ set tags d$op ++ } else { ++ set tags d_++ ++ } ++ } ++ default { ++ puts "error: Unhandled 3 way diff marker: {$op}" ++ set tags {} ++ } ++ } ++ } else { ++ set op [string index $line 0] ++ switch -- $op { ++ { } {set tags {}} ++ {@} {set tags d_@} ++ {-} {set tags d_-} ++ {+} { ++ if {[regexp {^\+([<>]{7} |={7})} $line _g op]} { ++ set line [string replace $line 0 0 { }] ++ set tags d$op ++ } else { ++ set tags d_+ ++ } ++ } ++ default { ++ puts "error: Unhandled 2 way diff marker: {$op}" ++ set tags {} ++ } ++ } ++ } ++ $ui_diff insert end $line $tags ++ if {[string index $line end] eq "\r"} { ++ $ui_diff tag add d_cr {end - 2c} ++ } ++ $ui_diff insert end "\n" $tags ++ } ++ $ui_diff conf -state disabled ++ ++ if {[eof $fd]} { ++ close $fd ++ set diff_active 0 ++ unlock_index ++ set ui_status_value {Ready.} ++ ++ if {[$ui_diff index end] eq {2.0}} { ++ handle_empty_diff ++ } ++ } ++} ++ ++proc apply_hunk {x y} { ++ global current_diff_path current_diff_header current_diff_side ++ global ui_diff ui_index file_states ++ ++ if {$current_diff_path eq {} || $current_diff_header eq {}} return ++ if {![lock_index apply_hunk]} return ++ ++ set apply_cmd {git apply --cached --whitespace=nowarn} ++ set mi [lindex $file_states($current_diff_path) 0] ++ if {$current_diff_side eq $ui_index} { ++ set mode unstage ++ lappend apply_cmd --reverse ++ if {[string index $mi 0] ne {M}} { ++ unlock_index ++ return ++ } ++ } else { ++ set mode stage ++ if {[string index $mi 1] ne {M}} { ++ unlock_index ++ return ++ } ++ } ++ ++ set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0] ++ set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0] ++ if {$s_lno eq {}} { ++ unlock_index ++ return ++ } ++ ++ set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end] ++ if {$e_lno eq {}} { ++ set e_lno end ++ } ++ ++ if {[catch { ++ set p [open "| $apply_cmd" w] ++ fconfigure $p -translation binary -encoding binary ++ puts -nonewline $p $current_diff_header ++ puts -nonewline $p [$ui_diff get $s_lno $e_lno] ++ close $p} err]} { ++ error_popup "Failed to $mode selected hunk.\n\n$err" ++ unlock_index ++ return ++ } ++ ++ $ui_diff conf -state normal ++ $ui_diff delete $s_lno $e_lno ++ $ui_diff conf -state disabled ++ ++ if {[$ui_diff get 1.0 end] eq "\n"} { ++ set o _ ++ } else { ++ set o ? ++ } ++ ++ if {$current_diff_side eq $ui_index} { ++ set mi ${o}M ++ } elseif {[string index $mi 0] eq {_}} { ++ set mi M$o ++ } else { ++ set mi ?$o ++ } ++ unlock_index ++ display_file $current_diff_path $mi ++ if {$o eq {_}} { ++ clear_diff ++ } ++} diff --cc git-gui/lib/error.tcl index 000000000,000000000..d0253ae2f new file mode 100644 --- /dev/null +++ b/git-gui/lib/error.tcl @@@ -1,0 -1,0 +1,101 @@@ ++# git-gui branch (create/delete) support ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc error_popup {msg} { ++ set title [appname] ++ if {[reponame] ne {}} { ++ append title " ([reponame])" ++ } ++ set cmd [list tk_messageBox \ ++ -icon error \ ++ -type ok \ ++ -title "$title: error" \ ++ -message $msg] ++ if {[winfo ismapped .]} { ++ lappend cmd -parent . ++ } ++ eval $cmd ++} ++ ++proc warn_popup {msg} { ++ set title [appname] ++ if {[reponame] ne {}} { ++ append title " ([reponame])" ++ } ++ set cmd [list tk_messageBox \ ++ -icon warning \ ++ -type ok \ ++ -title "$title: warning" \ ++ -message $msg] ++ if {[winfo ismapped .]} { ++ lappend cmd -parent . ++ } ++ eval $cmd ++} ++ ++proc info_popup {msg {parent .}} { ++ set title [appname] ++ if {[reponame] ne {}} { ++ append title " ([reponame])" ++ } ++ tk_messageBox \ ++ -parent $parent \ ++ -icon info \ ++ -type ok \ ++ -title $title \ ++ -message $msg ++} ++ ++proc ask_popup {msg} { ++ set title [appname] ++ if {[reponame] ne {}} { ++ append title " ([reponame])" ++ } ++ return [tk_messageBox \ ++ -parent . \ ++ -icon question \ ++ -type yesno \ ++ -title $title \ ++ -message $msg] ++} ++ ++proc hook_failed_popup {hook msg} { ++ set w .hookfail ++ toplevel $w ++ ++ frame $w.m ++ label $w.m.l1 -text "$hook hook failed:" \ ++ -anchor w \ ++ -justify left \ ++ -font font_uibold ++ text $w.m.t \ ++ -background white -borderwidth 1 \ ++ -relief sunken \ ++ -width 80 -height 10 \ ++ -font font_diff \ ++ -yscrollcommand [list $w.m.sby set] ++ label $w.m.l2 \ ++ -text {You must correct the above errors before committing.} \ ++ -anchor w \ ++ -justify left \ ++ -font font_uibold ++ scrollbar $w.m.sby -command [list $w.m.t yview] ++ pack $w.m.l1 -side top -fill x ++ pack $w.m.l2 -side bottom -fill x ++ pack $w.m.sby -side right -fill y ++ pack $w.m.t -side left -fill both -expand 1 ++ pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 ++ ++ $w.m.t insert 1.0 $msg ++ $w.m.t conf -state disabled ++ ++ button $w.ok -text OK \ ++ -width 15 \ ++ -command "destroy $w" ++ pack $w.ok -side bottom -anchor e -pady 10 -padx 10 ++ ++ bind $w "grab $w; focus $w" ++ bind $w "destroy $w" ++ wm title $w "[appname] ([reponame]): error" ++ tkwait window $w ++} diff --cc git-gui/lib/index.tcl index 000000000,000000000..42742850e new file mode 100644 --- /dev/null +++ b/git-gui/lib/index.tcl @@@ -1,0 -1,0 +1,409 @@@ ++# git-gui index (add/remove) support ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc update_indexinfo {msg pathList after} { ++ global update_index_cp ui_status_value ++ ++ if {![lock_index update]} return ++ ++ set update_index_cp 0 ++ set pathList [lsort $pathList] ++ set totalCnt [llength $pathList] ++ set batch [expr {int($totalCnt * .01) + 1}] ++ if {$batch > 25} {set batch 25} ++ ++ set ui_status_value [format \ ++ "$msg... %i/%i files (%.2f%%)" \ ++ $update_index_cp \ ++ $totalCnt \ ++ 0.0] ++ set fd [open "| git update-index -z --index-info" w] ++ fconfigure $fd \ ++ -blocking 0 \ ++ -buffering full \ ++ -buffersize 512 \ ++ -encoding binary \ ++ -translation binary ++ fileevent $fd writable [list \ ++ write_update_indexinfo \ ++ $fd \ ++ $pathList \ ++ $totalCnt \ ++ $batch \ ++ $msg \ ++ $after \ ++ ] ++} ++ ++proc write_update_indexinfo {fd pathList totalCnt batch msg after} { ++ global update_index_cp ui_status_value ++ global file_states current_diff_path ++ ++ if {$update_index_cp >= $totalCnt} { ++ close $fd ++ unlock_index ++ uplevel #0 $after ++ return ++ } ++ ++ for {set i $batch} \ ++ {$update_index_cp < $totalCnt && $i > 0} \ ++ {incr i -1} { ++ set path [lindex $pathList $update_index_cp] ++ incr update_index_cp ++ ++ set s $file_states($path) ++ switch -glob -- [lindex $s 0] { ++ A? {set new _O} ++ M? {set new _M} ++ D_ {set new _D} ++ D? {set new _?} ++ ?? {continue} ++ } ++ set info [lindex $s 2] ++ if {$info eq {}} continue ++ ++ puts -nonewline $fd "$info\t[encoding convertto $path]\0" ++ display_file $path $new ++ } ++ ++ set ui_status_value [format \ ++ "$msg... %i/%i files (%.2f%%)" \ ++ $update_index_cp \ ++ $totalCnt \ ++ [expr {100.0 * $update_index_cp / $totalCnt}]] ++} ++ ++proc update_index {msg pathList after} { ++ global update_index_cp ui_status_value ++ ++ if {![lock_index update]} return ++ ++ set update_index_cp 0 ++ set pathList [lsort $pathList] ++ set totalCnt [llength $pathList] ++ set batch [expr {int($totalCnt * .01) + 1}] ++ if {$batch > 25} {set batch 25} ++ ++ set ui_status_value [format \ ++ "$msg... %i/%i files (%.2f%%)" \ ++ $update_index_cp \ ++ $totalCnt \ ++ 0.0] ++ set fd [open "| git update-index --add --remove -z --stdin" w] ++ fconfigure $fd \ ++ -blocking 0 \ ++ -buffering full \ ++ -buffersize 512 \ ++ -encoding binary \ ++ -translation binary ++ fileevent $fd writable [list \ ++ write_update_index \ ++ $fd \ ++ $pathList \ ++ $totalCnt \ ++ $batch \ ++ $msg \ ++ $after \ ++ ] ++} ++ ++proc write_update_index {fd pathList totalCnt batch msg after} { ++ global update_index_cp ui_status_value ++ global file_states current_diff_path ++ ++ if {$update_index_cp >= $totalCnt} { ++ close $fd ++ unlock_index ++ uplevel #0 $after ++ return ++ } ++ ++ for {set i $batch} \ ++ {$update_index_cp < $totalCnt && $i > 0} \ ++ {incr i -1} { ++ set path [lindex $pathList $update_index_cp] ++ incr update_index_cp ++ ++ switch -glob -- [lindex $file_states($path) 0] { ++ AD {set new __} ++ ?D {set new D_} ++ _O - ++ AM {set new A_} ++ U? { ++ if {[file exists $path]} { ++ set new M_ ++ } else { ++ set new D_ ++ } ++ } ++ ?M {set new M_} ++ ?? {continue} ++ } ++ puts -nonewline $fd "[encoding convertto $path]\0" ++ display_file $path $new ++ } ++ ++ set ui_status_value [format \ ++ "$msg... %i/%i files (%.2f%%)" \ ++ $update_index_cp \ ++ $totalCnt \ ++ [expr {100.0 * $update_index_cp / $totalCnt}]] ++} ++ ++proc checkout_index {msg pathList after} { ++ global update_index_cp ui_status_value ++ ++ if {![lock_index update]} return ++ ++ set update_index_cp 0 ++ set pathList [lsort $pathList] ++ set totalCnt [llength $pathList] ++ set batch [expr {int($totalCnt * .01) + 1}] ++ if {$batch > 25} {set batch 25} ++ ++ set ui_status_value [format \ ++ "$msg... %i/%i files (%.2f%%)" \ ++ $update_index_cp \ ++ $totalCnt \ ++ 0.0] ++ set cmd [list git checkout-index] ++ lappend cmd --index ++ lappend cmd --quiet ++ lappend cmd --force ++ lappend cmd -z ++ lappend cmd --stdin ++ set fd [open "| $cmd " w] ++ fconfigure $fd \ ++ -blocking 0 \ ++ -buffering full \ ++ -buffersize 512 \ ++ -encoding binary \ ++ -translation binary ++ fileevent $fd writable [list \ ++ write_checkout_index \ ++ $fd \ ++ $pathList \ ++ $totalCnt \ ++ $batch \ ++ $msg \ ++ $after \ ++ ] ++} ++ ++proc write_checkout_index {fd pathList totalCnt batch msg after} { ++ global update_index_cp ui_status_value ++ global file_states current_diff_path ++ ++ if {$update_index_cp >= $totalCnt} { ++ close $fd ++ unlock_index ++ uplevel #0 $after ++ return ++ } ++ ++ for {set i $batch} \ ++ {$update_index_cp < $totalCnt && $i > 0} \ ++ {incr i -1} { ++ set path [lindex $pathList $update_index_cp] ++ incr update_index_cp ++ switch -glob -- [lindex $file_states($path) 0] { ++ U? {continue} ++ ?M - ++ ?D { ++ puts -nonewline $fd "[encoding convertto $path]\0" ++ display_file $path ?_ ++ } ++ } ++ } ++ ++ set ui_status_value [format \ ++ "$msg... %i/%i files (%.2f%%)" \ ++ $update_index_cp \ ++ $totalCnt \ ++ [expr {100.0 * $update_index_cp / $totalCnt}]] ++} ++ ++proc unstage_helper {txt paths} { ++ global file_states current_diff_path ++ ++ if {![lock_index begin-update]} return ++ ++ set pathList [list] ++ set after {} ++ foreach path $paths { ++ switch -glob -- [lindex $file_states($path) 0] { ++ A? - ++ M? - ++ D? { ++ lappend pathList $path ++ if {$path eq $current_diff_path} { ++ set after {reshow_diff;} ++ } ++ } ++ } ++ } ++ if {$pathList eq {}} { ++ unlock_index ++ } else { ++ update_indexinfo \ ++ $txt \ ++ $pathList \ ++ [concat $after {set ui_status_value {Ready.}}] ++ } ++} ++ ++proc do_unstage_selection {} { ++ global current_diff_path selected_paths ++ ++ if {[array size selected_paths] > 0} { ++ unstage_helper \ ++ {Unstaging selected files from commit} \ ++ [array names selected_paths] ++ } elseif {$current_diff_path ne {}} { ++ unstage_helper \ ++ "Unstaging [short_path $current_diff_path] from commit" \ ++ [list $current_diff_path] ++ } ++} ++ ++proc add_helper {txt paths} { ++ global file_states current_diff_path ++ ++ if {![lock_index begin-update]} return ++ ++ set pathList [list] ++ set after {} ++ foreach path $paths { ++ switch -glob -- [lindex $file_states($path) 0] { ++ _O - ++ ?M - ++ ?D - ++ U? { ++ lappend pathList $path ++ if {$path eq $current_diff_path} { ++ set after {reshow_diff;} ++ } ++ } ++ } ++ } ++ if {$pathList eq {}} { ++ unlock_index ++ } else { ++ update_index \ ++ $txt \ ++ $pathList \ ++ [concat $after {set ui_status_value {Ready to commit.}}] ++ } ++} ++ ++proc do_add_selection {} { ++ global current_diff_path selected_paths ++ ++ if {[array size selected_paths] > 0} { ++ add_helper \ ++ {Adding selected files} \ ++ [array names selected_paths] ++ } elseif {$current_diff_path ne {}} { ++ add_helper \ ++ "Adding [short_path $current_diff_path]" \ ++ [list $current_diff_path] ++ } ++} ++ ++proc do_add_all {} { ++ global file_states ++ ++ set paths [list] ++ foreach path [array names file_states] { ++ switch -glob -- [lindex $file_states($path) 0] { ++ U? {continue} ++ ?M - ++ ?D {lappend paths $path} ++ } ++ } ++ add_helper {Adding all changed files} $paths ++} ++ ++proc revert_helper {txt paths} { ++ global file_states current_diff_path ++ ++ if {![lock_index begin-update]} return ++ ++ set pathList [list] ++ set after {} ++ foreach path $paths { ++ switch -glob -- [lindex $file_states($path) 0] { ++ U? {continue} ++ ?M - ++ ?D { ++ lappend pathList $path ++ if {$path eq $current_diff_path} { ++ set after {reshow_diff;} ++ } ++ } ++ } ++ } ++ ++ set n [llength $pathList] ++ if {$n == 0} { ++ unlock_index ++ return ++ } elseif {$n == 1} { ++ set s "[short_path [lindex $pathList]]" ++ } else { ++ set s "these $n files" ++ } ++ ++ set reply [tk_dialog \ ++ .confirm_revert \ ++ "[appname] ([reponame])" \ ++ "Revert changes in $s? ++ ++Any unadded changes will be permanently lost by the revert." \ ++ question \ ++ 1 \ ++ {Do Nothing} \ ++ {Revert Changes} \ ++ ] ++ if {$reply == 1} { ++ checkout_index \ ++ $txt \ ++ $pathList \ ++ [concat $after {set ui_status_value {Ready.}}] ++ } else { ++ unlock_index ++ } ++} ++ ++proc do_revert_selection {} { ++ global current_diff_path selected_paths ++ ++ if {[array size selected_paths] > 0} { ++ revert_helper \ ++ {Reverting selected files} \ ++ [array names selected_paths] ++ } elseif {$current_diff_path ne {}} { ++ revert_helper \ ++ "Reverting [short_path $current_diff_path]" \ ++ [list $current_diff_path] ++ } ++} ++ ++proc do_select_commit_type {} { ++ global commit_type selected_commit_type ++ ++ if {$selected_commit_type eq {new} ++ && [string match amend* $commit_type]} { ++ create_new_commit ++ } elseif {$selected_commit_type eq {amend} ++ && ![string match amend* $commit_type]} { ++ load_last_commit ++ ++ # The amend request was rejected... ++ # ++ if {![string match amend* $commit_type]} { ++ set selected_commit_type new ++ } ++ } ++} diff --cc git-gui/lib/merge.tcl index 000000000,000000000..3dce856e5 new file mode 100644 --- /dev/null +++ b/git-gui/lib/merge.tcl @@@ -1,0 -1,0 +1,309 @@@ ++# git-gui branch merge support ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++namespace eval merge { ++ ++proc _can_merge {} { ++ global HEAD commit_type file_states ++ ++ if {[string match amend* $commit_type]} { ++ info_popup {Cannot merge while amending. ++ ++You must finish amending this commit before starting any type of merge. ++} ++ return 0 ++ } ++ ++ if {[committer_ident] eq {}} {return 0} ++ if {![lock_index merge]} {return 0} ++ ++ # -- Our in memory state should match the repository. ++ # ++ repository_state curType curHEAD curMERGE_HEAD ++ if {$commit_type ne $curType || $HEAD ne $curHEAD} { ++ info_popup {Last scanned state does not match repository state. ++ ++Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed. ++ ++The rescan will be automatically started now. ++} ++ unlock_index ++ rescan {set ui_status_value {Ready.}} ++ return 0 ++ } ++ ++ foreach path [array names file_states] { ++ switch -glob -- [lindex $file_states($path) 0] { ++ _O { ++ continue; # and pray it works! ++ } ++ U? { ++ error_popup "You are in the middle of a conflicted merge. ++ ++File [short_path $path] has merge conflicts. ++ ++You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge. ++" ++ unlock_index ++ return 0 ++ } ++ ?? { ++ error_popup "You are in the middle of a change. ++ ++File [short_path $path] is modified. ++ ++You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise. ++" ++ unlock_index ++ return 0 ++ } ++ } ++ } ++ ++ return 1 ++} ++ ++proc _refs {w list} { ++ set r {} ++ foreach i [$w.source.l curselection] { ++ lappend r [lindex [lindex $list $i] 0] ++ } ++ return $r ++} ++ ++proc _visualize {w list} { ++ set revs [_refs $w $list] ++ if {$revs eq {}} return ++ lappend revs --not HEAD ++ do_gitk $revs ++} ++ ++proc _start {w list} { ++ global HEAD ui_status_value current_branch ++ ++ set cmd [list git merge] ++ set names [_refs $w $list] ++ set revcnt [llength $names] ++ append cmd { } $names ++ ++ if {$revcnt == 0} { ++ return ++ } elseif {$revcnt == 1} { ++ set unit branch ++ } elseif {$revcnt <= 15} { ++ set unit branches ++ ++ if {[tk_dialog \ ++ $w.confirm_octopus \ ++ [wm title $w] \ ++ "Use octopus merge strategy? ++ ++You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts. ++" \ ++ question \ ++ 0 \ ++ {Cancel} \ ++ {Use octopus} \ ++ ] != 1} return ++ } else { ++ tk_messageBox \ ++ -icon error \ ++ -type ok \ ++ -title [wm title $w] \ ++ -parent $w \ ++ -message "Too many branches selected. ++ ++You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge. ++ ++Please select fewer branches. To merge more than 15 branches, merge the branches in batches. ++" ++ return ++ } ++ ++ set msg "Merging $current_branch, [join $names {, }]" ++ set ui_status_value "$msg..." ++ set cons [console::new "Merge" $msg] ++ console::exec $cons $cmd [namespace code [list _finish $revcnt]] ++ bind $w {} ++ destroy $w ++} ++ ++proc _finish {revcnt w ok} { ++ console::done $w $ok ++ if {$ok} { ++ set msg {Merge completed successfully.} ++ } else { ++ if {$revcnt != 1} { ++ info_popup "Octopus merge failed. ++ ++Your merge of $revcnt branches has failed. ++ ++There are file-level conflicts between the branches which must be resolved manually. ++ ++The working directory will now be reset. ++ ++You can attempt this merge again by merging only one branch at a time." $w ++ ++ set fd [open "| git read-tree --reset -u HEAD" r] ++ fconfigure $fd -blocking 0 -translation binary ++ fileevent $fd readable \ ++ [namespace code [list _reset_wait $fd]] ++ set ui_status_value {Aborting... please wait...} ++ return ++ } ++ ++ set msg {Merge failed. Conflict resolution is required.} ++ } ++ unlock_index ++ rescan [list set ui_status_value $msg] ++} ++ ++proc dialog {} { ++ global current_branch ++ global M1B ++ ++ if {![_can_merge]} return ++ ++ set fmt {list %(objectname) %(*objectname) %(refname) %(subject)} ++ set cmd [list git for-each-ref --tcl --format=$fmt] ++ lappend cmd refs/heads ++ lappend cmd refs/remotes ++ lappend cmd refs/tags ++ set fr_fd [open "| $cmd" r] ++ fconfigure $fr_fd -translation binary ++ while {[gets $fr_fd line] > 0} { ++ set line [eval $line] ++ set ref [lindex $line 2] ++ regsub ^refs/(heads|remotes|tags)/ $ref {} ref ++ set subj($ref) [lindex $line 3] ++ lappend sha1([lindex $line 0]) $ref ++ if {[lindex $line 1] ne {}} { ++ lappend sha1([lindex $line 1]) $ref ++ } ++ } ++ close $fr_fd ++ ++ set to_show {} ++ set fr_fd [open "| git rev-list --all --not HEAD"] ++ while {[gets $fr_fd line] > 0} { ++ if {[catch {set ref $sha1($line)}]} continue ++ foreach n $ref { ++ lappend to_show [list $n $line] ++ } ++ } ++ close $fr_fd ++ set to_show [lsort -unique $to_show] ++ ++ set w .merge_setup ++ toplevel $w ++ wm geometry $w "+[winfo rootx .]+[winfo rooty .]" ++ ++ set _visualize [namespace code [list _visualize $w $to_show]] ++ set _start [namespace code [list _start $w $to_show]] ++ ++ label $w.header \ ++ -text "Merge Into $current_branch" \ ++ -font font_uibold ++ pack $w.header -side top -fill x ++ ++ frame $w.buttons ++ button $w.buttons.visualize -text Visualize -command $_visualize ++ pack $w.buttons.visualize -side left ++ button $w.buttons.create -text Merge -command $_start ++ pack $w.buttons.create -side right ++ button $w.buttons.cancel -text {Cancel} -command [list destroy $w] ++ pack $w.buttons.cancel -side right -padx 5 ++ pack $w.buttons -side bottom -fill x -pady 10 -padx 10 ++ ++ labelframe $w.source -text {Source Branches} ++ listbox $w.source.l \ ++ -height 10 \ ++ -width 70 \ ++ -font font_diff \ ++ -selectmode extended \ ++ -yscrollcommand [list $w.source.sby set] ++ scrollbar $w.source.sby -command [list $w.source.l yview] ++ pack $w.source.sby -side right -fill y ++ pack $w.source.l -side left -fill both -expand 1 ++ pack $w.source -fill both -expand 1 -pady 5 -padx 5 ++ ++ foreach ref $to_show { ++ set n [lindex $ref 0] ++ if {[string length $n] > 20} { ++ set n "[string range $n 0 16]..." ++ } ++ $w.source.l insert end [format {%s %-20s %s} \ ++ [string range [lindex $ref 1] 0 5] \ ++ $n \ ++ $subj([lindex $ref 0])] ++ } ++ ++ bind $w.source.l [list event generate %W ] ++ bind $w.source.l [list event generate %W ] ++ bind $w.source.l [list event generate %W ] ++ bind $w.source.l [list event generate %W ] ++ bind $w.source.l $_visualize ++ ++ bind $w <$M1B-Key-Return> $_start ++ bind $w "grab $w; focus $w.source.l" ++ bind $w "unlock_index;destroy $w" ++ bind $w unlock_index ++ wm title $w "[appname] ([reponame]): Merge" ++ tkwait window $w ++} ++ ++proc reset_hard {} { ++ global HEAD commit_type file_states ++ ++ if {[string match amend* $commit_type]} { ++ info_popup {Cannot abort while amending. ++ ++You must finish amending this commit. ++} ++ return ++ } ++ ++ if {![lock_index abort]} return ++ ++ if {[string match *merge* $commit_type]} { ++ set op merge ++ } else { ++ set op commit ++ } ++ ++ if {[ask_popup "Abort $op? ++ ++Aborting the current $op will cause *ALL* uncommitted changes to be lost. ++ ++Continue with aborting the current $op?"] eq {yes}} { ++ set fd [open "| git read-tree --reset -u HEAD" r] ++ fconfigure $fd -blocking 0 -translation binary ++ fileevent $fd readable [namespace code [list _reset_wait $fd]] ++ set ui_status_value {Aborting... please wait...} ++ } else { ++ unlock_index ++ } ++} ++ ++proc _reset_wait {fd} { ++ global ui_comm ++ ++ read $fd ++ if {[eof $fd]} { ++ close $fd ++ unlock_index ++ ++ $ui_comm delete 0.0 end ++ $ui_comm edit modified false ++ ++ catch {file delete [gitdir MERGE_HEAD]} ++ catch {file delete [gitdir rr-cache MERGE_RR]} ++ catch {file delete [gitdir SQUASH_MSG]} ++ catch {file delete [gitdir MERGE_MSG]} ++ catch {file delete [gitdir GITGUI_MSG]} ++ ++ rescan {set ui_status_value {Abort completed. Ready.}} ++ } ++} ++ ++} diff --cc git-gui/lib/option.tcl index 000000000,000000000..17fcc65f7 new file mode 100644 --- /dev/null +++ b/git-gui/lib/option.tcl @@@ -1,0 -1,0 +1,290 @@@ ++# git-gui options editor ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc save_config {} { ++ global default_config font_descs ++ global repo_config global_config ++ global repo_config_new global_config_new ++ ++ foreach option $font_descs { ++ set name [lindex $option 0] ++ set font [lindex $option 1] ++ font configure $font \ ++ -family $global_config_new(gui.$font^^family) \ ++ -size $global_config_new(gui.$font^^size) ++ font configure ${font}bold \ ++ -family $global_config_new(gui.$font^^family) \ ++ -size $global_config_new(gui.$font^^size) ++ set global_config_new(gui.$name) [font configure $font] ++ unset global_config_new(gui.$font^^family) ++ unset global_config_new(gui.$font^^size) ++ } ++ ++ foreach name [array names default_config] { ++ set value $global_config_new($name) ++ if {$value ne $global_config($name)} { ++ if {$value eq $default_config($name)} { ++ catch {git config --global --unset $name} ++ } else { ++ regsub -all "\[{}\]" $value {"} value ++ git config --global $name $value ++ } ++ set global_config($name) $value ++ if {$value eq $repo_config($name)} { ++ catch {git config --unset $name} ++ set repo_config($name) $value ++ } ++ } ++ } ++ ++ foreach name [array names default_config] { ++ set value $repo_config_new($name) ++ if {$value ne $repo_config($name)} { ++ if {$value eq $global_config($name)} { ++ catch {git config --unset $name} ++ } else { ++ regsub -all "\[{}\]" $value {"} value ++ git config $name $value ++ } ++ set repo_config($name) $value ++ } ++ } ++} ++ ++proc do_about {} { ++ global appvers copyright ++ global tcl_patchLevel tk_patchLevel ++ ++ set w .about_dialog ++ toplevel $w ++ wm geometry $w "+[winfo rootx .]+[winfo rooty .]" ++ ++ label $w.header -text "About [appname]" \ ++ -font font_uibold ++ pack $w.header -side top -fill x ++ ++ frame $w.buttons ++ button $w.buttons.close -text {Close} \ ++ -default active \ ++ -command [list destroy $w] ++ pack $w.buttons.close -side right ++ pack $w.buttons -side bottom -fill x -pady 10 -padx 10 ++ ++ label $w.desc \ ++ -text "git-gui - a graphical user interface for Git. ++$copyright" \ ++ -padx 5 -pady 5 \ ++ -justify left \ ++ -anchor w \ ++ -borderwidth 1 \ ++ -relief solid ++ pack $w.desc -side top -fill x -padx 5 -pady 5 ++ ++ set v {} ++ append v "git-gui version $appvers\n" ++ append v "[git version]\n" ++ append v "\n" ++ if {$tcl_patchLevel eq $tk_patchLevel} { ++ append v "Tcl/Tk version $tcl_patchLevel" ++ } else { ++ append v "Tcl version $tcl_patchLevel" ++ append v ", Tk version $tk_patchLevel" ++ } ++ ++ label $w.vers \ ++ -text $v \ ++ -padx 5 -pady 5 \ ++ -justify left \ ++ -anchor w \ ++ -borderwidth 1 \ ++ -relief solid ++ pack $w.vers -side top -fill x -padx 5 -pady 5 ++ ++ menu $w.ctxm -tearoff 0 ++ $w.ctxm add command \ ++ -label {Copy} \ ++ -command " ++ clipboard clear ++ clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] ++ " ++ ++ bind $w "grab $w; focus $w.buttons.close" ++ bind $w "destroy $w" ++ bind $w "destroy $w" ++ bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" ++ wm title $w "About [appname]" ++ tkwait window $w ++} ++ ++proc do_options {} { ++ global repo_config global_config font_descs ++ global repo_config_new global_config_new ++ ++ array unset repo_config_new ++ array unset global_config_new ++ foreach name [array names repo_config] { ++ set repo_config_new($name) $repo_config($name) ++ } ++ load_config 1 ++ foreach name [array names repo_config] { ++ switch -- $name { ++ gui.diffcontext {continue} ++ } ++ set repo_config_new($name) $repo_config($name) ++ } ++ foreach name [array names global_config] { ++ set global_config_new($name) $global_config($name) ++ } ++ ++ set w .options_editor ++ toplevel $w ++ wm geometry $w "+[winfo rootx .]+[winfo rooty .]" ++ ++ label $w.header -text "Options" \ ++ -font font_uibold ++ pack $w.header -side top -fill x ++ ++ frame $w.buttons ++ button $w.buttons.restore -text {Restore Defaults} \ ++ -default normal \ ++ -command do_restore_defaults ++ pack $w.buttons.restore -side left ++ button $w.buttons.save -text Save \ ++ -default active \ ++ -command [list do_save_config $w] ++ pack $w.buttons.save -side right ++ button $w.buttons.cancel -text {Cancel} \ ++ -default normal \ ++ -command [list destroy $w] ++ pack $w.buttons.cancel -side right -padx 5 ++ pack $w.buttons -side bottom -fill x -pady 10 -padx 10 ++ ++ labelframe $w.repo -text "[reponame] Repository" ++ labelframe $w.global -text {Global (All Repositories)} ++ pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 ++ pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 ++ ++ set optid 0 ++ foreach option { ++ {t user.name {User Name}} ++ {t user.email {Email Address}} ++ ++ {b merge.summary {Summarize Merge Commits}} ++ {i-1..5 merge.verbosity {Merge Verbosity}} ++ ++ {b gui.trustmtime {Trust File Modification Timestamps}} ++ {i-1..99 gui.diffcontext {Number of Diff Context Lines}} ++ {t gui.newbranchtemplate {New Branch Name Template}} ++ } { ++ set type [lindex $option 0] ++ set name [lindex $option 1] ++ set text [lindex $option 2] ++ incr optid ++ foreach f {repo global} { ++ switch -glob -- $type { ++ b { ++ checkbutton $w.$f.$optid -text $text \ ++ -variable ${f}_config_new($name) \ ++ -onvalue true \ ++ -offvalue false ++ pack $w.$f.$optid -side top -anchor w ++ } ++ i-* { ++ regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max ++ frame $w.$f.$optid ++ label $w.$f.$optid.l -text "$text:" ++ pack $w.$f.$optid.l -side left -anchor w -fill x ++ spinbox $w.$f.$optid.v \ ++ -textvariable ${f}_config_new($name) \ ++ -from $min \ ++ -to $max \ ++ -increment 1 \ ++ -width [expr {1 + [string length $max]}] ++ bind $w.$f.$optid.v {%W selection range 0 end} ++ pack $w.$f.$optid.v -side right -anchor e -padx 5 ++ pack $w.$f.$optid -side top -anchor w -fill x ++ } ++ t { ++ frame $w.$f.$optid ++ label $w.$f.$optid.l -text "$text:" ++ entry $w.$f.$optid.v \ ++ -borderwidth 1 \ ++ -relief sunken \ ++ -width 20 \ ++ -textvariable ${f}_config_new($name) ++ pack $w.$f.$optid.l -side left -anchor w ++ pack $w.$f.$optid.v -side left -anchor w \ ++ -fill x -expand 1 \ ++ -padx 5 ++ pack $w.$f.$optid -side top -anchor w -fill x ++ } ++ } ++ } ++ } ++ ++ set all_fonts [lsort [font families]] ++ foreach option $font_descs { ++ set name [lindex $option 0] ++ set font [lindex $option 1] ++ set text [lindex $option 2] ++ ++ set global_config_new(gui.$font^^family) \ ++ [font configure $font -family] ++ set global_config_new(gui.$font^^size) \ ++ [font configure $font -size] ++ ++ frame $w.global.$name ++ label $w.global.$name.l -text "$text:" ++ pack $w.global.$name.l -side left -anchor w -fill x ++ eval tk_optionMenu $w.global.$name.family \ ++ global_config_new(gui.$font^^family) \ ++ $all_fonts ++ spinbox $w.global.$name.size \ ++ -textvariable global_config_new(gui.$font^^size) \ ++ -from 2 -to 80 -increment 1 \ ++ -width 3 ++ bind $w.global.$name.size {%W selection range 0 end} ++ pack $w.global.$name.size -side right -anchor e ++ pack $w.global.$name.family -side right -anchor e ++ pack $w.global.$name -side top -anchor w -fill x ++ } ++ ++ bind $w "grab $w; focus $w.buttons.save" ++ bind $w "destroy $w" ++ bind $w [list do_save_config $w] ++ wm title $w "[appname] ([reponame]): Options" ++ tkwait window $w ++} ++ ++proc do_restore_defaults {} { ++ global font_descs default_config repo_config ++ global repo_config_new global_config_new ++ ++ foreach name [array names default_config] { ++ set repo_config_new($name) $default_config($name) ++ set global_config_new($name) $default_config($name) ++ } ++ ++ foreach option $font_descs { ++ set name [lindex $option 0] ++ set repo_config(gui.$name) $default_config(gui.$name) ++ } ++ apply_config ++ ++ foreach option $font_descs { ++ set name [lindex $option 0] ++ set font [lindex $option 1] ++ set global_config_new(gui.$font^^family) \ ++ [font configure $font -family] ++ set global_config_new(gui.$font^^size) \ ++ [font configure $font -size] ++ } ++} ++ ++proc do_save_config {w} { ++ if {[catch {save_config} err]} { ++ error_popup "Failed to completely save options:\n\n$err" ++ } ++ reshow_diff ++ destroy $w ++} diff --cc git-gui/lib/remote.tcl index 000000000,000000000..99f353ed7 new file mode 100644 --- /dev/null +++ b/git-gui/lib/remote.tcl @@@ -1,0 -1,0 +1,159 @@@ ++# git-gui remote management ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc is_tracking_branch {name} { ++ global tracking_branches ++ ++ if {![catch {set info $tracking_branches($name)}]} { ++ return 1 ++ } ++ foreach t [array names tracking_branches] { ++ if {[string match {*/\*} $t] && [string match $t $name]} { ++ return 1 ++ } ++ } ++ return 0 ++} ++ ++proc all_tracking_branches {} { ++ global tracking_branches ++ ++ set all_trackings {} ++ set cmd {} ++ foreach name [array names tracking_branches] { ++ if {[regsub {/\*$} $name {} name]} { ++ lappend cmd $name ++ } else { ++ regsub ^refs/(heads|remotes)/ $name {} name ++ lappend all_trackings $name ++ } ++ } ++ ++ if {$cmd ne {}} { ++ set fd [open "| git for-each-ref --format=%(refname) $cmd" r] ++ while {[gets $fd name] > 0} { ++ regsub ^refs/(heads|remotes)/ $name {} name ++ lappend all_trackings $name ++ } ++ close $fd ++ } ++ ++ return [lsort -unique $all_trackings] ++} ++ ++proc load_all_remotes {} { ++ global repo_config ++ global all_remotes tracking_branches ++ ++ set all_remotes [list] ++ array unset tracking_branches ++ ++ set rm_dir [gitdir remotes] ++ if {[file isdirectory $rm_dir]} { ++ set all_remotes [glob \ ++ -types f \ ++ -tails \ ++ -nocomplain \ ++ -directory $rm_dir *] ++ ++ foreach name $all_remotes { ++ catch { ++ set fd [open [file join $rm_dir $name] r] ++ while {[gets $fd line] >= 0} { ++ if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \ ++ $line line src dst]} continue ++ if {![regexp ^refs/ $dst]} { ++ set dst "refs/heads/$dst" ++ } ++ set tracking_branches($dst) [list $name $src] ++ } ++ close $fd ++ } ++ } ++ } ++ ++ foreach line [array names repo_config remote.*.url] { ++ if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue ++ lappend all_remotes $name ++ ++ if {[catch {set fl $repo_config(remote.$name.fetch)}]} { ++ set fl {} ++ } ++ foreach line $fl { ++ if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue ++ if {![regexp ^refs/ $dst]} { ++ set dst "refs/heads/$dst" ++ } ++ set tracking_branches($dst) [list $name $src] ++ } ++ } ++ ++ set all_remotes [lsort -unique $all_remotes] ++} ++ ++proc populate_fetch_menu {} { ++ global all_remotes repo_config ++ ++ set m .mbar.fetch ++ foreach r $all_remotes { ++ set enable 0 ++ if {![catch {set a $repo_config(remote.$r.url)}]} { ++ if {![catch {set a $repo_config(remote.$r.fetch)}]} { ++ set enable 1 ++ } ++ } else { ++ catch { ++ set fd [open [gitdir remotes $r] r] ++ while {[gets $fd n] >= 0} { ++ if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { ++ set enable 1 ++ break ++ } ++ } ++ close $fd ++ } ++ } ++ ++ if {$enable} { ++ $m add command \ ++ -label "Fetch from $r..." \ ++ -command [list fetch_from $r] ++ } ++ } ++} ++ ++proc populate_push_menu {} { ++ global all_remotes repo_config ++ ++ set m .mbar.push ++ set fast_count 0 ++ foreach r $all_remotes { ++ set enable 0 ++ if {![catch {set a $repo_config(remote.$r.url)}]} { ++ if {![catch {set a $repo_config(remote.$r.push)}]} { ++ set enable 1 ++ } ++ } else { ++ catch { ++ set fd [open [gitdir remotes $r] r] ++ while {[gets $fd n] >= 0} { ++ if {[regexp {^Push:[ \t]*([^:]+):} $n]} { ++ set enable 1 ++ break ++ } ++ } ++ close $fd ++ } ++ } ++ ++ if {$enable} { ++ if {!$fast_count} { ++ $m add separator ++ } ++ $m add command \ ++ -label "Push to $r..." \ ++ -command [list push_to $r] ++ incr fast_count ++ } ++ } ++} diff --cc git-gui/lib/shortcut.tcl index 000000000,000000000..ebf72e445 new file mode 100644 --- /dev/null +++ b/git-gui/lib/shortcut.tcl @@@ -1,0 -1,0 +1,141 @@@ ++# git-gui desktop icon creators ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc do_windows_shortcut {} { ++ global argv0 ++ ++ set fn [tk_getSaveFile \ ++ -parent . \ ++ -title "[appname] ([reponame]): Create Desktop Icon" \ ++ -initialfile "Git [reponame].bat"] ++ if {$fn != {}} { ++ if {[catch { ++ set fd [open $fn w] ++ puts $fd "@ECHO Entering [reponame]" ++ puts $fd "@ECHO Starting git-gui... please wait..." ++ puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%" ++ puts $fd "@SET GIT_DIR=[file normalize [gitdir]]" ++ puts -nonewline $fd "@\"[info nameofexecutable]\"" ++ puts $fd " \"[file normalize $argv0]\"" ++ close $fd ++ } err]} { ++ error_popup "Cannot write script:\n\n$err" ++ } ++ } ++} ++ ++proc do_cygwin_shortcut {} { ++ global argv0 ++ ++ if {[catch { ++ set desktop [exec cygpath \ ++ --windows \ ++ --absolute \ ++ --long-name \ ++ --desktop] ++ }]} { ++ set desktop . ++ } ++ set fn [tk_getSaveFile \ ++ -parent . \ ++ -title "[appname] ([reponame]): Create Desktop Icon" \ ++ -initialdir $desktop \ ++ -initialfile "Git [reponame].bat"] ++ if {$fn != {}} { ++ if {[catch { ++ set fd [open $fn w] ++ set sh [exec cygpath \ ++ --windows \ ++ --absolute \ ++ /bin/sh] ++ set me [exec cygpath \ ++ --unix \ ++ --absolute \ ++ $argv0] ++ set gd [exec cygpath \ ++ --unix \ ++ --absolute \ ++ [gitdir]] ++ set gw [exec cygpath \ ++ --windows \ ++ --absolute \ ++ [file dirname [gitdir]]] ++ regsub -all ' $me "'\\''" me ++ regsub -all ' $gd "'\\''" gd ++ puts $fd "@ECHO Entering $gw" ++ puts $fd "@ECHO Starting git-gui... please wait..." ++ puts -nonewline $fd "@\"$sh\" --login -c \"" ++ puts -nonewline $fd "GIT_DIR='$gd'" ++ puts -nonewline $fd " '$me'" ++ puts $fd "&\"" ++ close $fd ++ } err]} { ++ error_popup "Cannot write script:\n\n$err" ++ } ++ } ++} ++ ++proc do_macosx_app {} { ++ global argv0 env ++ ++ set fn [tk_getSaveFile \ ++ -parent . \ ++ -title "[appname] ([reponame]): Create Desktop Icon" \ ++ -initialdir [file join $env(HOME) Desktop] \ ++ -initialfile "Git [reponame].app"] ++ if {$fn != {}} { ++ if {[catch { ++ set Contents [file join $fn Contents] ++ set MacOS [file join $Contents MacOS] ++ set exe [file join $MacOS git-gui] ++ ++ file mkdir $MacOS ++ ++ set fd [open [file join $Contents Info.plist] w] ++ puts $fd { ++ ++ ++ ++ CFBundleDevelopmentRegion ++ English ++ CFBundleExecutable ++ git-gui ++ CFBundleIdentifier ++ org.spearce.git-gui ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1.0 ++ NSPrincipalClass ++ NSApplication ++ ++} ++ close $fd ++ ++ set fd [open $exe w] ++ set gd [file normalize [gitdir]] ++ set ep [file normalize [gitexec]] ++ regsub -all ' $gd "'\\''" gd ++ regsub -all ' $ep "'\\''" ep ++ puts $fd "#!/bin/sh" ++ foreach name [array names env] { ++ if {[string match GIT_* $name]} { ++ regsub -all ' $env($name) "'\\''" v ++ puts $fd "export $name='$v'" ++ } ++ } ++ puts $fd "export PATH='$ep':\$PATH" ++ puts $fd "export GIT_DIR='$gd'" ++ puts $fd "exec [file normalize $argv0]" ++ close $fd ++ ++ file attributes $exe -permissions u+x,g+x,o+x ++ } err]} { ++ error_popup "Cannot write icon:\n\n$err" ++ } ++ } ++} diff --cc git-gui/lib/transport.tcl index 000000000,000000000..c0e7d20fc new file mode 100644 --- /dev/null +++ b/git-gui/lib/transport.tcl @@@ -1,0 -1,0 +1,164 @@@ ++# git-gui transport (fetch/push) support ++# Copyright (C) 2006, 2007 Shawn Pearce ++ ++proc fetch_from {remote} { ++ set w [console::new \ ++ "fetch $remote" \ ++ "Fetching new changes from $remote"] ++ set cmd [list git fetch] ++ lappend cmd $remote ++ console::exec $w $cmd ++} ++ ++proc push_to {remote} { ++ set w [console::new \ ++ "push $remote" \ ++ "Pushing changes to $remote"] ++ set cmd [list git push] ++ lappend cmd -v ++ lappend cmd $remote ++ console::exec $w $cmd ++} ++ ++proc start_push_anywhere_action {w} { ++ global push_urltype push_remote push_url push_thin push_tags ++ ++ set r_url {} ++ switch -- $push_urltype { ++ remote {set r_url $push_remote} ++ url {set r_url $push_url} ++ } ++ if {$r_url eq {}} return ++ ++ set cmd [list git push] ++ lappend cmd -v ++ if {$push_thin} { ++ lappend cmd --thin ++ } ++ if {$push_tags} { ++ lappend cmd --tags ++ } ++ lappend cmd $r_url ++ set cnt 0 ++ foreach i [$w.source.l curselection] { ++ set b [$w.source.l get $i] ++ lappend cmd "refs/heads/$b:refs/heads/$b" ++ incr cnt ++ } ++ if {$cnt == 0} { ++ return ++ } elseif {$cnt == 1} { ++ set unit branch ++ } else { ++ set unit branches ++ } ++ ++ set cons [console::new \ ++ "push $r_url" \ ++ "Pushing $cnt $unit to $r_url"] ++ console::exec $cons $cmd ++ destroy $w ++} ++ ++trace add variable push_remote write \ ++ [list radio_selector push_urltype remote] ++ ++proc do_push_anywhere {} { ++ global all_heads all_remotes current_branch ++ global push_urltype push_remote push_url push_thin push_tags ++ ++ set w .push_setup ++ toplevel $w ++ wm geometry $w "+[winfo rootx .]+[winfo rooty .]" ++ ++ label $w.header -text {Push Branches} -font font_uibold ++ pack $w.header -side top -fill x ++ ++ frame $w.buttons ++ button $w.buttons.create -text Push \ ++ -default active \ ++ -command [list start_push_anywhere_action $w] ++ pack $w.buttons.create -side right ++ button $w.buttons.cancel -text {Cancel} \ ++ -default normal \ ++ -command [list destroy $w] ++ pack $w.buttons.cancel -side right -padx 5 ++ pack $w.buttons -side bottom -fill x -pady 10 -padx 10 ++ ++ labelframe $w.source -text {Source Branches} ++ listbox $w.source.l \ ++ -height 10 \ ++ -width 70 \ ++ -selectmode extended \ ++ -yscrollcommand [list $w.source.sby set] ++ foreach h $all_heads { ++ $w.source.l insert end $h ++ if {$h eq $current_branch} { ++ $w.source.l select set end ++ } ++ } ++ scrollbar $w.source.sby -command [list $w.source.l yview] ++ pack $w.source.sby -side right -fill y ++ pack $w.source.l -side left -fill both -expand 1 ++ pack $w.source -fill both -expand 1 -pady 5 -padx 5 ++ ++ labelframe $w.dest -text {Destination Repository} ++ if {$all_remotes ne {}} { ++ radiobutton $w.dest.remote_r \ ++ -text {Remote:} \ ++ -value remote \ ++ -variable push_urltype ++ eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes ++ grid $w.dest.remote_r $w.dest.remote_m -sticky w ++ if {[lsearch -sorted -exact $all_remotes origin] != -1} { ++ set push_remote origin ++ } else { ++ set push_remote [lindex $all_remotes 0] ++ } ++ set push_urltype remote ++ } else { ++ set push_urltype url ++ } ++ radiobutton $w.dest.url_r \ ++ -text {Arbitrary URL:} \ ++ -value url \ ++ -variable push_urltype ++ entry $w.dest.url_t \ ++ -borderwidth 1 \ ++ -relief sunken \ ++ -width 50 \ ++ -textvariable push_url \ ++ -validate key \ ++ -validatecommand { ++ if {%d == 1 && [regexp {\s} %S]} {return 0} ++ if {%d == 1 && [string length %S] > 0} { ++ set push_urltype url ++ } ++ return 1 ++ } ++ grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5} ++ grid columnconfigure $w.dest 1 -weight 1 ++ pack $w.dest -anchor nw -fill x -pady 5 -padx 5 ++ ++ labelframe $w.options -text {Transfer Options} ++ checkbutton $w.options.thin \ ++ -text {Use thin pack (for slow network connections)} \ ++ -variable push_thin ++ grid $w.options.thin -columnspan 2 -sticky w ++ checkbutton $w.options.tags \ ++ -text {Include tags} \ ++ -variable push_tags ++ grid $w.options.tags -columnspan 2 -sticky w ++ grid columnconfigure $w.options 1 -weight 1 ++ pack $w.options -anchor nw -fill x -pady 5 -padx 5 ++ ++ set push_url {} ++ set push_thin 0 ++ set push_tags 0 ++ ++ bind $w "grab $w; focus $w.buttons.create" ++ bind $w "destroy $w" ++ bind $w [list start_push_anywhere_action $w] ++ wm title $w "[appname] ([reponame]): Push" ++ tkwait window $w ++}