doing when they write "Signed-off-by" line.
But the patch submission requirements are a lot more relaxed
-here, because the core GIT is thousand times smaller ;-). So
-here is only the relevant bits.
+here on the technical/contents front, because the core GIT is
+thousand times smaller ;-). So here is only the relevant bits.
(1) Make separate commits for logically separate changes.
Describe the technical detail of the change(s).
-If your description starts to get long, that's a sign that you
+If your description starts to get too long, that's a sign that you
probably need to split up your commit to finer grained pieces.
+Oh, another thing. I am picky about whitespaces. Make sure your
+changes do not trigger errors with the sample pre-commit hook shipped
+in templates/hooks--pre-commit.
-(2) Generate your patch using git/cogito out of your commits.
-git diff tools generate unidiff which is the preferred format.
+(2) Generate your patch using git tools out of your commits.
+
+git based diff tools (git, Cogito, and StGIT included) generate
+unidiff which is the preferred format.
+
You do not have to be afraid to use -M option to "git diff" or
"git format-patch", if your patch involves file renames. The
receiving end can handle them just fine.
which do not belong in a patch submission. Make sure to review
your patch after generating it, to ensure accuracy. Before
sending out, please make sure it cleanly applies to the "master"
-branch head.
+branch head. If you are preparing a work based on "next" branch,
+that is fine, but please mark it as such.
(3) Sending your patches.
-People on the git mailing list needs to be able to read and
+People on the git mailing list need to be able to read and
comment on the changes you are submitting. It is important for
a developer to be able to "quote" your changes, using standard
e-mail tools, so that they may comment on specific portions of
-your code. For this reason, all patches should be submitting
-e-mail "inline". WARNING: Be wary of your MUAs word-wrap
-corrupting your patch. Do not cut-n-paste your patch.
+your code. For this reason, all patches should be submited
+"inline". WARNING: Be wary of your MUAs word-wrap
+corrupting your patch. Do not cut-n-paste your patch; you can
+lose tabs that way if you are not careful.
-It is common convention to prefix your subject line with
+It is a common convention to prefix your subject line with
[PATCH]. This lets people easily distinguish patches from other
e-mail discussions.
SYNOPSIS
--------
-'git-ls-files' [-z] [-t]
+[verse]
+'git-ls-files' [-z] [-t] [-v]
(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\*
(-[c|d|o|i|s|u|k|m])\*
[-x <pattern>|--exclude=<pattern>]
K:: to be killed
?:: other
+-v::
+ Similar to `-t`, but use lowercase letters for files
+ that are marked as 'always matching index'.
+
--full-name::
When run from a subdirectory, the command usually
outputs paths relative to the current directory. This
path of the top-level directory relative to the current
directory (typically a sequence of "../", or an empty string).
+--git-dir::
+ Show `$GIT_DIR` if defined else show the path to the .git directory.
+
+--short, short=number::
+ Instead of outputting the full SHA1 values of object names try to
+ abbriviate them to a shorter unique name. When no length is specified
+ 7 is used. The minimum length is 4.
+
--since=datestring, --after=datestring::
Parses the date string, and outputs corresponding
--max-age= parameter for git-rev-list command.
SYNOPSIS
--------
+[verse]
'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <name> [<head>]
+'git-tag' -l [<pattern>]
DESCRIPTION
-----------
-Adds a 'tag' reference in .git/refs/tags/
+Adds a 'tag' reference in `.git/refs/tags/`
Unless `-f` is given, the tag must not yet exist in
`.git/refs/tags/` directory.
`-d <tag>` deletes the tag.
+`-l <pattern>` lists tags that match the given pattern (or all
+if no pattern is given).
+
OPTIONS
-------
-a::
-d::
Delete an existing tag with the given name
+-l <pattern>::
+ List tags that match the given pattern (or all if no pattern is given).
+
-m <msg>::
Use the given tag message (instead of prompting)
SYNOPSIS
--------
+[verse]
'git-update-index'
[--add] [--remove | --force-remove] [--replace]
[--refresh [-q] [--unmerged] [--ignore-missing]]
[--cacheinfo <mode> <object> <file>]\*
[--chmod=(+|-)x]
+ [--assume-unchanged | --no-assume-unchanged]
+ [--really-refresh]
[--info-only] [--index-info]
[-z] [--stdin]
[--verbose]
--chmod=(+|-)x::
Set the execute permissions on the updated files.
+--assume-unchanged, --no-assume-unchanged::
+ When these flags are specified, the object name recorded
+ for the paths are not updated. Instead, these options
+ sets and unsets the "assume unchanged" bit for the
+ paths. When the "assume unchanged" bit is on, git stops
+ checking the working tree files for possible
+ modifications, so you need to manually unset the bit to
+ tell git when you change the working tree file. This is
+ sometimes helpful when working with a big project on a
+ filesystem that has very slow lstat(2) system call
+ (e.g. cifs).
+
--info-only::
Do not create objects in the object database for all
<file> arguments that follow this flag; just insert
------------
+Using "assume unchanged" bit
+----------------------------
+
+Many operations in git depend on your filesystem to have an
+efficient `lstat(2)` implementation, so that `st_mtime`
+information for working tree files can be cheaply checked to see
+if the file contents have changed from the version recorded in
+the index file. Unfortunately, some filesystems have
+inefficient `lstat(2)`. If your filesystem is one of them, you
+can set "assume unchanged" bit to paths you have not changed to
+cause git not to do this check. Note that setting this bit on a
+path does not mean git will check the contents of the file to
+see if it has changed -- it makes git to omit any checking and
+assume it has *not* changed. When you make changes to working
+tree files, you have to explicitly tell git about it by dropping
+"assume unchanged" bit, either before or after you modify them.
+
+In order to set "assume unchanged" bit, use `--assume-unchanged`
+option. To unset, use `--no-assume-unchanged`.
+
+The command looks at `core.ignorestat` configuration variable. When
+this is true, paths updated with `git-update-index paths...` and
+paths updated with other git commands that update both index and
+working tree (e.g. `git-apply --index`, `git-checkout-index -u`,
+and `git-read-tree -u`) are automatically marked as "assume
+unchanged". Note that "assume unchanged" bit is *not* set if
+`git-update-index --refresh` finds the working tree file matches
+the index (use `git-update-index --really-refresh` if you want
+to mark them as "assume unchanged").
+
+
Examples
--------
To update and refresh only the files already checked out:
$ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
----------------
+On an inefficient filesystem with `core.ignorestat` set:
+
+------------
+$ git update-index --really-refresh <1>
+$ git update-index --no-assume-unchanged foo.c <2>
+$ git diff --name-only <3>
+$ edit foo.c
+$ git diff --name-only <4>
+M foo.c
+$ git update-index foo.c <5>
+$ git diff --name-only <6>
+$ edit foo.c
+$ git diff --name-only <7>
+$ git update-index --no-assume-unchanged foo.c <8>
+$ git diff --name-only <9>
+M foo.c
+
+<1> forces lstat(2) to set "assume unchanged" bits for paths
+ that match index.
+<2> mark the path to be edited.
+<3> this does lstat(2) and finds index matches the path.
+<4> this does lstat(2) and finds index does not match the path.
+<5> registering the new version to index sets "assume unchanged" bit.
+<6> and it is assumed unchanged.
+<7> even after you edit it.
+<8> you can tell about the change after the fact.
+<9> now it checks with lstat(2) and finds it has been changed.
+------------
+
Configuration
-------------
executable bit. On such an unfortunate filesystem, you may
need to use `git-update-index --chmod=`.
+The command looks at `core.ignorestat` configuration variable. See
+'Using "assume unchanged" bit' section above.
+
See Also
--------
#
# Define NO_ICONV if your libc does not properly support iconv.
#
+# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
+# a missing newline at the end of the file.
+#
# Define COLLISION_CHECK below if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
# sufficient guarantee that no collisions between objects will ever happen.
git-upload-pack$X git-verify-pack$X git-write-tree$X \
git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
- git-describe$X
+ git-describe$X git-merge-tree$X
# what 'all' will build and 'install' will install, in gitexecdir
ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
NO_STRCASESTR=YesPlease
NEEDS_LIBICONV=YesPlease
endif
+ifeq ($(uname_S),IRIX64)
+ NO_IPV6=YesPlease
+ NO_SETENV=YesPlease
+ NO_STRCASESTR=YesPlease
+ NO_SOCKADDR_STORAGE=YesPlease
+ SHELL_PATH=/usr/gnu/bin/bash
+ ALL_CFLAGS += -DPATH_MAX=1024
+ # for now, build 32-bit version
+ ALL_LDFLAGS += -L/usr/lib32
+endif
ifneq (,$(findstring arm,$(uname_M)))
ARM_SHA1 = YesPlease
endif
endif
endif
endif
+ifdef NO_ACCURATE_DIFF
+ ALL_CFLAGS += -DNO_ACCURATE_DIFF
+endif
ALL_CFLAGS += -DSHA1_HEADER=$(call shellquote,$(SHA1_HEADER)) $(COMPAT_CFLAGS)
LIB_OBJS += $(COMPAT_OBJS)
size -= len;
}
+#ifdef NO_ACCURATE_DIFF
+ if (oldsize > 0 && old[oldsize - 1] == '\n' &&
+ newsize > 0 && new[newsize - 1] == '\n') {
+ oldsize--;
+ newsize--;
+ }
+#endif
+
offset = find_offset(buf, desc->size, old, oldsize, frag->newpos);
if (offset >= 0) {
int diff = newsize - oldsize;
return -1;
}
- changed = ce_match_stat(active_cache[pos], &st);
+ changed = ce_match_stat(active_cache[pos], &st, 1);
if (changed)
return error("%s: does not match index",
old_name);
#define CE_NAMEMASK (0x0fff)
#define CE_STAGEMASK (0x3000)
#define CE_UPDATE (0x4000)
+#define CE_VALID (0x8000)
#define CE_STAGESHIFT 12
#define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT))
extern int remove_cache_entry_at(int pos);
extern int remove_file_from_cache(const char *path);
extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
-extern int ce_match_stat(struct cache_entry *ce, struct stat *st);
-extern int ce_modified(struct cache_entry *ce, struct stat *st);
+extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int);
+extern int ce_modified(struct cache_entry *ce, struct stat *st, int);
extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type);
extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
extern void rollback_index_file(struct cache_file *);
extern int trust_executable_bit;
+extern int assume_unchanged;
extern int only_use_symrefs;
extern int diff_rename_limit_default;
extern int shared_repository;
int all = 0;
prefix = setup_git_directory();
+ git_config(git_default_config);
prefix_length = prefix ? strlen(prefix) : 0;
if (read_cache() < 0) {
/*
* Performs an in-place topological sort on the list supplied.
*/
-void sort_in_topological_order(struct commit_list ** list)
+void sort_in_topological_order(struct commit_list ** list, int lifo)
{
struct commit_list * next = *list;
struct commit_list * work = NULL, **insert;
}
next=next->next;
}
+
/* process the list in topological order */
+ if (!lifo)
+ sort_by_date(&work);
while (work) {
struct commit * work_item = pop_commit(&work);
struct sort_node * work_node = (struct sort_node *)work_item->object.util;
* guaranteeing topological order.
*/
pn->indegree--;
- if (!pn->indegree)
- commit_list_insert(parent, &work);
+ if (!pn->indegree) {
+ if (!lifo)
+ insert_by_date(parent, &work);
+ else
+ commit_list_insert(parent, &work);
+ }
}
parents=parents->next;
}
* Post-conditions:
* invariant of resulting list is:
* a reachable from b => ord(b) < ord(a)
+ * in addition, when lifo == 0, commits on parallel tracks are
+ * sorted in the dates order.
*/
-void sort_in_topological_order(struct commit_list ** list);
+void sort_in_topological_order(struct commit_list ** list, int lifo);
#endif /* COMMIT_H */
return 0;
}
+ if (!strcmp(var, "core.ignorestat")) {
+ assume_unchanged = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "core.symrefsonly")) {
only_use_symrefs = git_config_bool(var, value);
return 0;
--- /dev/null
+Contributed Software
+
+Although these pieces are available as part of the official git
+source tree, they are in somewhat different status. The
+intention is to keep interesting tools around git here, maybe
+even experimental ones, to give users an easier access to them,
+and to give tools wider exposure, so that they can be improved
+faster.
+
+I am not expecting to touch these myself that much. As far as
+my day-to-day operation is concerned, these subdirectories are
+owned by their respective primary authors. I am willing to help
+if users of these components and the contrib/ subtree "owners"
+have technical/design issues to resolve, but the initiative to
+fix and/or enhance things _must_ be on the side of the subtree
+owners. IOW, I won't be actively looking for bugs and rooms for
+enhancements in them as the git maintainer -- I may only do so
+just as one of the users when I want to scratch my own itch. If
+you have patches to things in contrib/ area, the patch should be
+first sent to the primary author, and then the primary author
+should ack and forward it to me (git pull request is nicer).
+This is the same way as how I have been treating gitk, and to a
+lesser degree various foreign SCM interfaces, so you know the
+drill.
+
+I expect that things that start their life in the contrib/ area
+to graduate out of contrib/ once they mature, either by becoming
+projects on their own, or moving to the toplevel directory. On
+the other hand, I expect I'll be proposing removal of disused
+and inactive ones from time to time.
+
+If you have new things to add to this area, please first propose
+it on the git mailing list, and after a list discussion proves
+there are some general interests (it does not have to be a
+list-wide consensus for a tool targeted to a relatively narrow
+audience -- for example I do not work with projects whose
+upstream is svn, so I have no use for git-svn myself, but it is
+of general interest for people who need to interoperate with SVN
+repositories in a way git-svn works better than git-svnimport),
+submit a patch to create a subdirectory of contrib/ and put your
+stuff there.
+
+-jc
+
--- /dev/null
+gitview(1)
+==========
+
+NAME
+----
+gitview - A GTK based repository browser for git
+
+SYNOPSIS
+--------
+'gitview' [options] [args]
+
+DESCRIPTION
+---------
+
+Dependencies
+
+* Python 2.4
+* PyGTK 2.8 or later
+* PyCairo 1.0 or later
+
+OPTIONS
+------
+ --without-diff
+ If the user doesn't want to list the commit diffs in the main window. This may speed up the repository browsing.
+
+ <args>
+ All the valid option for git-rev-list(1)
+
+EXAMPLES
+------
+ gitview v2.6.12.. include/scsi drivers/scsi
+ Show as the changes since version v2.6.12 that changed any file in the include/scsi
+ or drivers/scsi subdirectories
+
+ gitk --since=2.weeks.ago
+ Show the changes during the last two weeks
+
+
show_file('-', ce);
continue;
}
- changed = ce_match_stat(ce, &st);
+ changed = ce_match_stat(ce, &st, 0);
if (!changed && !diff_options.find_copies_harder)
continue;
oldmode = ntohl(ce->ce_mode);
}
return -1;
}
- changed = ce_match_stat(ce, &st);
+ changed = ce_match_stat(ce, &st, 0);
if (changed) {
mode = create_ce_mode(st.st_mode);
if (!trust_executable_bit &&
ce = active_cache[pos];
if ((lstat(name, &st) < 0) ||
!S_ISREG(st.st_mode) || /* careful! */
- ce_match_stat(ce, &st) ||
+ ce_match_stat(ce, &st, 0) ||
memcmp(sha1, ce->sha1, 20))
return 0;
/* we return 1 only when we can stat, it is a regular file,
strcpy(path + len, ce->name);
if (!lstat(path, &st)) {
- unsigned changed = ce_match_stat(ce, &st);
+ unsigned changed = ce_match_stat(ce, &st, 1);
if (!changed)
return 0;
if (!state->force) {
char git_default_email[MAX_GITNAME];
char git_default_name[MAX_GITNAME];
int trust_executable_bit = 1;
+int assume_unchanged = 0;
int only_use_symrefs = 0;
int repository_format_version = 0;
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
[ -e "$dir" ] && echo "$dir already exists." && usage
mkdir -p "$dir" &&
D=$(cd "$dir" && pwd) &&
+trap 'err=$?; rm -r $D; exit $err' exit
case "$bare" in
yes) GIT_DIR="$D" ;;
*) GIT_DIR="$D/.git" ;;
fi &&
rm -f "$GIT_DIR/objects/sample" &&
cd "$repo" &&
- find objects -depth -print | cpio -puamd$l "$GIT_DIR/" || exit 1
+ find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1
;;
yes)
mkdir -p "$GIT_DIR/objects/info"
git checkout
esac
fi
+
+trap - exit
+
# Copyright (c) 2005 Junio C Hamano.
#
-USAGE='<upstream> [<head>]'
+USAGE='[--onto <newbase>] <upstream> [<branch>]'
+LONG_USAGE='If <branch> is specified, switch to that branch first. Then,
+extract commits in the current branch that are not in <upstream>,
+and reconstruct the current on top of <upstream>, discarding the original
+development history. If --onto <newbase> is specified, the history is
+reconstructed on top of <newbase>, instead of <upstream>. For example,
+while on "topic" branch:
+
+ A---B---C topic
+ /
+ D---E---F---G master
+
+ $ '"$0"' --onto master~1 master topic
+
+would rewrite the history to look like this:
+
+
+ A'\''--B'\''--C'\'' topic
+ /
+ D---E---F---G master
+'
+
. git-sh-setup
-case $# in 1|2) ;; *) usage ;; esac
+unset newbase
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ --onto)
+ test 2 -le "$#" || usage
+ newbase="$2"
+ shift
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
# Make sure we do not have .dotest
if mkdir .dotest
;;
esac
-# The other head is given. Make sure it is valid.
-other=$(git-rev-parse --verify "$1^0") || usage
-
-# Make sure the branch to rebase is valid.
-head=$(git-rev-parse --verify "${2-HEAD}^0") || exit
+# The upstream head must be given. Make sure it is valid.
+upstream_name="$1"
+upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+ die "invalid upsteram $upstream_name"
# If a hook exists, give it a chance to interrupt
if test -x "$GIT_DIR/hooks/pre-rebase"
# If the branch to rebase is given, first switch to it.
case "$#" in
2)
+ branch_name="$2"
git-checkout "$2" || usage
+ ;;
+*)
+ branch_name=`git symbolic-ref HEAD` || die "No current branch"
+ branch_name=`expr "$branch_name" : 'refs/heads/\(.*\)'`
+ ;;
esac
+branch=$(git-rev-parse --verify "${branch_name}^0") || exit
-mb=$(git-merge-base "$other" "$head")
+# Make sure the branch to rebase onto is valid.
+onto_name=${newbase-"$upstream_name"}
+onto=$(git-rev-parse --verify "${onto_name}^0") || exit
-# Check if we are already based on $other.
-if test "$mb" = "$other"
+# Now we are rebasing commits $upstream..$branch on top of $onto
+
+# Check if we are already based on $onto, but this should be
+# done only when upstream and onto are the same.
+if test "$upstream" = "onto"
then
- echo >&2 "Current branch `git-symbolic-ref HEAD` is up to date."
- exit 0
+ mb=$(git-merge-base "$onto" "$branch")
+ if test "$mb" = "$onto"
+ then
+ echo >&2 "Current branch $branch_name is up to date."
+ exit 0
+ fi
fi
-# Rewind the head to "$other"
-git-reset --hard "$other"
+# Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
+git-reset --hard "$onto"
-# If the $other is a proper descendant of the tip of the branch, then
+# If the $onto is a proper descendant of the tip of the branch, then
# we just fast forwarded.
-if test "$mb" = "$head"
+if test "$mb" = "$onto"
then
- echo >&2 "Fast-forwarded $head to $other."
+ echo >&2 "Fast-forwarded $branch to $newbase."
exit 0
fi
-git-format-patch -k --stdout --full-index "$other" ORIG_HEAD |
+git-format-patch -k --stdout --full-index "$upstream" ORIG_HEAD |
git am --binary -3 -k
#!/bin/sh
# Copyright (c) 2005 Linus Torvalds
-USAGE='[-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <tagname> [<head>]'
+USAGE='-l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <tagname> [<head>]'
SUBDIRECTORY_OK='Yes'
. git-sh-setup
force=
message=
username=
+list=
while case "$#" in 0) break ;; esac
do
case "$1" in
-f)
force=1
;;
+ -l)
+ cd "$GIT_DIR/refs" &&
+ case "$#" in
+ 1)
+ find tags -type f -print ;;
+ *)
+ shift
+ find tags -type f -print | grep "$@" ;;
+ esac
+ exit $?
+ ;;
-m)
annotate=1
shift
static int show_modified = 0;
static int show_killed = 0;
static int show_other_directories = 0;
+static int show_valid_bit = 0;
static int line_terminator = '\n';
static int prefix_len = 0, prefix_offset = 0;
if (pathspec && !match(pathspec, ps_matched, ce->name, len))
return;
+ if (tag && *tag && show_valid_bit &&
+ (ce->ce_flags & htons(CE_VALID))) {
+ static char alttag[4];
+ memcpy(alttag, tag, 3);
+ if (isalpha(tag[0]))
+ alttag[0] = tolower(tag[0]);
+ else if (tag[0] == '?')
+ alttag[0] = '!';
+ else {
+ alttag[0] = 'v';
+ alttag[1] = tag[0];
+ alttag[2] = ' ';
+ alttag[3] = 0;
+ }
+ tag = alttag;
+ }
+
if (!show_stage) {
fputs(tag, stdout);
write_name_quoted("", 0, ce->name + offset,
err = lstat(ce->name, &st);
if (show_deleted && err)
show_ce_entry(tag_removed, ce);
- if (show_modified && ce_modified(ce, &st))
+ if (show_modified && ce_modified(ce, &st, 0))
show_ce_entry(tag_modified, ce);
}
}
}
static const char ls_files_usage[] =
- "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
+ "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
"[ --exclude-per-directory=<filename> ] [--full-name] [--] [<file>]*";
line_terminator = 0;
continue;
}
- if (!strcmp(arg, "-t")) {
+ if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
tag_cached = "H ";
tag_unmerged = "M ";
tag_removed = "R ";
tag_modified = "C ";
tag_other = "? ";
tag_killed = "K ";
+ if (arg[1] == 'v')
+ show_valid_bit = 1;
continue;
}
if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
--- /dev/null
+#include "cache.h"
+#include "diff.h"
+
+static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
+static int resolve_directories = 1;
+
+static void merge_trees(struct tree_desc t[3], const char *base);
+
+static void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1)
+{
+ unsigned long size = 0;
+ void *buf = NULL;
+
+ if (sha1) {
+ buf = read_object_with_reference(sha1, "tree", &size, NULL);
+ if (!buf)
+ die("unable to read tree %s", sha1_to_hex(sha1));
+ }
+ desc->size = size;
+ desc->buf = buf;
+ return buf;
+}
+
+struct name_entry {
+ const unsigned char *sha1;
+ const char *path;
+ unsigned int mode;
+ int pathlen;
+};
+
+static void entry_clear(struct name_entry *a)
+{
+ memset(a, 0, sizeof(*a));
+}
+
+static int entry_compare(struct name_entry *a, struct name_entry *b)
+{
+ return base_name_compare(
+ a->path, a->pathlen, a->mode,
+ b->path, b->pathlen, b->mode);
+}
+
+static void entry_extract(struct tree_desc *t, struct name_entry *a)
+{
+ a->sha1 = tree_entry_extract(t, &a->path, &a->mode);
+ a->pathlen = strlen(a->path);
+}
+
+/* An empty entry never compares same, not even to another empty entry */
+static int same_entry(struct name_entry *a, struct name_entry *b)
+{
+ return a->sha1 &&
+ b->sha1 &&
+ !memcmp(a->sha1, b->sha1, 20) &&
+ a->mode == b->mode;
+}
+
+static const char *sha1_to_hex_zero(const unsigned char *sha1)
+{
+ if (sha1)
+ return sha1_to_hex(sha1);
+ return "0000000000000000000000000000000000000000";
+}
+
+static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
+{
+ char branch1_sha1[50];
+
+ /* If it's already branch1, don't bother showing it */
+ if (!branch1)
+ return;
+ memcpy(branch1_sha1, sha1_to_hex_zero(branch1->sha1), 41);
+
+ printf("0 %06o->%06o %s->%s %s%s\n",
+ branch1->mode, result->mode,
+ branch1_sha1, sha1_to_hex_zero(result->sha1),
+ base, result->path);
+}
+
+static int unresolved_directory(const char *base, struct name_entry n[3])
+{
+ int baselen;
+ char *newbase;
+ struct name_entry *p;
+ struct tree_desc t[3];
+ void *buf0, *buf1, *buf2;
+
+ if (!resolve_directories)
+ return 0;
+ p = n;
+ if (!p->mode) {
+ p++;
+ if (!p->mode)
+ p++;
+ }
+ if (!S_ISDIR(p->mode))
+ return 0;
+ baselen = strlen(base);
+ newbase = xmalloc(baselen + p->pathlen + 2);
+ memcpy(newbase, base, baselen);
+ memcpy(newbase + baselen, p->path, p->pathlen);
+ memcpy(newbase + baselen + p->pathlen, "/", 2);
+
+ buf0 = fill_tree_descriptor(t+0, n[0].sha1);
+ buf1 = fill_tree_descriptor(t+1, n[1].sha1);
+ buf2 = fill_tree_descriptor(t+2, n[2].sha1);
+ merge_trees(t, newbase);
+
+ free(buf0);
+ free(buf1);
+ free(buf2);
+ free(newbase);
+ return 1;
+}
+
+static void unresolved(const char *base, struct name_entry n[3])
+{
+ if (unresolved_directory(base, n))
+ return;
+ if (n[0].sha1)
+ printf("1 %06o %s %s%s\n", n[0].mode, sha1_to_hex(n[0].sha1), base, n[0].path);
+ if (n[1].sha1)
+ printf("2 %06o %s %s%s\n", n[1].mode, sha1_to_hex(n[1].sha1), base, n[1].path);
+ if (n[2].sha1)
+ printf("3 %06o %s %s%s\n", n[2].mode, sha1_to_hex(n[2].sha1), base, n[2].path);
+}
+
+typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, const char *base);
+
+static void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback)
+{
+ struct name_entry *entry = xmalloc(n*sizeof(*entry));
+
+ for (;;) {
+ struct name_entry entry[3];
+ unsigned long mask = 0;
+ int i, last;
+
+ last = -1;
+ for (i = 0; i < n; i++) {
+ if (!t[i].size)
+ continue;
+ entry_extract(t+i, entry+i);
+ if (last >= 0) {
+ int cmp = entry_compare(entry+i, entry+last);
+
+ /*
+ * Is the new name bigger than the old one?
+ * Ignore it
+ */
+ if (cmp > 0)
+ continue;
+ /*
+ * Is the new name smaller than the old one?
+ * Ignore all old ones
+ */
+ if (cmp < 0)
+ mask = 0;
+ }
+ mask |= 1ul << i;
+ last = i;
+ }
+ if (!mask)
+ break;
+
+ /*
+ * Update the tree entries we've walked, and clear
+ * all the unused name-entries.
+ */
+ for (i = 0; i < n; i++) {
+ if (mask & (1ul << i)) {
+ update_tree_entry(t+i);
+ continue;
+ }
+ entry_clear(entry + i);
+ }
+ callback(n, mask, entry, base);
+ }
+ free(entry);
+}
+
+/*
+ * Merge two trees together (t[1] and t[2]), using a common base (t[0])
+ * as the origin.
+ *
+ * This walks the (sorted) trees in lock-step, checking every possible
+ * name. Note that directories automatically sort differently from other
+ * files (see "base_name_compare"), so you'll never see file/directory
+ * conflicts, because they won't ever compare the same.
+ *
+ * IOW, if a directory changes to a filename, it will automatically be
+ * seen as the directory going away, and the filename being created.
+ *
+ * Think of this as a three-way diff.
+ *
+ * The output will be either:
+ * - successful merge
+ * "0 mode sha1 filename"
+ * NOTE NOTE NOTE! FIXME! We really really need to walk the index
+ * in parallel with this too!
+ *
+ * - conflict:
+ * "1 mode sha1 filename"
+ * "2 mode sha1 filename"
+ * "3 mode sha1 filename"
+ * where not all of the 1/2/3 lines may exist, of course.
+ *
+ * The successful merge rules are the same as for the three-way merge
+ * in git-read-tree.
+ */
+static void threeway_callback(int n, unsigned long mask, struct name_entry *entry, const char *base)
+{
+ /* Same in both? */
+ if (same_entry(entry+1, entry+2)) {
+ if (entry[0].sha1) {
+ resolve(base, NULL, entry+1);
+ return;
+ }
+ }
+
+ if (same_entry(entry+0, entry+1)) {
+ if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
+ resolve(base, entry+1, entry+2);
+ return;
+ }
+ }
+
+ if (same_entry(entry+0, entry+2)) {
+ if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
+ resolve(base, NULL, entry+1);
+ return;
+ }
+ }
+
+ unresolved(base, entry);
+}
+
+static void merge_trees(struct tree_desc t[3], const char *base)
+{
+ traverse_trees(3, t, base, threeway_callback);
+}
+
+static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
+{
+ unsigned char sha1[20];
+ void *buf;
+
+ if (get_sha1(rev, sha1) < 0)
+ die("unknown rev %s", rev);
+ buf = fill_tree_descriptor(desc, sha1);
+ if (!buf)
+ die("%s is not a tree", rev);
+ return buf;
+}
+
+int main(int argc, char **argv)
+{
+ struct tree_desc t[3];
+ void *buf1, *buf2, *buf3;
+
+ if (argc < 4)
+ usage(merge_tree_usage);
+
+ buf1 = get_tree_descriptor(t+0, argv[1]);
+ buf2 = get_tree_descriptor(t+1, argv[2]);
+ buf3 = get_tree_descriptor(t+2, argv[3]);
+ merge_trees(t, "");
+ free(buf1);
+ free(buf2);
+ free(buf3);
+ return 0;
+}
ce->ce_uid = htonl(st->st_uid);
ce->ce_gid = htonl(st->st_gid);
ce->ce_size = htonl(st->st_size);
+
+ if (assume_unchanged)
+ ce->ce_flags |= htons(CE_VALID);
}
static int ce_compare_data(struct cache_entry *ce, struct stat *st)
return changed;
}
-int ce_match_stat(struct cache_entry *ce, struct stat *st)
+int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid)
{
- unsigned int changed = ce_match_stat_basic(ce, st);
+ unsigned int changed;
+
+ /*
+ * If it's marked as always valid in the index, it's
+ * valid whatever the checked-out copy says.
+ */
+ if (!ignore_valid && (ce->ce_flags & htons(CE_VALID)))
+ return 0;
+
+ changed = ce_match_stat_basic(ce, st);
/*
* Within 1 second of this sequence:
* effectively mean we can make at most one commit per second,
* which is not acceptable. Instead, we check cache entries
* whose mtime are the same as the index file timestamp more
- * careful than others.
+ * carefully than others.
*/
if (!changed &&
index_file_timestamp &&
return changed;
}
-int ce_modified(struct cache_entry *ce, struct stat *st)
+int ce_modified(struct cache_entry *ce, struct stat *st, int really)
{
int changed, changed_fs;
- changed = ce_match_stat(ce, st);
+ changed = ce_match_stat(ce, st, really);
if (!changed)
return 0;
/*
return -1;
if (len1 > len2)
return 1;
+
+ /* Compare stages */
+ flags1 &= CE_STAGEMASK;
+ flags2 &= CE_STAGEMASK;
+
if (flags1 < flags2)
return -1;
if (flags1 > flags2)
int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
+
pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
/* existing match? Just replace it. */
return;
if (!lstat(ce->name, &st)) {
- unsigned changed = ce_match_stat(ce, &st);
+ unsigned changed = ce_match_stat(ce, &st, 1);
if (!changed)
return;
errno = 0;
" ordering output:\n"
" --merge-order [ --show-breaks ]\n"
" --topo-order\n"
+" --date-order\n"
" formatting output:\n"
" --parents\n"
" --objects\n"
static int show_breaks = 0;
static int stop_traversal = 0;
static int topo_order = 0;
+static int lifo = 1;
static int no_merges = 0;
static const char **paths = NULL;
static int remove_empty_trees = 0;
}
if (!strcmp(arg, "--topo-order")) {
topo_order = 1;
+ lifo = 1;
+ limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--date-order")) {
+ topo_order = 1;
+ lifo = 0;
limited = 1;
continue;
}
if (limited)
list = limit_list(list);
if (topo_order)
- sort_in_topological_order(&list);
+ sort_in_topological_order(&list, lifo);
show_commit_list(list);
} else {
#ifndef NO_OPENSSL
"--show-breaks",
"--sparse",
"--topo-order",
+ "--date-order",
"--unpacked",
NULL
};
continue;
}
if (!strcmp(arg, "--short") ||
- !strncmp(arg, "--short=", 9)) {
+ !strncmp(arg, "--short=", 8)) {
filter &= ~(DO_FLAGS|DO_NOREV);
verify = 1;
abbrev = DEFAULT_ABBREV;
- if (arg[8] == '=')
- abbrev = strtoul(arg + 9, NULL, 10);
+ if (arg[7] == '=')
+ abbrev = strtoul(arg + 8, NULL, 10);
if (abbrev < MINIMUM_ABBREV)
abbrev = MINIMUM_ABBREV;
else if (40 <= abbrev)
sprintf(path, "%s/pack", objdir);
len = strlen(path);
dir = opendir(path);
- if (!dir)
+ if (!dir) {
+ fprintf(stderr, "unable to open object pack directory: %s: %s\n", path, strerror(errno));
return;
+ }
path[len++] = '/';
while ((de = readdir(dir)) != NULL) {
int namelen = strlen(de->d_name);
int num_rev, i, extra = 0;
int all_heads = 0, all_tags = 0;
int all_mask, all_revs;
+ int lifo = 1;
char head_path[128];
const char *head_path_p;
int head_path_len;
int no_name = 0;
int sha1_name = 0;
int shown_merge_point = 0;
- int topo_order = 0;
int with_current_branch = 0;
int head_at = -1;
else if (!strcmp(arg, "--independent"))
independent = 1;
else if (!strcmp(arg, "--topo-order"))
- topo_order = 1;
+ lifo = 1;
+ else if (!strcmp(arg, "--date-order"))
+ lifo = 0;
else
usage(show_branch_usage);
ac--; av++;
exit(0);
/* Sort topologically */
- if (topo_order)
- sort_in_topological_order(&seen);
+ sort_in_topological_order(&seen, lifo);
/* Give names to commits */
if (!sha1_name && !no_name)
fi
}
+# Most tests can use the created repository, but some amy need to create more.
+# Usage: test_create_repo <directory>
+test_create_repo () {
+ test "$#" = 1 ||
+ error "bug in the test script: not 1 parameter to test-create-repo"
+ owd=`pwd`
+ repo="$1"
+ mkdir "$repo"
+ cd "$repo" || error "Cannot setup test environment"
+ "$GIT_EXEC_PATH/git" init-db --template=$GIT_EXEC_PATH/templates/blt/ 2>/dev/null ||
+ error "cannot run git init-db -- have you built things yet?"
+ mv .git/hooks .git/hooks-disabled
+ cd "$owd"
+}
+
test_done () {
trap - exit
case "$test_failure" in
# Test repository
test=trash
rm -fr "$test"
-mkdir "$test"
-cd "$test" || error "Cannot setup test environment"
-"$GIT_EXEC_PATH/git" init-db --template=../../templates/blt/ 2>/dev/null ||
-error "cannot run git init-db -- have you built things yet?"
-
-mv .git/hooks .git/hooks-disabled
+test_create_repo $test
+cd "$test"
static int info_only;
static int force_remove;
static int verbose;
+static int mark_valid_only = 0;
+#define MARK_VALID 1
+#define UNMARK_VALID 2
+
/* Three functions to allow overloaded pointer return; see linux/err.h */
static inline void *ERR_PTR(long error)
va_end(vp);
}
+static int mark_valid(const char *path)
+{
+ int namelen = strlen(path);
+ int pos = cache_name_pos(path, namelen);
+ if (0 <= pos) {
+ switch (mark_valid_only) {
+ case MARK_VALID:
+ active_cache[pos]->ce_flags |= htons(CE_VALID);
+ break;
+ case UNMARK_VALID:
+ active_cache[pos]->ce_flags &= ~htons(CE_VALID);
+ break;
+ }
+ active_cache_changed = 1;
+ return 0;
+ }
+ return -1;
+}
+
static int add_file_to_cache(const char *path)
{
int size, namelen, option, status;
ce = xmalloc(size);
memset(ce, 0, size);
memcpy(ce->name, path, namelen);
+ ce->ce_flags = htons(namelen);
fill_stat_cache_info(ce, &st);
ce->ce_mode = create_ce_mode(st.st_mode);
if (0 <= pos)
ce->ce_mode = active_cache[pos]->ce_mode;
}
- ce->ce_flags = htons(namelen);
if (index_path(ce->sha1, path, &st, !info_only))
return -1;
* For example, you'd want to do this after doing a "git-read-tree",
* to link up the stat cache details with the proper files.
*/
-static struct cache_entry *refresh_entry(struct cache_entry *ce)
+static struct cache_entry *refresh_entry(struct cache_entry *ce, int really)
{
struct stat st;
struct cache_entry *updated;
if (lstat(ce->name, &st) < 0)
return ERR_PTR(-errno);
- changed = ce_match_stat(ce, &st);
- if (!changed)
- return NULL;
+ changed = ce_match_stat(ce, &st, really);
+ if (!changed) {
+ if (really && assume_unchanged &&
+ !(ce->ce_flags & htons(CE_VALID)))
+ ; /* mark this one VALID again */
+ else
+ return NULL;
+ }
- if (ce_modified(ce, &st))
+ if (ce_modified(ce, &st, really))
return ERR_PTR(-EINVAL);
size = ce_size(ce);
updated = xmalloc(size);
memcpy(updated, ce, size);
fill_stat_cache_info(updated, &st);
+
+ /* In this case, if really is not set, we should leave
+ * CE_VALID bit alone. Otherwise, paths marked with
+ * --no-assume-unchanged (i.e. things to be edited) will
+ * reacquire CE_VALID bit automatically, which is not
+ * really what we want.
+ */
+ if (!really && assume_unchanged && !(ce->ce_flags & htons(CE_VALID)))
+ updated->ce_flags &= ~htons(CE_VALID);
+
return updated;
}
-static int refresh_cache(void)
+static int refresh_cache(int really)
{
int i;
int has_errors = 0;
continue;
}
- new = refresh_entry(ce);
+ new = refresh_entry(ce, really);
if (!new)
continue;
if (IS_ERR(new)) {
if (not_new && PTR_ERR(new) == -ENOENT)
continue;
+ if (really && PTR_ERR(new) == -EINVAL) {
+ /* If we are doing --really-refresh that
+ * means the index is not valid anymore.
+ */
+ ce->ce_flags &= ~htons(CE_VALID);
+ active_cache_changed = 1;
+ }
if (quiet)
continue;
printf("%s: needs update\n", ce->name);
memcpy(ce->name, path, len);
ce->ce_flags = create_ce_flags(len, stage);
ce->ce_mode = create_ce_mode(mode);
+ if (assume_unchanged)
+ ce->ce_flags |= htons(CE_VALID);
option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
if (add_cache_entry(ce, option))
fprintf(stderr, "Ignoring path %s\n", path);
return;
}
+ if (mark_valid_only) {
+ if (mark_valid(p))
+ die("Unable to mark file %s", path);
+ return;
+ }
+
if (force_remove) {
if (remove_file_from_cache(p))
die("git-update-index: unable to remove %s", path);
continue;
}
if (!strcmp(path, "--refresh")) {
- has_errors |= refresh_cache();
+ has_errors |= refresh_cache(0);
+ continue;
+ }
+ if (!strcmp(path, "--really-refresh")) {
+ has_errors |= refresh_cache(1);
continue;
}
if (!strcmp(path, "--cacheinfo")) {
die("git-update-index: %s cannot chmod %s", path, argv[i]);
continue;
}
+ if (!strcmp(path, "--assume-unchanged")) {
+ mark_valid_only = MARK_VALID;
+ continue;
+ }
+ if (!strcmp(path, "--no-assume-unchanged")) {
+ mark_valid_only = UNMARK_VALID;
+ continue;
+ }
if (!strcmp(path, "--info-only")) {
info_only = 1;
continue;
static char *capabilities = "multi_ack";
struct object *o = parse_object(sha1);
+ if (!o)
+ die("git-upload-pack: cannot find object %s:", sha1_to_hex(sha1));
+
if (capabilities)
packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), refname,
0, capabilities);
funny = 0;
for (i = 0; i < entries; i++) {
struct cache_entry *ce = active_cache[i];
- if (ntohs(ce->ce_flags) & ~CE_NAMEMASK) {
+ if (ce_stage(ce)) {
if (10 < ++funny) {
fprintf(stderr, "...\n");
break;