Merge commit '7c851733e4bc2b36bd9df63cab2fe11180242670'

This commit is contained in:
Johannes Sixt
2007-07-21 17:09:51 +02:00
63 changed files with 5341 additions and 1318 deletions

View File

@@ -29,7 +29,7 @@ DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
prefix?=$(HOME)
bindir?=$(prefix)/bin
mandir?=$(prefix)/man
mandir?=$(prefix)/share/man
man1dir=$(mandir)/man1
man5dir=$(mandir)/man5
man7dir=$(mandir)/man7

View File

@@ -86,6 +86,7 @@ git-check-attr purehelpers
git-check-ref-format purehelpers
git-cherry ancillaryinterrogators
git-cherry-pick mainporcelain
git-citool mainporcelain
git-clean mainporcelain
git-clone mainporcelain
git-commit mainporcelain
@@ -111,6 +112,7 @@ git-fsck ancillaryinterrogators
git-gc mainporcelain
git-get-tar-commit-id ancillaryinterrogators
git-grep mainporcelain
git-gui mainporcelain
git-hash-object plumbingmanipulators
git-http-fetch synchelpers
git-http-push synchelpers

View File

@@ -0,0 +1,32 @@
git-citool(1)
=============
NAME
----
git-citool - Graphical alternative to git-commit
SYNOPSIS
--------
'git citool'
DESCRIPTION
-----------
A Tcl/Tk based graphical interface to review modified files, stage
them into the index, enter a commit message and record the new
commit onto the current branch. This interface is an alternative
to the less interactive gitlink:git-commit[1] program.
git-citool is actually a standard alias for 'git gui citool'.
See gitlink:git-gui[1] for more details.
Author
------
Written by Shawn O. Pearce <spearce@spearce.org>.
Documentation
--------------
Documentation by Shawn O. Pearce <spearce@spearce.org>.
GIT
---
Part of the gitlink:git[7] suite

View File

@@ -9,17 +9,17 @@ git-config - Get and set repository or global options
SYNOPSIS
--------
[verse]
'git-config' [--system | --global] name [value [value_regex]]
'git-config' [--system | --global] [-z|--null] name [value [value_regex]]
'git-config' [--system | --global] --add name value
'git-config' [--system | --global] --replace-all name [value [value_regex]]
'git-config' [--system | --global] [type] --get name [value_regex]
'git-config' [--system | --global] [type] --get-all name [value_regex]
'git-config' [--system | --global] [type] --get-regexp name_regex [value_regex]
'git-config' [--system | --global] [type] [-z|--null] --get name [value_regex]
'git-config' [--system | --global] [type] [-z|--null] --get-all name [value_regex]
'git-config' [--system | --global] [type] [-z|--null] --get-regexp name_regex [value_regex]
'git-config' [--system | --global] --unset name [value_regex]
'git-config' [--system | --global] --unset-all name [value_regex]
'git-config' [--system | --global] --rename-section old_name new_name
'git-config' [--system | --global] --remove-section name
'git-config' [--system | --global] -l | --list
'git-config' [--system | --global] [-z|--null] -l | --list
DESCRIPTION
-----------
@@ -118,6 +118,14 @@ See also <<FILES>>.
in the config file will cause the value to be multiplied
by 1024, 1048576, or 1073741824 prior to output.
-z, --null::
For all options that output values and/or keys, always
end values with with the null character (instead of a
newline). Use newline instead as a delimiter between
key and value. This allows for secure parsing of the
output without getting confused e.g. by values that
contain line breaks.
[[FILES]]
FILES

115
Documentation/git-gui.txt Normal file
View File

@@ -0,0 +1,115 @@
git-gui(1)
==========
NAME
----
git-gui - A portable graphical interface to Git
SYNOPSIS
--------
'git gui' [<command>] [arguments]
DESCRIPTION
-----------
A Tcl/Tk based graphical user interface to Git. git-gui focuses
on allowing users to make changes to their repository by making
new commits, amending existing ones, creating branches, performing
local merges, and fetching/pushing to remote repositories.
Unlike gitlink:gitk[1], git-gui focuses on commit generation
and single file annotation, and does not show project history.
It does however supply menu actions to start a gitk session from
within git-gui.
git-gui is known to work on all popular UNIX systems, Mac OS X,
and Windows (under both Cygwin and MSYS). To the extent possible
OS specific user interface guidelines are followed, making git-gui
a fairly native interface for users.
COMMANDS
--------
blame::
Start a blame viewer on the specified file on the given
version (or working directory if not specified).
browser::
Start a tree browser showing all files in the specified
commit (or 'HEAD' by default). Files selected through the
browser are opened in the blame viewer.
citool::
Start git-gui and arrange to make exactly one commit before
exiting and returning to the shell. The interface is limited
to only commit actions, slightly reducing the application's
startup time and simplifying the menubar.
version::
Display the currently running version of git-gui.
Examples
--------
git gui blame Makefile::
Show the contents of the file 'Makefile' in the current
working directory, and provide annotations for both the
original author of each line, and who moved the line to its
current location. The uncommitted file is annotated, and
uncommitted changes (if any) are explicitly attributed to
'Not Yet Committed'.
git gui blame v0.99.8 Makefile::
Show the contents of 'Makefile' in revision 'v0.99.8'
and provide annotations for each line. Unlike the above
example the file is read from the object database and not
the working directory.
git gui citool::
Make one commit and return to the shell when it is complete.
git citool::
Same as 'git gui citool' (above).
git gui browser maint::
Show a browser for the tree of the 'maint' branch. Files
selected in the browser can be viewed with the internal
blame viewer.
See Also
--------
'gitk(1)'::
The git repository browser. Shows branches, commit history
and file differences. gitk is the utility started by
git-gui's Repository Visualize actions.
Other
-----
git-gui is actually maintained as an independent project, but stable
versions are distributed as part of the Git suite for the convience
of end users.
A git-gui development repository can be obtained from:
git clone git://repo.or.cz/git-gui.git
or
git clone http://repo.or.cz/r/git-gui.git
or browsed online at http://repo.or.cz/w/git-gui.git/[].
Author
------
Written by Shawn O. Pearce <spearce@spearce.org>.
Documentation
--------------
Documentation by Shawn O. Pearce <spearce@spearce.org>.
GIT
---
Part of the gitlink:git[7] suite

View File

@@ -52,7 +52,14 @@ include::pretty-options.txt[]
See also gitlink:git-reflog[1].
--decorate::
Print out the ref names of any commits that are shown.
Print out the ref names of any commits that are shown.
--full-diff::
Without this flag, "git log -p <paths>..." shows commits that
touch the specified paths, and diffs about the same specified
paths. With this, the full diff is shown for commits that touch
the specified paths; this means that "<paths>..." limits only
commits, and doesn't limit diff for those commits.
<paths>...::
Show only commits that affect the specified paths.

View File

@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
SYNOPSIS
--------
'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
'git-read-tree' (<tree-ish> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
DESCRIPTION
@@ -50,6 +50,12 @@ OPTIONS
trees that are not directly related to the current
working tree status into a temporary index file.
--trivial::
Restrict three-way merge by `git-read-tree` to happen
only if there is no file-level merging required, instead
of resolving merge for trivial cases and leaving
conflicting files unresolved in the index.
--aggressive::
Usually a three-way merge by `git-read-tree` resolves
the merge for really trivial cases and leaves other

View File

@@ -39,6 +39,19 @@ the current branch. It is basically an alias for 'git log -g --abbrev-commit
OPTIONS
-------
--stale-fix::
This revamps the logic -- the definition of "broken commit"
becomes: a commit that is not reachable from any of the refs and
there is a missing object among the commit, tree, or blob
objects reachable from it that is not reachable from any of the
refs.
+
This computation involves traversing all the reachable objects, i.e. it
has the same cost as 'git prune'. Fortunately, once this is run, we
should not have to ever worry about missing objects, because the current
prune and pack-objects know about reflogs and protect objects referred by
them.
--expire=<time>::
Entries older than this time are pruned. Without the
option it is taken from configuration `gc.reflogExpire`,

View File

@@ -16,11 +16,13 @@ SYNOPSIS
[ \--sparse ]
[ \--no-merges ]
[ \--remove-empty ]
[ \--full-history ]
[ \--not ]
[ \--all ]
[ \--stdin ]
[ \--topo-order ]
[ \--parents ]
[ \--timestamp ]
[ \--left-right ]
[ \--cherry-pick ]
[ \--encoding[=<encoding>] ]
@@ -116,6 +118,9 @@ e.g. "2 hours ago".
Print the parents of the commit.
--timestamp::
Print the raw commit timestamp.
--left-right::
Mark which side of a symmetric diff a commit is reachable from.
@@ -228,6 +233,14 @@ limiting may be applied.
Stop when a given path disappears from the tree.
--full-history::
Show also parts of history irrelevant to current state of a given
path. This turns off history simplification, which removed merges
which didn't change anything at all at some child. It will still actually
simplify away merges that didn't change anything at all into either
child.
--no-merges::
Do not print commits with more than one parent.

View File

@@ -89,6 +89,10 @@ OPTIONS
--git-dir::
Show `$GIT_DIR` if defined else show the path to the .git directory.
--is-inside-git-dir::
Return "true" if we are in the git directory, otherwise "false".
Some commands require to be run in a working directory.
--short, --short=number::
Instead of outputting the full SHA1 values of object names try to
abbreviate them to a shorter unique name. When no length is specified

View File

@@ -59,9 +59,11 @@ The --cc option must be repeated for each user you want on the cc list.
Only necessary if --compose is also set. If --compose
is not set, this will be prompted for.
--no-signed-off-by-cc::
Do not add emails found in Signed-off-by: or Cc: lines to the
cc list.
--signed-off-by-cc, --no-signed-off-by-cc::
If this is set, add emails found in Signed-off-by: or Cc: lines to the
cc list.
Default is the value of 'sendemail.signedoffbycc' configuration value;
if that is unspecified, default to --signed-off-by-cc.
--quiet::
Make git-send-email less verbose. One line per email should be
@@ -82,9 +84,18 @@ The --cc option must be repeated for each user you want on the cc list.
Only necessary if --compose is also set. If --compose
is not set, this will be prompted for.
--suppress-from::
Do not add the From: address to the cc: list, if it shows up in a From:
line.
--suppress-from, --no-suppress-from::
If this is set, do not add the From: address to the cc: list, if it
shows up in a From: line.
Default is the value of 'sendemail.suppressfrom' configuration value;
if that is unspecified, default to --no-supress-from.
--thread, --no-thread::
If this is set, the In-Reply-To header will be set on each email sent.
If disabled with "--no-thread", no emails will have the In-Reply-To
header set.
Default is the value of the 'sendemail.thread' configuration value;
if that is unspecified, default to --thread.
--dry-run::
Do everything except actually send the emails.

View File

@@ -66,9 +66,10 @@ COMMANDS
to the names of remotes if trunk/branches/tags are
specified. The prefix does not automatically include a
trailing slash, so be sure you include one in the
argument if that is what you want. This is useful if
you wish to track multiple projects that share a common
repository.
argument if that is what you want. If --branches/-b is
specified, the prefix must include a trailing slash.
Setting a prefix is useful if you wish to track multiple
projects that share a common repository.
'fetch'::
Fetch unfetched revisions from the Subversion remote we are

View File

@@ -41,9 +41,11 @@ unreleased) version of git, that is available from 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
* link:v1.5.2/git.html[documentation for release 1.5.2]
* link:v1.5.2.2/git.html[documentation for release 1.5.2.2]
* release notes for
link:RelNotes-1.5.2.2.txt[1.5.2.2],
link:RelNotes-1.5.2.1.txt[1.5.2.1],
link:RelNotes-1.5.2.txt[1.5.2].
* link:v1.5.1.6/git.html[documentation for release 1.5.1.6]

View File

@@ -5,6 +5,15 @@
'full', 'fuller', 'email', 'raw' and 'format:<string>'.
When left out the format default to 'medium'.
--abbrev-commit::
Instead of showing the full 40-byte hexadecimal commit object
name, show only handful hexdigits prefix. Non default number of
digits can be specified with "--abbrev=<n>" (which also modifies
diff output, if it is displayed).
+
This should make "--pretty=oneline" a whole lot more readable for
people using 80-column terminals.
--encoding[=<encoding>]::
The commit objects record the encoding used for the log message
in their encoding header; this option can be used to tell the

14
INSTALL
View File

@@ -89,10 +89,16 @@ Issues of note:
will include them. Note that config.mak is not distributed;
the name is reserved for local settings.
- To build and install documentation suite, you need to have the
asciidoc/xmlto toolchain. Alternatively, pre-formatted
documentation are available in "html" and "man" branches of the git
repository itself. For example, you could:
- To build and install documentation suite, you need to have
the asciidoc/xmlto toolchain. Because not many people are
inclined to install the tools, the default build target
("make all") does _not_ build them. The documentation is
written for AsciiDoc 7, but "make ASCIIDOC8=YesPlease doc"
will let you format with AsciiDoc 8.
Alternatively, pre-formatted documentation are available in
"html" and "man" branches of the git repository itself. For
example, you could:
$ mkdir manual && cd manual
$ git init

View File

@@ -211,7 +211,8 @@ SCRIPT_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 git-submodule.sh
git-lost-found.sh git-quiltimport.sh git-submodule.sh \
git-filter-branch.sh
SCRIPT_PERL = \
git-add--interactive.perl \
@@ -1056,15 +1057,16 @@ git.spec: git.spec.in
mv $@+ $@
GIT_TARNAME=git-$(GIT_VERSION)
dist: git.spec git-archive
dist: git.spec git-archive configure
./git-archive --format=tar \
--prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar
@mkdir -p $(GIT_TARNAME)
@cp git.spec $(GIT_TARNAME)
@cp git.spec configure $(GIT_TARNAME)
@echo $(GIT_VERSION) > $(GIT_TARNAME)/version
@$(MAKE) -C git-gui TARDIR=../$(GIT_TARNAME)/git-gui dist-version
$(TAR) rf $(GIT_TARNAME).tar \
$(GIT_TARNAME)/git.spec \
$(GIT_TARNAME)/configure \
$(GIT_TARNAME)/version \
$(GIT_TARNAME)/git-gui/version
@rm -rf $(GIT_TARNAME)

View File

@@ -40,42 +40,29 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
dir->nr = dst - dir->entries;
for (i = 0; i < specs; i++) {
struct stat st;
const char *match;
if (seen[i])
continue;
match = pathspec[i];
if (!match[0])
continue;
/* Existing file? We must have ignored it */
if (!lstat(match, &st)) {
struct dir_entry *ent;
ent = dir_add_name(dir, match, strlen(match));
ent->ignored = 1;
if (S_ISDIR(st.st_mode))
ent->ignored_dir = 1;
continue;
}
die("pathspec '%s' did not match any files", match);
if (!seen[i] && !file_exists(pathspec[i]))
die("pathspec '%s' did not match any files",
pathspec[i]);
}
}
static void fill_directory(struct dir_struct *dir, const char **pathspec)
static void fill_directory(struct dir_struct *dir, const char **pathspec,
int ignored_too)
{
const char *path, *base;
int baselen;
/* Set up the default git porcelain excludes */
memset(dir, 0, sizeof(*dir));
dir->exclude_per_dir = ".gitignore";
path = git_path("info/exclude");
if (!access(path, R_OK))
add_excludes_from_file(dir, path);
if (!access(excludes_file, R_OK))
add_excludes_from_file(dir, excludes_file);
if (!ignored_too) {
dir->collect_ignored = 1;
dir->exclude_per_dir = ".gitignore";
path = git_path("info/exclude");
if (!access(path, R_OK))
add_excludes_from_file(dir, path);
if (!access(excludes_file, R_OK))
add_excludes_from_file(dir, excludes_file);
}
/*
* Calculate common prefix for the pathspec, and
@@ -219,13 +206,11 @@ int cmd_add(int argc, const char **argv, const char *prefix)
}
pathspec = get_pathspec(prefix, argv + i);
fill_directory(&dir, pathspec);
fill_directory(&dir, pathspec, ignored_too);
if (show_only) {
const char *sep = "", *eof = "";
for (i = 0; i < dir.nr; i++) {
if (!ignored_too && dir.entries[i]->ignored)
continue;
printf("%s%s", sep, dir.entries[i]->name);
sep = " ";
eof = "\n";
@@ -237,25 +222,13 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die("index file corrupt");
if (!ignored_too) {
int has_ignored = 0;
for (i = 0; i < dir.nr; i++)
if (dir.entries[i]->ignored)
has_ignored = 1;
if (has_ignored) {
fprintf(stderr, ignore_warning);
for (i = 0; i < dir.nr; i++) {
if (!dir.entries[i]->ignored)
continue;
fprintf(stderr, "%s", dir.entries[i]->name);
if (dir.entries[i]->ignored_dir)
fprintf(stderr, " (directory)");
fputc('\n', stderr);
}
fprintf(stderr,
"Use -f if you really want to add them.\n");
exit(1);
if (dir.ignored_nr) {
fprintf(stderr, ignore_warning);
for (i = 0; i < dir.ignored_nr; i++) {
fprintf(stderr, "%s\n", dir.ignored[i]->name);
}
fprintf(stderr, "Use -f if you really want to add them.\n");
exit(1);
}
for (i = 0; i < dir.nr; i++)

View File

@@ -247,7 +247,6 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
char c;
int color;
struct commit *commit;
char subject[256];
switch (item->kind) {
case REF_LOCAL_BRANCH:
@@ -268,17 +267,23 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
}
if (verbose) {
char *subject = NULL;
unsigned long subject_len = 0;
const char *sub = " **** invalid ref ****";
commit = lookup_commit(item->sha1);
if (commit && !parse_commit(commit))
if (commit && !parse_commit(commit)) {
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
subject, sizeof(subject), 0,
&subject, &subject_len, 0,
NULL, NULL, 0);
else
strcpy(subject, " **** invalid ref ****");
sub = subject;
}
printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color),
maxwidth, item->name,
branch_get_color(COLOR_BRANCH_RESET),
find_unique_abbrev(item->sha1, abbrev), subject);
find_unique_abbrev(item->sha1, abbrev), sub);
if (subject)
free(subject);
} else {
printf("%c %s%s%s\n", c, branch_get_color(color), item->name,
branch_get_color(COLOR_BRANCH_RESET));

View File

@@ -2,7 +2,7 @@
#include "cache.h"
static const char git_config_set_usage[] =
"git-config [ --global | --system ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
"git-config [ --global | --system ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
static char *key;
static regex_t *key_regexp;
@@ -12,14 +12,17 @@ static int use_key_regexp;
static int do_all;
static int do_not_match;
static int seen;
static char delim = '=';
static char key_delim = ' ';
static char term = '\n';
static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
static int show_all_config(const char *key_, const char *value_)
{
if (value_)
printf("%s=%s\n", key_, value_);
printf("%s%c%s%c", key_, delim, value_, term);
else
printf("%s\n", key_);
printf("%s%c", key_, term);
return 0;
}
@@ -40,7 +43,7 @@ static int show_config(const char* key_, const char* value_)
if (show_keys) {
if (value_)
printf("%s ", key_);
printf("%s%c", key_, key_delim);
else
printf("%s", key_);
}
@@ -58,7 +61,7 @@ static int show_config(const char* key_, const char* value_)
key_, vptr);
}
else
printf("%s\n", vptr);
printf("%s%c", vptr, term);
return 0;
}
@@ -159,6 +162,11 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (!strcmp(argv[1], "--system"))
setenv("GIT_CONFIG", git_etc_gitconfig(), 1);
else if (!strcmp(argv[1], "--null") || !strcmp(argv[1], "-z")) {
term = '\0';
delim = '\n';
key_delim = '\n';
}
else if (!strcmp(argv[1], "--rename-section")) {
int ret;
if (argc != 4)

View File

@@ -41,7 +41,8 @@ static int copy_file(const char *dst, const char *src, int mode)
return fdo;
}
status = copy_fd(fdi, fdo);
close(fdo);
if (close(fdo) != 0)
return error("%s: write error: %s", dst, strerror(errno));
if (!status && adjust_shared_perm(dst))
return -1;

View File

@@ -58,6 +58,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
argc = setup_revisions(argc, argv, rev, "HEAD");
if (rev->diffopt.pickaxe || rev->diffopt.filter)
rev->always_show_header = 0;
if (rev->diffopt.follow_renames) {
rev->always_show_header = 0;
if (rev->diffopt.nr_paths != 1)
usage("git logs can only follow renames on one pathname at a time");
}
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--decorate")) {
@@ -584,7 +589,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
get_patch_ids(&rev, &ids, prefix);
if (!use_stdout)
realstdout = fdopen(dup(1), "w");
realstdout = xfdopen(xdup(1), "w");
prepare_revision_walk(&rev);
while ((commit = get_revision(&rev)) != NULL) {
@@ -742,11 +747,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
sign = '-';
if (verbose) {
static char buf[16384];
char *buf = NULL;
unsigned long buflen = 0;
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
buf, sizeof(buf), 0, NULL, NULL, 0);
&buf, &buflen, 0, NULL, NULL, 0);
printf("%c %s %s\n", sign,
sha1_to_hex(commit->object.sha1), buf);
free(buf);
}
else {
printf("%c %s\n", sign,

View File

@@ -105,6 +105,8 @@ static int pack_refs(unsigned int flags)
fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
for_each_ref(handle_one_ref, &cbdata);
if (ferror(cbdata.refs_file))
die("failed to write ref-pack file");
if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file))
die("failed to write ref-pack file (%s)", strerror(errno));
if (commit_lock_file(&packed) < 0)

View File

@@ -84,7 +84,7 @@ static void prime_cache_tree(void)
}
static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
static struct lock_file lock_file;

View File

@@ -57,7 +57,8 @@ static int write_rr(struct path_list *rr, int out_fd)
write_in_full(out_fd, path, length) != length)
die("unable to write rerere record");
}
close(out_fd);
if (close(out_fd) != 0)
die("unable to write rerere record");
return commit_lock_file(&write_lock);
}

View File

@@ -92,11 +92,13 @@ static void show_commit(struct commit *commit)
putchar('\n');
if (revs.verbose_header) {
static char pretty_header[16384];
char *buf = NULL;
unsigned long buflen = 0;
pretty_print_commit(revs.commit_format, commit, ~0,
pretty_header, sizeof(pretty_header),
&buf, &buflen,
revs.abbrev, NULL, NULL, revs.date_mode);
printf("%s%c", pretty_header, hdr_termination);
printf("%s%c", buf, hdr_termination);
free(buf);
}
fflush(stdout);
if (commit->parents) {

View File

@@ -259,17 +259,19 @@ static void join_revs(struct commit_list **list_p,
static void show_one_commit(struct commit *commit, int no_name)
{
char pretty[256], *cp;
char *pretty = NULL;
const char *pretty_str = "(unavailable)";
unsigned long pretty_len = 0;
struct commit_name *name = commit->util;
if (commit->object.parsed)
if (commit->object.parsed) {
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
pretty, sizeof(pretty), 0, NULL, NULL, 0);
else
strcpy(pretty, "(unavailable)");
if (!prefixcmp(pretty, "[PATCH] "))
cp = pretty + 8;
else
cp = pretty;
&pretty, &pretty_len,
0, NULL, NULL, 0);
pretty_str = pretty;
}
if (!prefixcmp(pretty_str, "[PATCH] "))
pretty_str += 8;
if (!no_name) {
if (name && name->head_name) {
@@ -286,7 +288,8 @@ static void show_one_commit(struct commit *commit, int no_name)
printf("[%s] ",
find_unique_abbrev(commit->object.sha1, 7));
}
puts(cp);
puts(pretty_str);
free(pretty);
}
static char *ref_name[MAX_REVS + 1];

View File

@@ -478,7 +478,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
if (0 <= it->entry_count) {
if (size < 20)
goto free_return;
hashcpy(it->sha1, (unsigned char*)buf);
hashcpy(it->sha1, (const unsigned char*)buf);
buf += 20;
size -= 20;
}

View File

@@ -234,8 +234,11 @@ extern void verify_non_filename(const char *prefix, const char *name);
*/
#define ALLOC_GROW(x, nr, alloc) \
do { \
if ((nr) >= alloc) { \
alloc = alloc_nr(alloc); \
if ((nr) > alloc) { \
if (alloc_nr(alloc) < (nr)) \
alloc = (nr); \
else \
alloc = alloc_nr(alloc); \
x = xrealloc((x), alloc * sizeof(*(x))); \
} \
} while(0)

399
commit.c
View File

@@ -529,6 +529,14 @@ static int add_rfc2047(char *buf, const char *line, int len,
return bp - buf;
}
static unsigned long bound_rfc2047(unsigned long len, const char *encoding)
{
/* upper bound of q encoded string of length 'len' */
unsigned long elen = strlen(encoding);
return len * 3 + elen + 100;
}
static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
const char *line, enum date_mode dmode,
const char *encoding)
@@ -776,7 +784,7 @@ static void fill_person(struct interp *table, const char *msg, int len)
}
static long format_commit_message(const struct commit *commit,
const char *msg, char *buf, unsigned long space)
const char *msg, char **buf_p, unsigned long *space_p)
{
struct interp table[] = {
{ "%H" }, /* commit hash */
@@ -905,31 +913,252 @@ static long format_commit_message(const struct commit *commit,
if (!table[i].value)
interp_set_entry(table, i, "<unknown>");
interpolate(buf, space, user_format, table, ARRAY_SIZE(table));
do {
char *buf = *buf_p;
unsigned long space = *space_p;
space = interpolate(buf, space, user_format,
table, ARRAY_SIZE(table));
if (!space)
break;
buf = xrealloc(buf, space);
*buf_p = buf;
*space_p = space;
} while (1);
interp_clear_table(table, ARRAY_SIZE(table));
return strlen(buf);
return strlen(*buf_p);
}
static void pp_header(enum cmit_fmt fmt,
int abbrev,
enum date_mode dmode,
const char *encoding,
const struct commit *commit,
const char **msg_p,
unsigned long *len_p,
unsigned long *ofs_p,
char **buf_p,
unsigned long *space_p)
{
int parents_shown = 0;
for (;;) {
const char *line = *msg_p;
char *dst;
int linelen = get_one_line(*msg_p, *len_p);
unsigned long len;
if (!linelen)
return;
*msg_p += linelen;
*len_p -= linelen;
if (linelen == 1)
/* End of header */
return;
ALLOC_GROW(*buf_p, linelen + *ofs_p + 20, *space_p);
dst = *buf_p + *ofs_p;
if (fmt == CMIT_FMT_RAW) {
memcpy(dst, line, linelen);
*ofs_p += linelen;
continue;
}
if (!memcmp(line, "parent ", 7)) {
if (linelen != 48)
die("bad parent line in commit");
continue;
}
if (!parents_shown) {
struct commit_list *parent;
int num;
for (parent = commit->parents, num = 0;
parent;
parent = parent->next, num++)
;
/* with enough slop */
num = *ofs_p + num * 50 + 20;
ALLOC_GROW(*buf_p, num, *space_p);
dst = *buf_p + *ofs_p;
*ofs_p += add_merge_info(fmt, dst, commit, abbrev);
parents_shown = 1;
}
/*
* MEDIUM == DEFAULT shows only author with dates.
* FULL shows both authors but not dates.
* FULLER shows both authors and dates.
*/
if (!memcmp(line, "author ", 7)) {
len = linelen;
if (fmt == CMIT_FMT_EMAIL)
len = bound_rfc2047(linelen, encoding);
ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
dst = *buf_p + *ofs_p;
*ofs_p += add_user_info("Author", fmt, dst,
line + 7, dmode, encoding);
}
if (!memcmp(line, "committer ", 10) &&
(fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
len = linelen;
if (fmt == CMIT_FMT_EMAIL)
len = bound_rfc2047(linelen, encoding);
ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
dst = *buf_p + *ofs_p;
*ofs_p += add_user_info("Commit", fmt, dst,
line + 10, dmode, encoding);
}
}
}
static void pp_title_line(enum cmit_fmt fmt,
const char **msg_p,
unsigned long *len_p,
unsigned long *ofs_p,
char **buf_p,
unsigned long *space_p,
int indent,
const char *subject,
const char *after_subject,
const char *encoding,
int plain_non_ascii)
{
char *title;
unsigned long title_alloc, title_len;
unsigned long len;
title_len = 0;
title_alloc = 80;
title = xmalloc(title_alloc);
for (;;) {
const char *line = *msg_p;
int linelen = get_one_line(line, *len_p);
*msg_p += linelen;
*len_p -= linelen;
if (!linelen || is_empty_line(line, &linelen))
break;
if (title_alloc <= title_len + linelen + 2) {
title_alloc = title_len + linelen + 80;
title = xrealloc(title, title_alloc);
}
len = 0;
if (title_len) {
if (fmt == CMIT_FMT_EMAIL) {
len++;
title[title_len++] = '\n';
}
len++;
title[title_len++] = ' ';
}
memcpy(title + title_len, line, linelen);
title_len += linelen;
}
/* Enough slop for the MIME header and rfc2047 */
len = bound_rfc2047(title_len, encoding)+ 1000;
if (subject)
len += strlen(subject);
if (after_subject)
len += strlen(after_subject);
if (encoding)
len += strlen(encoding);
ALLOC_GROW(*buf_p, title_len + *ofs_p + len, *space_p);
if (subject) {
len = strlen(subject);
memcpy(*buf_p + *ofs_p, subject, len);
*ofs_p += len;
*ofs_p += add_rfc2047(*buf_p + *ofs_p,
title, title_len, encoding);
} else {
memcpy(*buf_p + *ofs_p, title, title_len);
*ofs_p += title_len;
}
(*buf_p)[(*ofs_p)++] = '\n';
if (plain_non_ascii) {
const char *header_fmt =
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=%s\n"
"Content-Transfer-Encoding: 8bit\n";
*ofs_p += snprintf(*buf_p + *ofs_p,
*space_p - *ofs_p,
header_fmt, encoding);
}
if (after_subject) {
len = strlen(after_subject);
memcpy(*buf_p + *ofs_p, after_subject, len);
*ofs_p += len;
}
free(title);
if (fmt == CMIT_FMT_EMAIL) {
ALLOC_GROW(*buf_p, *ofs_p + 20, *space_p);
(*buf_p)[(*ofs_p)++] = '\n';
}
}
static void pp_remainder(enum cmit_fmt fmt,
const char **msg_p,
unsigned long *len_p,
unsigned long *ofs_p,
char **buf_p,
unsigned long *space_p,
int indent)
{
int first = 1;
for (;;) {
const char *line = *msg_p;
int linelen = get_one_line(line, *len_p);
*msg_p += linelen;
*len_p -= linelen;
if (!linelen)
break;
if (is_empty_line(line, &linelen)) {
if (first)
continue;
if (fmt == CMIT_FMT_SHORT)
break;
}
first = 0;
ALLOC_GROW(*buf_p, *ofs_p + linelen + indent + 20, *space_p);
if (indent) {
memset(*buf_p + *ofs_p, ' ', indent);
*ofs_p += indent;
}
memcpy(*buf_p + *ofs_p, line, linelen);
*ofs_p += linelen;
(*buf_p)[(*ofs_p)++] = '\n';
}
}
unsigned long pretty_print_commit(enum cmit_fmt fmt,
const struct commit *commit,
unsigned long len,
char *buf, unsigned long space,
char **buf_p, unsigned long *space_p,
int abbrev, const char *subject,
const char *after_subject,
enum date_mode dmode)
{
int hdr = 1, body = 0, seen_title = 0;
unsigned long offset = 0;
unsigned long beginning_of_body;
int indent = 4;
int parents_shown = 0;
const char *msg = commit->buffer;
int plain_non_ascii = 0;
char *reencoded;
const char *encoding;
char *buf;
if (fmt == CMIT_FMT_USERFORMAT)
return format_commit_message(commit, msg, buf, space);
return format_commit_message(commit, msg, buf_p, space_p);
encoding = (git_log_output_encoding
? git_log_output_encoding
@@ -937,8 +1166,10 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
if (!encoding)
encoding = "utf-8";
reencoded = logmsg_reencode(commit, encoding);
if (reencoded)
if (reencoded) {
msg = reencoded;
len = strlen(reencoded);
}
if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
indent = 0;
@@ -969,137 +1200,57 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
}
}
for (;;) {
const char *line = msg;
int linelen = get_one_line(msg, len);
pp_header(fmt, abbrev, dmode, encoding,
commit, &msg, &len,
&offset, buf_p, space_p);
if (fmt != CMIT_FMT_ONELINE && !subject) {
ALLOC_GROW(*buf_p, offset + 20, *space_p);
(*buf_p)[offset++] = '\n';
}
/* Skip excess blank lines at the beginning of body, if any... */
for (;;) {
int linelen = get_one_line(msg, len);
int ll = linelen;
if (!linelen)
break;
/*
* We want some slop for indentation and a possible
* final "...". Thus the "+ 20".
*/
if (offset + linelen + 20 > space) {
memcpy(buf + offset, " ...\n", 8);
offset += 8;
if (!is_empty_line(msg, &ll))
break;
}
msg += linelen;
len -= linelen;
if (hdr) {
if (linelen == 1) {
hdr = 0;
if ((fmt != CMIT_FMT_ONELINE) && !subject)
buf[offset++] = '\n';
continue;
}
if (fmt == CMIT_FMT_RAW) {
memcpy(buf + offset, line, linelen);
offset += linelen;
continue;
}
if (!memcmp(line, "parent ", 7)) {
if (linelen != 48)
die("bad parent line in commit");
continue;
}
if (!parents_shown) {
offset += add_merge_info(fmt, buf + offset,
commit, abbrev);
parents_shown = 1;
continue;
}
/*
* MEDIUM == DEFAULT shows only author with dates.
* FULL shows both authors but not dates.
* FULLER shows both authors and dates.
*/
if (!memcmp(line, "author ", 7))
offset += add_user_info("Author", fmt,
buf + offset,
line + 7,
dmode,
encoding);
if (!memcmp(line, "committer ", 10) &&
(fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
offset += add_user_info("Commit", fmt,
buf + offset,
line + 10,
dmode,
encoding);
continue;
}
if (!subject)
body = 1;
if (is_empty_line(line, &linelen)) {
if (!seen_title)
continue;
if (!body)
continue;
if (subject)
continue;
if (fmt == CMIT_FMT_SHORT)
break;
}
seen_title = 1;
if (subject) {
int slen = strlen(subject);
memcpy(buf + offset, subject, slen);
offset += slen;
offset += add_rfc2047(buf + offset, line, linelen,
encoding);
}
else {
memset(buf + offset, ' ', indent);
memcpy(buf + offset + indent, line, linelen);
offset += linelen + indent;
}
buf[offset++] = '\n';
if (fmt == CMIT_FMT_ONELINE)
break;
if (subject && plain_non_ascii) {
int sz;
char header[512];
const char *header_fmt =
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=%s\n"
"Content-Transfer-Encoding: 8bit\n";
sz = snprintf(header, sizeof(header), header_fmt,
encoding);
if (sizeof(header) < sz)
die("Encoding name %s too long", encoding);
memcpy(buf + offset, header, sz);
offset += sz;
}
if (after_subject) {
int slen = strlen(after_subject);
if (slen > space - offset - 1)
slen = space - offset - 1;
memcpy(buf + offset, after_subject, slen);
offset += slen;
after_subject = NULL;
}
subject = NULL;
}
while (offset && isspace(buf[offset-1]))
/* These formats treat the title line specially. */
if (fmt == CMIT_FMT_ONELINE
|| fmt == CMIT_FMT_EMAIL)
pp_title_line(fmt, &msg, &len, &offset,
buf_p, space_p, indent,
subject, after_subject, encoding,
plain_non_ascii);
beginning_of_body = offset;
if (fmt != CMIT_FMT_ONELINE)
pp_remainder(fmt, &msg, &len, &offset,
buf_p, space_p, indent);
while (offset && isspace((*buf_p)[offset-1]))
offset--;
ALLOC_GROW(*buf_p, offset + 20, *space_p);
buf = *buf_p;
/* Make sure there is an EOLN for the non-oneline case */
if (fmt != CMIT_FMT_ONELINE)
buf[offset++] = '\n';
/*
* make sure there is another EOLN to separate the headers from whatever
* body the caller appends if we haven't already written a body
* The caller may append additional body text in e-mail
* format. Make sure we did not strip the blank line
* between the header and the body.
*/
if (fmt == CMIT_FMT_EMAIL && !body)
if (fmt == CMIT_FMT_EMAIL && offset <= beginning_of_body)
buf[offset++] = '\n';
buf[offset] = '\0';
free(reencoded);
return offset;
}

View File

@@ -61,7 +61,7 @@ enum cmit_fmt {
};
extern enum cmit_fmt get_commit_format(const char *arg);
extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char **buf_p, unsigned long *space_p, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
/** Removes the first commit from a list sorted by date, and adds all
* of its parents.

View File

@@ -225,11 +225,10 @@ static int git_tcp_connect_sock(char *host, int flags)
}
if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
saved_errno = errno;
fprintf(stderr, "%s[%d: %s]: net=%s, errno=%s\n",
fprintf(stderr, "%s[%d: %s]: errno=%s\n",
host,
cnt,
ai_name(ai),
hstrerror(h_errno),
strerror(saved_errno));
close(sockfd);
sockfd = -1;
@@ -316,11 +315,10 @@ static int git_tcp_connect_sock(char *host, int flags)
if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
saved_errno = errno;
fprintf(stderr, "%s[%d: %s]: net=%s, errno=%s\n",
fprintf(stderr, "%s[%d: %s]: errno=%s\n",
host,
cnt,
inet_ntoa(*(struct in_addr *)&sa.sin_addr),
hstrerror(h_errno),
strerror(saved_errno));
close(sockfd);
sockfd = -1;

View File

@@ -682,8 +682,11 @@ _git_push ()
esac
__gitcomp "$(__git_refs "$remote")" "" "${cur#*:}"
;;
+*)
__gitcomp "$(__git_refs)" + "${cur#+}"
;;
*)
__gitcomp "$(__git_refs2)"
__gitcomp "$(__git_refs)"
;;
esac
;;

1577
contrib/fast-import/git-p4 Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
@python "%~d0%~p0git-p4" %*

View File

@@ -0,0 +1,159 @@
git-p4 - Perforce <-> Git converter using git-fast-import
Usage
=====
git-p4 supports two main modes: Importing from Perforce to a Git repository is
done using "git-p4 sync" or "git-p4 rebase". Submitting changes from Git back
to Perforce is done using "git-p4 submit".
Importing
=========
You can simply start with
git-p4 clone //depot/path/project
or
git-p4 clone //depot/path/project myproject
This will create an empty git repository in a subdirectory called "project" (or
"myproject" with the second command), import the head revision from the
specified perforce path into a git "p4" branch (remotes/p4 actually), create a
master branch off it and check it out. If you want the entire history (not just
the head revision) then you can simply append a "@all" to the depot path:
git-p4 clone //depot/project/main@all myproject
If you want more control you can also use the git-p4 sync command directly:
mkdir repo-git
cd repo-git
git init
git-p4 sync //path/in/your/perforce/depot
This will import the current head revision of the specified depot path into a
"remotes/p4/master" branch of your git repository. You can use the
--branch=mybranch option to use a different branch.
If you want to import the entire history of a given depot path just use
git-p4 sync //path/in/depot@all
To achieve optimal compression you may want to run 'git repack -a -d -f' after
a big import. This may take a while.
Support for Perforce integrations is still work in progress. Don't bother
trying it unless you want to hack on it :)
Incremental Imports
===================
After an initial import you can easily synchronize your git repository with
newer changes from the Perforce depot by just calling
git-p4 sync
in your git repository. By default the "remotes/p4/master" branch is updated.
It is recommended to run 'git repack -a -d -f' from time to time when using
incremental imports to optimally combine the individual git packs that each
incremental import creates through the use of git-fast-import.
A useful setup may be that you have a periodically updated git repository
somewhere that contains a complete import of a Perforce project. That git
repository can be used to clone the working repository from and one would
import from Perforce directly after cloning using git-p4. If the connection to
the Perforce server is slow and the working repository hasn't been synced for a
while it may be desirable to fetch changes from the origin git repository using
the efficient git protocol. git-p4 supports this setup by calling "git fetch origin"
by default if there is an origin branch. You can disable this using
git config git-p4.syncFromOrigin false
Updating
========
A common working pattern is to fetch the latest changes from the Perforce depot
and merge them with local uncommitted changes. The recommended way is to use
git's rebase mechanism to preserve linear history. git-p4 provides a convenient
git-p4 rebase
command that calls git-p4 sync followed by git rebase to rebase the current
working branch.
Submitting
==========
git-p4 has support for submitting changes from a git repository back to the
Perforce depot. This requires a Perforce checkout separate to your git
repository. To submit all changes that are in the current git branch but not in
the "p4" branch (or "origin" if "p4" doesn't exist) simply call
git-p4 submit
in your git repository. If you want to submit changes in a specific branch that
is not your current git branch you can also pass that as an argument:
git-p4 submit mytopicbranch
You can override the reference branch with the --origin=mysourcebranch option.
If a submit fails you may have to "p4 resolve" and submit manually. You can
continue importing the remaining changes with
git-p4 submit --continue
After submitting you should sync your perforce import branch ("p4" or "origin")
from Perforce using git-p4's sync command.
If you have changes in your working directory that you haven't committed into
git yet but that you want to commit to Perforce directly ("quick fixes") then
you do not have to go through the intermediate step of creating a git commit
first but you can just call
git-p4 submit --direct
Example
=======
# Clone a repository
git-p4 clone //depot/path/project
# Enter the newly cloned directory
cd project
# Do some work...
vi foo.h
# ... and commit locally to gi
git commit foo.h
# In the meantime somebody submitted changes to the Perforce depot. Rebase your latest
# changes against the latest changes in Perforce:
git-p4 rebase
# Submit your locally committed changes back to Perforce
git-p4 submit
# ... and synchronize with Perforce
git-p4 rebase
Implementation Details...
=========================
* Changesets from Perforce are imported using git fast-import.
* The import does not require anything from the Perforce client view as it just uses
"p4 print //depot/path/file#revision" to get the actual file contents.
* Every imported changeset has a special [git-p4...] line at the
end of the log message that gives information about the corresponding
Perforce change number and is also used by git-p4 itself to find out
where to continue importing when doing incremental imports.
Basically when syncing it extracts the perforce change number of the
latest commit in the "p4" branch and uses "p4 changes //depot/path/...@changenum,#head"
to find out which changes need to be imported.
* git-p4 submit uses "git rev-list" to pick the commits between the "p4" branch
and the current branch.
The commits themselves are applied using git diff/format-patch ... | git apply

View File

@@ -24,6 +24,11 @@ git_dir=$(cd "$orig_git" 2>/dev/null &&
git rev-parse --git-dir 2>/dev/null) ||
die "\"$orig_git\" is not a git repository!"
if test "$git_dir" = ".git"
then
git_dir="$orig_git/.git"
fi
# don't link to a workdir
if test -L "$git_dir/config"
then

2
diff.c
View File

@@ -2205,6 +2205,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
}
else if (!strcmp(arg, "--find-copies-harder"))
options->find_copies_harder = 1;
else if (!strcmp(arg, "--follow"))
options->follow_renames = 1;
else if (!strcmp(arg, "--abbrev"))
options->abbrev = DEFAULT_ABBREV;
else if (!prefixcmp(arg, "--abbrev=")) {

1
diff.h
View File

@@ -55,6 +55,7 @@ struct diff_options {
full_index:1,
silent_on_remove:1,
find_copies_harder:1,
follow_renames:1,
color_diff:1,
color_diff_words:1,
has_changes:1,

View File

@@ -119,6 +119,21 @@ static int is_exact_match(struct diff_filespec *src,
return 0;
}
static int basename_same(struct diff_filespec *src, struct diff_filespec *dst)
{
int src_len = strlen(src->path), dst_len = strlen(dst->path);
while (src_len && dst_len) {
char c1 = src->path[--src_len];
char c2 = dst->path[--dst_len];
if (c1 != c2)
return 0;
if (c1 == '/')
return 1;
}
return (!src_len || src->path[src_len - 1] == '/') &&
(!dst_len || dst->path[dst_len - 1] == '/');
}
struct diff_score {
int src; /* index in rename_src */
int dst; /* index in rename_dst */
@@ -186,8 +201,11 @@ static int estimate_similarity(struct diff_filespec *src,
*/
if (!dst->size)
score = 0; /* should not happen */
else
else {
score = (int)(src_copied * MAX_SCORE / max_size);
if (basename_same(src, dst))
score++;
}
return score;
}
@@ -295,9 +313,22 @@ void diffcore_rename(struct diff_options *options)
if (rename_dst[i].pair)
continue; /* dealt with an earlier round */
for (j = 0; j < rename_src_nr; j++) {
int k;
struct diff_filespec *one = rename_src[j].one;
if (!is_exact_match(one, two, contents_too))
continue;
/* see if there is a basename match, too */
for (k = j; k < rename_src_nr; k++) {
one = rename_src[k].one;
if (basename_same(one, two) &&
is_exact_match(one, two,
contents_too)) {
j = k;
break;
}
}
record_rename_pair(i, j, (int)MAX_SCORE);
rename_count++;
break; /* we are done with this entry */

28
dir.c
View File

@@ -275,7 +275,6 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len) {
struct dir_entry *ent;
ent = xmalloc(sizeof(*ent) + len + 1);
ent->ignored = ent->ignored_dir = 0;
ent->len = len;
memcpy(ent->name, pathname, len);
ent->name[len] = 0;
@@ -287,10 +286,19 @@ struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int
if (cache_name_pos(pathname, len) >= 0)
return NULL;
ALLOC_GROW(dir->entries, dir->nr, dir->alloc);
ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
}
struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
{
if (cache_name_pos(pathname, len) >= 0)
return NULL;
ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc);
return dir->ignored[dir->ignored_nr++] = dir_entry_new(pathname, len);
}
enum exist_status {
index_nonexistent = 0,
index_directory,
@@ -423,6 +431,18 @@ static int simplify_away(const char *path, int pathlen, const struct path_simpli
return 0;
}
static int in_pathspec(const char *path, int len, const struct path_simplify *simplify)
{
if (simplify) {
for (; simplify->path; simplify++) {
if (len == simplify->len
&& !memcmp(path, simplify->path, len))
return 1;
}
}
return 0;
}
/*
* Read a directory tree. We currently ignore anything but
* directories, regular files and symlinks. That's because git
@@ -463,6 +483,9 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
continue;
exclude = excluded(dir, fullname);
if (exclude && dir->collect_ignored
&& in_pathspec(fullname, baselen + len, simplify))
dir_add_ignored(dir, fullname, baselen + len);
if (exclude != dir->show_ignored) {
if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
continue;
@@ -609,6 +632,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
read_directory_recursive(dir, path, base, baselen, 0, simplify);
free_simplify(simplify);
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
return dir->nr;
}

9
dir.h
View File

@@ -13,9 +13,7 @@
struct dir_entry {
unsigned int ignored : 1;
unsigned int ignored_dir : 1;
unsigned int len : 30;
unsigned int len;
char name[FLEX_ARRAY]; /* more */
};
@@ -31,11 +29,14 @@ struct exclude_list {
struct dir_struct {
int nr, alloc;
int ignored_nr, ignored_alloc;
unsigned int show_ignored:1,
show_other_directories:1,
hide_empty_directories:1,
no_gitlinks:1;
no_gitlinks:1,
collect_ignored:1;
struct dir_entry **entries;
struct dir_entry **ignored;
/* Exclude info */
const char *exclude_per_dir;

View File

@@ -377,6 +377,13 @@ then
)
)
# Upstream URL
git-config remote."$origin".url "$repo" &&
# Set up the mappings to track the remote branches.
git-config remote."$origin".fetch \
"+refs/heads/*:$remote_top/*" '^$' &&
# Write out remote.$origin config, and update our "$head_points_at".
case "$head_points_at" in
?*)
@@ -384,21 +391,20 @@ then
git-symbolic-ref HEAD "refs/heads/$head_points_at" &&
# Tracking branch for the primary branch at the remote.
origin_track="$remote_top/$head_points_at" &&
git-update-ref HEAD "$head_sha1" &&
# Upstream URL
git-config remote."$origin".url "$repo" &&
# Set up the mappings to track the remote branches.
git-config remote."$origin".fetch \
"+refs/heads/*:$remote_top/*" '^$' &&
rm -f "refs/remotes/$origin/HEAD"
git-symbolic-ref "refs/remotes/$origin/HEAD" \
"refs/remotes/$origin/$head_points_at" &&
git-config branch."$head_points_at".remote "$origin" &&
git-config branch."$head_points_at".merge "refs/heads/$head_points_at"
;;
'')
# Source had detached HEAD pointing nowhere
git-update-ref --no-deref HEAD "$head_sha1" &&
rm -f "refs/remotes/$origin/HEAD"
;;
esac
case "$no_checkout" in

View File

@@ -298,6 +298,22 @@ static inline ssize_t xwrite(int fd, const void *buf, size_t len)
}
}
static inline int xdup(int fd)
{
int ret = dup(fd);
if (ret < 0)
die("dup failed: %s", strerror(errno));
return ret;
}
static inline FILE *xfdopen(int fd, const char *mode)
{
FILE *stream = fdopen(fd, mode);
if (stream == NULL)
die("Out of memory? fdopen failed: %s", strerror(errno));
return stream;
}
static inline size_t xsize_t(off_t len)
{
return (size_t)len;

View File

@@ -774,7 +774,6 @@ sub commit {
or die "Cannot write branch $branch for update: $!\n";
if ($tag) {
my ($in, $out) = ('','');
my ($xtag) = $tag;
$xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
$xtag =~ tr/_/\./ if ( $opt_u );
@@ -1008,7 +1007,7 @@ if ($orig_branch) {
if ($opt_r && $opt_o ne 'HEAD');
system('git-update-ref', 'HEAD', "$orig_branch");
unless ($opt_i) {
system('git checkout');
system('git checkout -f');
die "checkout failed: $?\n" if $?;
}
}

441
git-filter-branch.sh Normal file
View File

@@ -0,0 +1,441 @@
#!/bin/sh
#
# Rewrite revision history
# Copyright (c) Petr Baudis, 2006
# Minimal changes to "port" it to core-git (c) Johannes Schindelin, 2007
#
# Lets you rewrite GIT revision history by creating a new branch from
# your current branch by applying custom filters on each revision.
# Those filters can modify each tree (e.g. removing a file or running
# a perl rewrite on all files) or information about each commit.
# Otherwise, all information (including original commit times or merge
# information) will be preserved.
#
# The command takes the new branch name as a mandatory argument and
# the filters as optional arguments. If you specify no filters, the
# commits will be recommitted without any changes, which would normally
# have no effect and result with the new branch pointing to the same
# branch as your current branch. (Nevertheless, this may be useful in
# the future for compensating for some Git bugs or such, therefore
# such a usage is permitted.)
#
# WARNING! The rewritten history will have different ids for all the
# objects and will not converge with the original branch. You will not
# be able to easily push and distribute the rewritten branch. Please do
# not use this command if you do not know the full implications, and
# avoid using it anyway - do not do what a simple single commit on top
# of the current version would fix.
#
# Always verify that the rewritten version is correct before disposing
# the original branch.
#
# Note that since this operation is extensively I/O expensive, it might
# be a good idea to do it off-disk, e.g. on tmpfs. Reportedly the speedup
# is very noticeable.
#
# OPTIONS
# -------
# -d TEMPDIR:: The path to the temporary tree used for rewriting
# When applying a tree filter, the command needs to temporary
# checkout the tree to some directory, which may consume
# considerable space in case of large projects. By default it
# does this in the '.git-rewrite/' directory but you can override
# that choice by this parameter.
#
# Filters
# ~~~~~~~
# The filters are applied in the order as listed below. The COMMAND
# argument is always evaluated in shell using the 'eval' command.
# The $GIT_COMMIT environment variable is permanently set to contain
# the id of the commit being rewritten. The author/committer environment
# variables are set before the first filter is run.
#
# A 'map' function is available that takes an "original sha1 id" argument
# and outputs a "rewritten sha1 id" if the commit has been already
# rewritten, fails otherwise; the 'map' function can return several
# ids on separate lines if your commit filter emitted multiple commits
# (see below).
#
# --env-filter COMMAND:: The filter for modifying environment
# This is the filter for modifying the environment in which
# the commit will be performed. Specifically, you might want
# to rewrite the author/committer name/email/time environment
# variables (see `git-commit` for details). Do not forget to
# re-export the variables.
#
# --tree-filter COMMAND:: The filter for rewriting tree (and its contents)
# This is the filter for rewriting the tree and its contents.
# The COMMAND argument is evaluated in shell with the working
# directory set to the root of the checked out tree. The new tree
# is then used as-is (new files are auto-added, disappeared files
# are auto-removed - .gitignore files nor any other ignore rules
# HAVE NO EFFECT!).
#
# --index-filter COMMAND:: The filter for rewriting index
# This is the filter for rewriting the Git's directory index.
# It is similar to the tree filter but does not check out the
# tree, which makes it much faster. However, you must use the
# lowlevel Git index manipulation commands to do your work.
#
# --parent-filter COMMAND:: The filter for rewriting parents
# This is the filter for rewriting the commit's parent list.
# It will receive the parent string on stdin and shall output
# the new parent string on stdout. The parent string is in
# format accepted by `git-commit-tree`: empty for initial
# commit, "-p parent" for a normal commit and "-p parent1
# -p parent2 -p parent3 ..." for a merge commit.
#
# --msg-filter COMMAND:: The filter for rewriting commit message
# This is the filter for rewriting the commit messages.
# The COMMAND argument is evaluated in shell with the original
# commit message on standard input; its standard output is
# is used as the new commit message.
#
# --commit-filter COMMAND:: The filter for performing the commit
# If this filter is passed, it will be called instead of the
# `git-commit-tree` command, with those arguments:
#
# TREE_ID [-p PARENT_COMMIT_ID]...
#
# and the log message on stdin. The commit id is expected on
# stdout. As a special extension, the commit filter may emit
# multiple commit ids; in that case, all of them will be used
# as parents instead of the original commit in further commits.
#
# --tag-name-filter COMMAND:: The filter for rewriting tag names.
# If this filter is passed, it will be called for every tag ref
# that points to a rewritten object (or to a tag object which
# points to a rewritten object). The original tag name is passed
# via standard input, and the new tag name is expected on standard
# output.
#
# The original tags are not deleted, but can be overwritten;
# use "--tag-name-filter=cat" to simply update the tags. In this
# case, be very careful and make sure you have the old tags
# backed up in case the conversion has run afoul.
#
# Note that there is currently no support for proper rewriting of
# tag objects; in layman terms, if the tag has a message or signature
# attached, the rewritten tag won't have it. Sorry. (It is by
# definition impossible to preserve signatures at any rate, though.)
#
# --subdirectory-filter DIRECTORY:: Only regard the history, as seen by
# the given subdirectory. The result will contain that directory as
# its project root.
#
# EXAMPLE USAGE
# -------------
# Suppose you want to remove a file (containing confidential information
# or copyright violation) from all commits:
#
# git-filter-branch --tree-filter 'rm filename' newbranch
#
# A significantly faster version:
#
# git-filter-branch --index-filter 'git-update-index --remove filename' newbranch
#
# Now, you will get the rewritten history saved in the branch 'newbranch'
# (your current branch is left untouched).
#
# To "etch-graft" a commit to the revision history (set a commit to be
# the parent of the current initial commit and propagate that):
#
# git-filter-branch --parent-filter sed\ 's/^$/-p graftcommitid/' newbranch
#
# (if the parent string is empty - therefore we are dealing with the
# initial commit - add graftcommit as a parent). Note that this assumes
# history with a single root (that is, no git-merge without common ancestors
# happened). If this is not the case, use:
#
# git-filter-branch --parent-filter 'cat; [ "$GIT_COMMIT" = "COMMIT" ] && echo "-p GRAFTCOMMIT"' newbranch
#
# To remove commits authored by "Darl McBribe" from the history:
#
# git-filter-branch --commit-filter 'if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ]; then shift; while [ -n "$1" ]; do shift; echo "$1"; shift; done; else git-commit-tree "$@"; fi' newbranch
#
# (the shift magic first throws away the tree id and then the -p
# parameters). Note that this handles merges properly! In case Darl
# committed a merge between P1 and P2, it will be propagated properly
# and all children of the merge will become merge commits with P1,P2
# as their parents instead of the merge commit.
#
# To restrict rewriting to only part of the history, specify a revision
# range in addition to the new branch name. The new branch name will
# point to the top-most revision that a 'git rev-list' of this range
# will print.
#
# Consider this history:
#
# D--E--F--G--H
# / /
# A--B-----C
#
# To rewrite commits D,E,F,G,H, use:
#
# git-filter-branch ... new-H C..H
#
# To rewrite commits E,F,G,H, use one of these:
#
# git-filter-branch ... new-H C..H --not D
# git-filter-branch ... new-H D..H --not C
#
# To move the whole tree into a subdirectory, or remove it from there:
#
# git-filter-branch --index-filter \
# 'git-ls-files -s | sed "s-\t-&newsubdir/-" |
# GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
# git-update-index --index-info &&
# mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' directorymoved
# Testsuite: TODO
set -e
USAGE="git-filter-branch [-d TEMPDIR] [FILTERS] DESTBRANCH [REV-RANGE]"
. git-sh-setup
map()
{
# if it was not rewritten, take the original
test -r "$workdir/../map/$1" || echo "$1"
cat "$workdir/../map/$1"
}
# When piped a commit, output a script to set the ident of either
# "author" or "committer
set_ident () {
lid="$(echo "$1" | tr "A-Z" "a-z")"
uid="$(echo "$1" | tr "a-z" "A-Z")"
pick_id_script='
/^'$lid' /{
s/'\''/'\''\\'\'\''/g
h
s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
s/'\''/'\''\'\'\''/g
s/.*/export GIT_'$uid'_NAME='\''&'\''/p
g
s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
s/'\''/'\''\'\'\''/g
s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p
g
s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
s/'\''/'\''\'\'\''/g
s/.*/export GIT_'$uid'_DATE='\''&'\''/p
q
}
'
LANG=C LC_ALL=C sed -ne "$pick_id_script"
# Ensure non-empty id name.
echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
}
tempdir=.git-rewrite
filter_env=
filter_tree=
filter_index=
filter_parent=
filter_msg=cat
filter_commit='git-commit-tree "$@"'
filter_tag_name=
filter_subdir=
while case "$#" in 0) usage;; esac
do
case "$1" in
--)
shift
break
;;
-*)
;;
*)
break;
esac
# all switches take one argument
ARG="$1"
case "$#" in 1) usage ;; esac
shift
OPTARG="$1"
shift
case "$ARG" in
-d)
tempdir="$OPTARG"
;;
--env-filter)
filter_env="$OPTARG"
;;
--tree-filter)
filter_tree="$OPTARG"
;;
--index-filter)
filter_index="$OPTARG"
;;
--parent-filter)
filter_parent="$OPTARG"
;;
--msg-filter)
filter_msg="$OPTARG"
;;
--commit-filter)
filter_commit="$OPTARG"
;;
--tag-name-filter)
filter_tag_name="$OPTARG"
;;
--subdirectory-filter)
filter_subdir="$OPTARG"
;;
*)
usage
;;
esac
done
dstbranch="$1"
shift
test -n "$dstbranch" || die "missing branch name"
git-show-ref "refs/heads/$dstbranch" 2> /dev/null &&
die "branch $dstbranch already exists"
test ! -e "$tempdir" || die "$tempdir already exists, please remove it"
mkdir -p "$tempdir/t"
cd "$tempdir/t"
workdir="$(pwd)"
case "$GIT_DIR" in
/*)
;;
*)
export GIT_DIR="$(pwd)/../../$GIT_DIR"
;;
esac
export GIT_INDEX_FILE="$(pwd)/../index"
git-read-tree # seed the index file
ret=0
mkdir ../map # map old->new commit ids for rewriting parents
case "$filter_subdir" in
"")
git-rev-list --reverse --topo-order --default HEAD \
--parents "$@"
;;
*)
git-rev-list --reverse --topo-order --default HEAD \
--parents --full-history "$@" -- "$filter_subdir"
esac > ../revs
commits=$(cat ../revs | wc -l | tr -d " ")
test $commits -eq 0 && die "Found nothing to rewrite"
i=0
while read commit parents; do
i=$(($i+1))
printf "$commit ($i/$commits) "
case "$filter_subdir" in
"")
git-read-tree -i -m $commit
;;
*)
git-read-tree -i -m $commit:"$filter_subdir"
esac
export GIT_COMMIT=$commit
git-cat-file commit "$commit" >../commit
eval "$(set_ident AUTHOR <../commit)"
eval "$(set_ident COMMITTER <../commit)"
eval "$filter_env" < /dev/null
if [ "$filter_tree" ]; then
git-checkout-index -f -u -a
# files that $commit removed are now still in the working tree;
# remove them, else they would be added again
git-ls-files -z --others | xargs -0 rm -f
eval "$filter_tree" < /dev/null
git-diff-index -r $commit | cut -f 2- | tr '\n' '\0' | \
xargs -0 git-update-index --add --replace --remove
git-ls-files -z --others | \
xargs -0 git-update-index --add --replace --remove
fi
eval "$filter_index" < /dev/null
parentstr=
for parent in $parents; do
for reparent in $(map "$parent"); do
parentstr="$parentstr -p $reparent"
done
done
if [ "$filter_parent" ]; then
parentstr="$(echo "$parentstr" | eval "$filter_parent")"
fi
sed -e '1,/^$/d' <../commit | \
eval "$filter_msg" | \
sh -c "$filter_commit" git-commit-tree $(git-write-tree) $parentstr | \
tee ../map/$commit
done <../revs
src_head=$(tail -n 1 ../revs | sed -e 's/ .*//')
target_head=$(head -n 1 ../map/$src_head)
case "$target_head" in
'')
echo Nothing rewritten
;;
*)
git-update-ref refs/heads/"$dstbranch" $target_head
if [ $(cat ../map/$src_head | wc -l) -gt 1 ]; then
echo "WARNING: Your commit filter caused the head commit to expand to several rewritten commits. Only the first such commit was recorded as the current $dstbranch head but you will need to resolve the situation now (probably by manually merging the other commits). These are all the commits:" >&2
sed 's/^/ /' ../map/$src_head >&2
ret=1
fi
;;
esac
if [ "$filter_tag_name" ]; then
git-for-each-ref --format='%(objectname) %(objecttype) %(refname)' refs/tags |
while read sha1 type ref; do
ref="${ref#refs/tags/}"
# XXX: Rewrite tagged trees as well?
if [ "$type" != "commit" -a "$type" != "tag" ]; then
continue;
fi
if [ "$type" = "tag" ]; then
# Dereference to a commit
sha1t="$sha1"
sha1="$(git-rev-parse "$sha1"^{commit} 2>/dev/null)" || continue
fi
[ -f "../map/$sha1" ] || continue
new_sha1="$(cat "../map/$sha1")"
export GIT_COMMIT="$sha1"
new_ref="$(echo "$ref" | eval "$filter_tag_name")"
echo "$ref -> $new_ref ($sha1 -> $new_sha1)"
if [ "$type" = "tag" ]; then
# Warn that we are not rewriting the tag object itself.
warn "unreferencing tag object $sha1t"
fi
git-update-ref "refs/tags/$new_ref" "$new_sha1"
done
fi
cd ../..
rm -rf "$tempdir"
echo "Rewritten history saved to the $dstbranch branch"
exit $ret

View File

@@ -31,11 +31,35 @@ ifndef INSTALL
INSTALL = install
endif
INSTALL_D0 = $(INSTALL) -d -m755 # space is required here
INSTALL_D1 =
INSTALL_R0 = $(INSTALL) -m644 # space is required here
INSTALL_R1 =
INSTALL_X0 = $(INSTALL) -m755 # space is required here
INSTALL_X1 =
INSTALL_L0 = rm -f # space is required here
INSTALL_L1 = && ln # space is required here
INSTALL_L2 =
INSTALL_L3 =
ifndef V
QUIET_GEN = @echo ' ' GEN $@;
QUIET_BUILT_IN = @echo ' ' BUILTIN $@;
QUIET_INDEX = @echo ' ' INDEX $(dir $@);
QUIET = @
QUIET_GEN = $(QUIET)echo ' ' GEN $@ &&
QUIET_BUILT_IN = $(QUIET)echo ' ' BUILTIN $@ &&
QUIET_INDEX = $(QUIET)echo ' ' INDEX $(dir $@) &&
QUIET_2DEVNULL = 2>/dev/null
INSTALL_D0 = dir=
INSTALL_D1 = && echo ' ' DEST $$dir && $(INSTALL) -d -m755 "$$dir"
INSTALL_R0 = src=
INSTALL_R1 = && echo ' ' INSTALL 644 `basename $$src` && $(INSTALL) -m644 $$src
INSTALL_X0 = src=
INSTALL_X1 = && echo ' ' INSTALL 755 `basename $$src` && $(INSTALL) -m755 $$src
INSTALL_L0 = dst=
INSTALL_L1 = && src=
INSTALL_L2 = && dst=
INSTALL_L3 = && echo ' ' 'LINK ' `basename "$$dst"` '->' `basename "$$src"` && rm -f "$$dst" && ln "$$src" "$$dst"
endif
TCL_PATH ?= tclsh
@@ -115,12 +139,12 @@ GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
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)' ;)
$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
$(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(INSTALL_D1)
$(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)'
$(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true
dist-version:
@mkdir -p $(TARDIR)

View File

@@ -64,15 +64,17 @@ Options:
email sent, rather than to the first email sent.
Defaults to on.
--no-signed-off-cc Suppress the automatic addition of email addresses
that appear in Signed-off-by: or Cc: lines to the cc:
list. Note: Using this option is not recommended.
--signed-off-cc Automatically add email addresses that appear in
Signed-off-by: or Cc: lines to the cc: list. Defaults to on.
--smtp-server If set, specifies the outgoing SMTP server to use.
Defaults to localhost.
--suppress-from Suppress sending emails to yourself if your address
appears in a From: line.
appears in a From: line. Defaults to off.
--thread Specify that the "In-Reply-To:" header should be set on all
emails. Defaults to on.
--quiet Make git-send-email less verbose. One line per email
should be all that is output.
@@ -137,9 +139,6 @@ my $compose_filename = ".msg.$$";
my (@to,@cc,@initial_cc,@bcclist,@xh,
$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
# Behavior modification variables
my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc,
$dry_run) = (1, 0, 0, 0, 0);
my $smtp_server;
my $envelope_sender;
@@ -154,9 +153,22 @@ if ($@) {
$term = new FakeTerm "$@: going non-interactive";
}
my $def_chain = $repo->config_bool('sendemail.chainreplyto');
if (defined $def_chain and not $def_chain) {
$chain_reply_to = 0;
# Behavior modification variables
my ($quiet, $dry_run) = (0, 0);
# Variables with corresponding config settings
my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc);
my %config_settings = (
"thread" => [\$thread, 1],
"chainreplyto" => [\$chain_reply_to, 1],
"suppressfrom" => [\$suppress_from, 0],
"signedoffcc" => [\$signed_off_cc, 1],
);
foreach my $setting (keys %config_settings) {
my $config = $repo->config_bool("sendemail.$setting");
${$config_settings{$setting}->[0]} = (defined $config) ? $config : $config_settings{$setting}->[1];
}
@bcclist = $repo->config('sendemail.bcc');
@@ -177,10 +189,11 @@ my $rc = GetOptions("from=s" => \$from,
"smtp-server=s" => \$smtp_server,
"compose" => \$compose,
"quiet" => \$quiet,
"suppress-from" => \$suppress_from,
"no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
"suppress-from!" => \$suppress_from,
"signed-off-cc|signed-off-by-cc!" => \$signed_off_cc,
"dry-run" => \$dry_run,
"envelope-sender=s" => \$envelope_sender,
"thread!" => \$thread,
);
unless ($rc) {
@@ -287,7 +300,7 @@ if (!defined $initial_subject && $compose) {
$prompting++;
}
if (!defined $initial_reply_to && $prompting) {
if ($thread && !defined $initial_reply_to && $prompting) {
do {
$_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ",
$initial_reply_to);
@@ -412,13 +425,21 @@ sub extract_valid_address {
# 1 second since the last time we were called.
# We'll setup a template for the message id, using the "from" address:
my $message_id_from = extract_valid_address($from);
my $message_id_template = "<%s-git-send-email-$message_id_from>";
sub make_message_id
{
my $date = time;
my $pseudo_rand = int (rand(4200));
my $du_part;
for ($from, $committer, $author) {
$du_part = extract_valid_address($_);
last if ($du_part ne '');
}
if ($du_part eq '') {
use Sys::Hostname qw();
$du_part = 'user@' . Sys::Hostname::hostname();
}
my $message_id_template = "<%s-git-send-email-$du_part>";
$message_id = sprintf $message_id_template, "$date$pseudo_rand";
#print "new message id = $message_id\n"; # Was useful for debugging
}
@@ -467,6 +488,8 @@ sub send_message
$ccline = "\nCc: $cc";
}
$from = sanitize_address_rfc822($from);
make_message_id();
my $header = "From: $from
To: $to${ccline}
Subject: $subject
@@ -474,7 +497,7 @@ Date: $date
Message-Id: $message_id
X-Mailer: git-send-email $gitversion
";
if ($reply_to) {
if ($thread && $reply_to) {
$header .= "In-Reply-To: $reply_to\n";
$header .= "References: $references\n";
@@ -533,7 +556,6 @@ X-Mailer: git-send-email $gitversion
$reply_to = $initial_reply_to;
$references = $initial_reply_to || '';
make_message_id();
$subject = $initial_subject;
foreach my $t (@files) {
@@ -600,7 +622,7 @@ foreach my $t (@files) {
}
} else {
$message .= $_;
if (/^(Signed-off-by|Cc): (.*)$/i && !$no_signed_off_cc) {
if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) {
my $c = $2;
chomp $c;
push @cc, $c;
@@ -627,7 +649,6 @@ foreach my $t (@files) {
$references = "$message_id";
}
}
make_message_id();
}
if ($compose) {

View File

@@ -867,34 +867,14 @@ sub commit {
or die "Cannot write branch $dest for update: $!\n";
}
if($tag) {
my($in, $out) = ('','');
if ($tag) {
$last_rev = "-" if %$changed_paths;
# the tag was 'complex', i.e. did not refer to a "real" revision
$dest =~ tr/_/\./ if $opt_u;
$branch = $dest;
my $pid = open2($in, $out, 'git-mktag');
print $out ("object $cid\n".
"type commit\n".
"tag $dest\n".
"tagger $committer_name <$committer_email> 0 +0000\n") and
close($out)
or die "Cannot create tag object $dest: $!\n";
my $tagobj = <$in>;
chomp $tagobj;
if ( !close($in) or waitpid($pid, 0) != $pid or
$? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
die "Cannot create tag object $dest: $!\n";
}
open(C,">$git_dir/refs/tags/$dest") and
print C ("$tagobj\n") and
close(C)
or die "Cannot create tag $branch: $!\n";
system('git-tag', $dest, $cid) == 0
or die "Cannot create tag $dest: $!\n";
print "Created tag '$dest' on '$branch'\n" if $opt_v;
}

View File

@@ -164,11 +164,10 @@ rm -rf $RPM_BUILD_ROOT
%{_bindir}/git-gui
%{_bindir}/git-citool
%{_datadir}/git-gui/
# Not Yet...
# %{!?_without_docs: %{_mandir}/man1/git-gui.1}
# %{!?_without_docs: %doc Documentation/git-gui.html}
# %{!?_without_docs: %{_mandir}/man1/git-citool.1}
# %{!?_without_docs: %doc Documentation/git-citool.html}
%{!?_without_docs: %{_mandir}/man1/git-gui.1*}
%{!?_without_docs: %doc Documentation/git-gui.html}
%{!?_without_docs: %{_mandir}/man1/git-citool.1*}
%{!?_without_docs: %doc Documentation/git-citool.html}
%files -n gitk
%defattr(-,root,root)
@@ -188,6 +187,12 @@ rm -rf $RPM_BUILD_ROOT
%{!?_without_docs: %doc Documentation/technical}
%changelog
* Tue Jun 26 2007 Quy Tonthat <qtonthat@gmail.com>
- Fixed problems looking for wrong manpages.
* Thu Jun 21 2007 Shawn O. Pearce <spearce@spearce.org>
- Added documentation files for git-gui
* Tue May 13 2007 Quy Tonthat <qtonthat@gmail.com>
- Added lib files for git-gui
- Added Documentation/technical (As needed by Git Users Manual)

3021
gitk

File diff suppressed because it is too large Load Diff

View File

@@ -634,7 +634,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
write_or_die(keep_fd, keep_msg, keep_msg_len);
write_or_die(keep_fd, "\n", 1);
}
close(keep_fd);
if (close(keep_fd) != 0)
die("cannot write keep file");
report = "keep";
}
}

View File

@@ -44,33 +44,33 @@ void interp_clear_table(struct interp *table, int ninterps)
* { "%%", "%"},
* }
*
* Returns 1 on a successful substitution pass that fits in result,
* Returns 0 on a failed or overflowing substitution pass.
* Returns 0 on a successful substitution pass that fits in result,
* Returns a number of bytes needed to hold the full substituted
* string otherwise.
*/
int interpolate(char *result, int reslen,
unsigned long interpolate(char *result, unsigned long reslen,
const char *orig,
const struct interp *interps, int ninterps)
{
const char *src = orig;
char *dest = result;
int newlen = 0;
unsigned long newlen = 0;
const char *name, *value;
int namelen, valuelen;
unsigned long namelen, valuelen;
int i;
char c;
memset(result, 0, reslen);
while ((c = *src) && newlen < reslen - 1) {
while ((c = *src)) {
if (c == '%') {
/* Try to match an interpolation string. */
for (i = 0; i < ninterps; i++) {
name = interps[i].name;
namelen = strlen(name);
if (strncmp(src, name, namelen) == 0) {
if (strncmp(src, name, namelen) == 0)
break;
}
}
/* Check for valid interpolation. */
@@ -78,29 +78,25 @@ int interpolate(char *result, int reslen,
value = interps[i].value;
valuelen = strlen(value);
if (newlen + valuelen < reslen - 1) {
if (newlen + valuelen + 1 < reslen) {
/* Substitute. */
strncpy(dest, value, valuelen);
newlen += valuelen;
dest += valuelen;
src += namelen;
} else {
/* Something's not fitting. */
return 0;
}
} else {
/* Skip bogus interpolation. */
*dest++ = *src++;
newlen++;
newlen += valuelen;
src += namelen;
continue;
}
} else {
/* Straight copy one non-interpolation character. */
*dest++ = *src++;
newlen++;
}
/* Straight copy one non-interpolation character. */
if (newlen + 1 < reslen)
*dest++ = *src;
src++;
newlen++;
}
return newlen < reslen - 1;
if (newlen + 1 < reslen)
return 0;
else
return newlen + 2;
}

View File

@@ -19,8 +19,8 @@ struct interp {
extern void interp_set_entry(struct interp *table, int slot, const char *value);
extern void interp_clear_table(struct interp *table, int ninterps);
extern int interpolate(char *result, int reslen,
const char *orig,
const struct interp *interps, int ninterps);
extern unsigned long interpolate(char *result, unsigned long reslen,
const char *orig,
const struct interp *interps, int ninterps);
#endif /* INTERPOLATE_H */

View File

@@ -79,16 +79,25 @@ static int detect_any_signoff(char *letter, int size)
return seen_head && seen_name;
}
static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
static unsigned long append_signoff(char **buf_p, unsigned long *buf_sz_p,
unsigned long at, const char *signoff)
{
static const char signed_off_by[] = "Signed-off-by: ";
int signoff_len = strlen(signoff);
size_t signoff_len = strlen(signoff);
int has_signoff = 0;
char *cp = buf;
char *cp;
char *buf;
unsigned long buf_sz;
/* Do we have enough space to add it? */
if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3)
return at;
buf = *buf_p;
buf_sz = *buf_sz_p;
if (buf_sz <= at + strlen(signed_off_by) + signoff_len + 3) {
buf_sz += strlen(signed_off_by) + signoff_len + 3;
buf = xrealloc(buf, buf_sz);
*buf_p = buf;
*buf_sz_p = buf_sz;
}
cp = buf;
/* First see if we already have the sign-off by the signer */
while ((cp = strstr(cp, signed_off_by))) {
@@ -133,7 +142,8 @@ static unsigned int digits_in_number(unsigned int number)
void show_log(struct rev_info *opt, const char *sep)
{
static char this_header[16384];
char *msgbuf = NULL;
unsigned long msgbuf_len = 0;
struct log_info *log = opt->loginfo;
struct commit *commit = log->commit, *parent = log->parent;
int abbrev = opt->diffopt.abbrev;
@@ -278,14 +288,15 @@ void show_log(struct rev_info *opt, const char *sep)
/*
* And then the pretty-printed message itself
*/
len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header,
sizeof(this_header), abbrev, subject,
len = pretty_print_commit(opt->commit_format, commit, ~0u,
&msgbuf, &msgbuf_len, abbrev, subject,
extra_headers, opt->date_mode);
if (opt->add_signoff)
len = append_signoff(this_header, sizeof(this_header), len,
len = append_signoff(&msgbuf, &msgbuf_len, len,
opt->add_signoff);
printf("%s%s%s", this_header, extra, sep);
printf("%s%s%s", msgbuf, extra, sep);
free(msgbuf);
}
int log_tree_diff_flush(struct rev_info *opt)

6
refs.c
View File

@@ -1124,8 +1124,7 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1;
written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
free(logrec);
close(logfd);
if (written != len)
if (close(logfd) != 0 || written != len)
return error("Unable to append to %s", log_file);
return 0;
}
@@ -1222,8 +1221,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
goto error_free_return;
}
written = write_in_full(fd, ref, len);
close(fd);
if (written != len) {
if (close(fd) != 0 || written != len) {
error("Unable to write to %s", lockpath);
goto error_unlink_return;
}

View File

@@ -1230,7 +1230,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (revs->prune_data) {
diff_tree_setup_paths(revs->prune_data, &revs->pruning);
revs->prune_fn = try_to_simplify_commit;
/* Can't prune commits with rename following: the paths change.. */
if (!revs->diffopt.follow_renames)
revs->prune_fn = try_to_simplify_commit;
if (!revs->full_diff)
diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
}

View File

@@ -519,4 +519,34 @@ git config --list > result
test_expect_success 'value continued on next line' 'cmp result expect'
cat > .git/config <<\EOF
[section "sub=section"]
val1 = foo=bar
val2 = foo\nbar
val3 = \n\n
val4 =
val5
EOF
cat > expect <<\EOF
section.sub=section.val1
foo=barQsection.sub=section.val2
foo
barQsection.sub=section.val3
Qsection.sub=section.val4
Qsection.sub=section.val5Q
EOF
git config --null --list | tr '[\000]' 'Q' > result
echo >>result
test_expect_success '--null --list' 'cmp result expect'
git config --null --get-regexp 'val[0-9]' | tr '[\000]' 'Q' > result
echo >>result
test_expect_success '--null --get-regexp' 'cmp result expect'
test_done

View File

@@ -64,4 +64,17 @@ test_expect_success \
'validate the output.' \
'compare_diff_patch current expected'
test_expect_success 'favour same basenames over different ones' '
cp path1 another-path &&
git add another-path &&
git commit -m 1 &&
git rm path1 &&
mkdir subdir &&
git mv another-path subdir/path1 &&
git runstatus | grep "renamed: .*path1 -> subdir/path1"'
test_expect_success 'favour same basenames even with minor differences' '
git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
git runstatus | grep "renamed: .*path1 -> subdir/path1"'
test_done

110
t/t7003-filter-branch.sh Executable file
View File

@@ -0,0 +1,110 @@
#!/bin/sh
test_description='git-filter-branch'
. ./test-lib.sh
make_commit () {
lower=$(echo $1 | tr A-Z a-z)
echo $lower > $lower
git add $lower
test_tick
git commit -m $1
git tag $1
}
test_expect_success 'setup' '
make_commit A
make_commit B
git checkout -b branch B
make_commit D
make_commit E
git checkout master
make_commit C
git checkout branch
git merge C
git tag F
make_commit G
make_commit H
'
H=$(git-rev-parse H)
test_expect_success 'rewrite identically' '
git-filter-branch H2
'
test_expect_success 'result is really identical' '
test $H = $(git-rev-parse H2)
'
test_expect_success 'rewrite, renaming a specific file' '
git-filter-branch --tree-filter "mv d doh || :" H3
'
test_expect_success 'test that the file was renamed' '
test d = $(git show H3:doh)
'
git tag oldD H3~4
test_expect_success 'rewrite one branch, keeping a side branch' '
git-filter-branch --tree-filter "mv b boh || :" modD D..oldD
'
test_expect_success 'common ancestor is still common (unchanged)' '
test "$(git-merge-base modD D)" = "$(git-rev-parse B)"
'
test_expect_success 'filter subdirectory only' '
mkdir subdir &&
touch subdir/new &&
git add subdir/new &&
test_tick &&
git commit -m "subdir" &&
echo H > a &&
test_tick &&
git commit -m "not subdir" a &&
echo A > subdir/new &&
test_tick &&
git commit -m "again subdir" subdir/new &&
git rm a &&
test_tick &&
git commit -m "again not subdir" &&
git-filter-branch --subdirectory-filter subdir sub
'
test_expect_success 'subdirectory filter result looks okay' '
test 2 = $(git-rev-list sub | wc -l) &&
git show sub:new &&
! git show sub:subdir
'
test_expect_success 'setup and filter history that requires --full-history' '
git checkout master &&
mkdir subdir &&
echo A > subdir/new &&
git add subdir/new &&
test_tick &&
git commit -m "subdir on master" subdir/new &&
git rm a &&
test_tick &&
git commit -m "again subdir on master" &&
git merge branch &&
git-filter-branch --subdirectory-filter subdir sub-master
'
test_expect_success 'subdirectory filter result looks okay' '
test 3 = $(git-rev-list -1 --parents sub-master | wc -w) &&
git show sub-master^:new &&
git show sub-master^2:new &&
! git show sub:subdir
'
test_expect_success 'use index-filter to move into a subdirectory' '
git-filter-branch --index-filter \
"git-ls-files -s | sed \"s-\\t-&newsubdir/-\" |
GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
git-update-index --index-info &&
mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" directorymoved &&
test -z "$(git diff HEAD directorymoved:newsubdir)"'
test_done

View File

@@ -180,8 +180,8 @@ test_expect_success 'req_Root (export-all)' \
tail -n1 log | grep -q "^I LOVE YOU$"'
test_expect_failure 'req_Root failure (export-all w/o whitelist)' \
'cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1
|| false'
'cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 ||
false'
test_expect_success 'req_Root (everything together)' \
'cat request-base | git-cvsserver --export-all --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&

View File

@@ -60,6 +60,12 @@ gitweb_run () {
. ./test-lib.sh
perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
test_expect_success 'skipping gitweb tests, perl version is too old' :
test_done
exit
}
gitweb_init
# ----------------------------------------------------------------------

View File

@@ -3,6 +3,7 @@
*/
#include "cache.h"
#include "diff.h"
#include "diffcore.h"
#include "tree.h"
static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
@@ -290,6 +291,78 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
return 0;
}
/*
* Does it look like the resulting diff might be due to a rename?
* - single entry
* - not a valid previous file
*/
static inline int diff_might_be_rename(void)
{
return diff_queued_diff.nr == 1 &&
!DIFF_FILE_VALID(diff_queued_diff.queue[0]->one);
}
static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
{
struct diff_options diff_opts;
struct diff_queue_struct *q = &diff_queued_diff;
struct diff_filepair *choice;
const char *paths[1];
int i;
/* Remove the file creation entry from the diff queue, and remember it */
choice = q->queue[0];
q->nr = 0;
diff_setup(&diff_opts);
diff_opts.recursive = 1;
diff_opts.detect_rename = DIFF_DETECT_RENAME;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = opt->paths[0];
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
if (diff_setup_done(&diff_opts) < 0)
die("unable to set up diff options to follow renames");
diff_tree(t1, t2, base, &diff_opts);
diffcore_std(&diff_opts);
/* Go through the new set of filepairing, and see if we find a more interesting one */
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
/*
* Found a source? Not only do we use that for the new
* diff_queued_diff, we will also use that as the path in
* the future!
*/
if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
/* Switch the file-pairs around */
q->queue[i] = choice;
choice = p;
/* Update the path we use from now on.. */
opt->paths[0] = xstrdup(p->one->path);
diff_tree_setup_paths(opt->paths, opt);
break;
}
}
/*
* Then, discard all the non-relevane file pairs...
*/
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
diff_free_filepair(p);
}
/*
* .. and re-instate the one we want (which might be either the
* original one, or the rename/copy we found)
*/
q->queue[0] = choice;
q->nr = 1;
}
int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
{
void *tree1, *tree2;
@@ -306,6 +379,11 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha
init_tree_desc(&t1, tree1, size1);
init_tree_desc(&t2, tree2, size2);
retval = diff_tree(&t1, &t2, base, opt);
if (opt->follow_renames && diff_might_be_rename()) {
init_tree_desc(&t1, tree1, size1);
init_tree_desc(&t2, tree2, size2);
try_to_follow_renames(&t1, &t2, base, opt);
}
free(tree1);
free(tree2);
return retval;

View File

@@ -22,7 +22,7 @@ static inline const unsigned char *tree_entry_extract(struct tree_desc *desc, co
static inline int tree_entry_len(const char *name, const unsigned char *sha1)
{
return (char *)sha1 - (char *)name - 1;
return (const char *)sha1 - name - 1;
}
void update_tree_entry(struct tree_desc *);