mirror of
https://github.com/git/git.git
synced 2026-03-13 10:23:30 +01:00
Merge commit '7c851733e4bc2b36bd9df63cab2fe11180242670'
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
32
Documentation/git-citool.txt
Normal file
32
Documentation/git-citool.txt
Normal 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
|
||||
@@ -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
115
Documentation/git-gui.txt
Normal 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
14
INSTALL
@@ -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
|
||||
|
||||
8
Makefile
8
Makefile
@@ -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)
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
7
cache.h
7
cache.h
@@ -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
399
commit.c
@@ -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;
|
||||
}
|
||||
|
||||
2
commit.h
2
commit.h
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
1577
contrib/fast-import/git-p4
Executable file
File diff suppressed because it is too large
Load Diff
1
contrib/fast-import/git-p4.bat
Normal file
1
contrib/fast-import/git-p4.bat
Normal file
@@ -0,0 +1 @@
|
||||
@python "%~d0%~p0git-p4" %*
|
||||
159
contrib/fast-import/git-p4.txt
Normal file
159
contrib/fast-import/git-p4.txt
Normal 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
|
||||
|
||||
@@ -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
2
diff.c
@@ -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
1
diff.h
@@ -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,
|
||||
|
||||
@@ -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
28
dir.c
@@ -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
9
dir.h
@@ -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;
|
||||
|
||||
20
git-clone.sh
20
git-clone.sh
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
441
git-filter-branch.sh
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
15
git.spec.in
15
git.spec.in
@@ -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)
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
33
log-tree.c
33
log-tree.c
@@ -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
6
refs.c
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
110
t/t7003-filter-branch.sh
Executable 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
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
78
tree-diff.c
78
tree-diff.c
@@ -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;
|
||||
|
||||
@@ -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 *);
|
||||
|
||||
Reference in New Issue
Block a user