Merge branch 'master' of git://repo.or.cz/alt-git

This commit is contained in:
Johannes Sixt
2008-07-16 09:38:41 +02:00
101 changed files with 4624 additions and 1812 deletions

View File

@@ -4,8 +4,16 @@ GIT v1.5.6.3 Release Notes
Fixes since v1.5.6.2
--------------------
* Setting core.sharerepository to traditional "true" value was supposed to make
the repository group writable but should not affect permission for others.
However, since 1.5.6, it was broken to drop permission for others when umask is
022, making the repository unreadable by others.
* Setting GIT_TRACE will report spawning of external process via run_command().
* Using an object with very deep delta chain pinned memory needed for extracting
intermediate base objects unnecessarily long, leading to excess memory usage.
* Bash completion script did not notice '--' marker on the command
line and tried the relatively slow "ref completion" even when
completing arguments after one.
@@ -14,6 +22,12 @@ Fixes since v1.5.6.2
tree file for it confused "racy-git avoidance" logic into thinking
that the path is now unchanged.
* The section that describes attributes related to git-archive were placed
in a wrong place in the gitattributes(5) manual page.
* "git am" was not helpful to the users when it detected that the committer
information is not set up properly yet.
* "git clone" had a leftover debugging fprintf().
* "git clone -q" was not quiet enough as it used to and gave object count
@@ -23,8 +37,10 @@ Fixes since v1.5.6.2
good thing if the remote side is well packed but otherwise not,
especially for a project that is not really big.
* The section that describes attributes related to git-archive were placed
in a wrong place in the gitattributes(5) manual page.
* "git daemon" used to call syslog() from a signal handler, which
could raise signals of its own but generally is not reentrant. This
was fixed by restructuring the code to report syslog() after the handler
returns.
* When "git push" tries to remove a remote ref, and corresponding
tracking ref is missing, we used to report error (i.e. failure to
@@ -34,9 +50,3 @@ Fixes since v1.5.6.2
MIME multipart mail correctly.
Contains other various documentation fixes.
--
exec >/var/tmp/1
O=v1.5.6.2-23-ge965647
echo O=$(git describe maint)
git shortlog --no-merges $O..maint

View File

@@ -97,15 +97,50 @@ Updates since v1.5.6
when you are working in a slow network disk and are outside any working tree,
as bash-completion and "git help" may still need to run in these places.
* By default, stash entries never expire. Set reflogexpire in [gc
"refs/stash"] to a reasonable value to get traditional auto-expiration
behaviour back
* Longstanding latency issue with bash completion script has been
addressed. This will need to be backmerged to 'maint' later.
* pager.<cmd> configuration variable can be used to enable/disable the
default paging behaviour per command.
* "git-add -i" has a new action 'e/dit' to allow you edit the patch hunk
manually.
* git-am records the original tip of the branch in ORIG_HEAD before it
starts applying patches.
* git-apply can handle a patch that touches the same path more than once
much better than before.
* git-apply can be told not to trust the line counts recorded in the input
patch but recount, with the new --recount option.
* git-apply can be told to apply a patch to a path deeper than what the
patch records with --directory option.
* git-archive can be told to omit certain paths from its output using
export-ignore attributes.
* With -v option, git-branch describes the remote tracking statistics
similar to the way git-checkout reports by how many commits your branch
is ahead/behind.
* git-branch's --contains option used to always require a commit parameter
to limit the branches with; it now defaults to list branches that
contains HEAD if this parameter is omitted.
* git-branch's --merged and --no-merged option used to always limit the
branches relative to the HEAD, but they can now take an optional commit
argument that is used in place of HEAD.
* git-bundle can read the revision arguments from the standard input.
* git-cherry-pick can replay a root commit now.
* git-clone can clone from a remote whose URL would be rewritten by
configuration stored in $HOME/.gitconfig now.
@@ -120,11 +155,29 @@ Updates since v1.5.6
* fast-export learned to export and import marks file; this can be used to
interface with fast-import incrementally.
* git-rebase records the original tip of branch in ORIG_HEAD before it is
rewound.
* "git rerere" can be told to update the index with auto-reused resolution
with rerere.autoupdate configuration variable.
* git-rev-list learned --children option to show child commits it
encountered during the traversal, instead of shoing parent commits.
* git-send-mail can talk not just over SSL but over TLS now.
* git-shortlog honors custom output format specified with "--pretty=format:".
* "git-stash save" learned --keep-index option. This lets you stash away the
local changes and bring the changes staged in the index to your working
tree for examination and testing.
* git-stash also learned branch subcommand to create a new branch out of
stashed changes.
* git-status gives the remote tracking statistics similar to the way
git-checkout reports by how many commits your branch is ahead/behind.
* You can tell "git status -u" to even more aggressively omit checking
untracked files with --untracked-files=no.
@@ -148,6 +201,6 @@ this release, unless otherwise noted.
---
exec >/var/tmp/1
O=v1.5.6.2-246-g86d7244
O=v1.5.6.3-350-g499027b
echo O=$(git describe refs/heads/master)
git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint

View File

@@ -301,7 +301,7 @@ If it does not apply correctly, there can be various reasons.
patch appropriately.
* Your MUA corrupted your patch; "am" would complain that
the patch does not apply. Look at .dotest/ subdirectory and
the patch does not apply. Look at .git/rebase/ subdirectory and
see what 'patch' file contains and check for the common
corruption patterns mentioned above.

View File

@@ -187,8 +187,9 @@ update::
"Update>>". When the prompt ends with double '>>', you can
make more than one selection, concatenated with whitespace or
comma. Also you can say ranges. E.g. "2-5 7,9" to choose
2,3,4,5,7,9 from the list. You can say '*' to choose
everything.
2,3,4,5,7,9 from the list. If the second number in a range is
omitted, all remaining patches are taken. E.g. "7-" to choose
7,8,9 from the list. You can say '*' to choose everything.
+
What you chose are then highlighted with '*',
like this:
@@ -236,6 +237,7 @@ patch::
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help
+
After deciding the fate for all hunks, if there is any hunk

View File

@@ -12,8 +12,8 @@ SYNOPSIS
'git am' [--signoff] [--keep] [--utf8 | --no-utf8]
[--3way] [--interactive] [--binary]
[--whitespace=<option>] [-C<n>] [-p<n>]
<mbox>|<Maildir>...
'git am' [--skip | --resolved]
[<mbox> | <Maildir>...]
'git am' (--skip | --resolved)
DESCRIPTION
-----------
@@ -140,11 +140,17 @@ aborts in the middle,. You can recover from this in one of two ways:
the index file to bring it in a state that the patch should
have produced. Then run the command with '--resolved' option.
The command refuses to process new mailboxes while `.dotest`
The command refuses to process new mailboxes while `.git/rebase`
directory exists, so if you decide to start over from scratch,
run `rm -f -r .dotest` before running the command with mailbox
run `rm -f -r .git/rebase` before running the command with mailbox
names.
Before any patches are applied, ORIG_HEAD is set to the tip of the
current branch. This is useful if you have problems with multiple
commits, like running 'git am' on the wrong branch or an error in the
commits that is more easily fixed by changing the mailbox (e.g.
errors in the "From:" lines).
SEE ALSO
--------

View File

@@ -8,24 +8,27 @@ git-branch - List, create, or delete branches
SYNOPSIS
--------
[verse]
'git branch' [--color | --no-color] [-r | -a] [--merged | --no-merged]
[-v [--abbrev=<length> | --no-abbrev]]
[--contains <commit>]
'git branch' [--color | --no-color] [-r | -a]
[-v [--abbrev=<length> | --no-abbrev]]
[(--merged | --no-merged | --contains) [<commit>]]
'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
'git branch' (-d | -D) [-r] <branchname>...
DESCRIPTION
-----------
With no arguments given a list of existing branches
will be shown, the current branch will be highlighted with an asterisk.
Option `-r` causes the remote-tracking branches to be listed,
and option `-a` shows both.
With `--contains <commit>`, shows only the branches that
contains the named commit (in other words, the branches whose
tip commits are descendant of the named commit).
With `--merged`, only branches merged into HEAD will be listed, and
with `--no-merged` only branches not merged into HEAD will be listed.
With no arguments, existing branches are listed, the current branch will
be highlighted with an asterisk. Option `-r` causes the remote-tracking
branches to be listed, and option `-a` shows both.
With `--contains`, shows only the branches that contains the named commit
(in other words, the branches whose tip commits are descendant of the
named commit). With `--merged`, only branches merged into the named
commit (i.e. the branches whose tip commits are reachable from the named
commit) will be listed. With `--no-merged` only branches not merged into
the named commit will be listed. Missing <commit> argument defaults to
'HEAD' (i.e. the tip of the current branch).
In its second form, a new branch named <branchname> will be created.
It will start out with a head equal to the one given as <start-point>.

View File

@@ -8,7 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message
SYNOPSIS
--------
'git mailinfo' [-k] [-u | --encoding=<encoding>] <msg> <patch>
'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] <msg> <patch>
DESCRIPTION
@@ -46,6 +46,9 @@ conversion, even with this flag.
from what is specified by i18n.commitencoding, this flag
can be used to override it.
-n::
Disable all charset re-coding of the metadata.
<msg>::
The commit log message extracted from e-mail, usually
except the title line which comes from e-mail Subject.

View File

@@ -81,7 +81,9 @@ Otherwise, merge will refuse to do any harm to your repository
(that is, it may fetch the objects from remote, and it may even
update the local branch used to keep track of the remote branch
with `git pull remote rbranch:lbranch`, but your working tree,
`.git/HEAD` pointer and index file are left intact).
`.git/HEAD` pointer and index file are left intact). In addition,
merge always sets `.git/ORIG_HEAD` to the original state of HEAD so
a problematic merge can be removed by using `git reset ORIG_HEAD`.
You may have local modifications in the working tree files. In
other words, 'git-diff' is allowed to report changes.

View File

@@ -26,7 +26,8 @@ of commits that would be shown by `git log <upstream>..HEAD`.
The current branch is reset to <upstream>, or <newbase> if the
--onto option was supplied. This has the exact same effect as
`git reset --hard <upstream>` (or <newbase>).
`git reset --hard <upstream>` (or <newbase>). ORIG_HEAD is set
to point at the tip of the branch before the reset.
The commits that were previously saved into the temporary area are
then reapplied to the current branch, one by one, in order. Note that
@@ -38,7 +39,7 @@ It is possible that a merge failure will prevent this process from being
completely automatic. You will have to resolve any such merge failure
and run `git rebase --continue`. Another option is to bypass the commit
that caused the merge failure with `git rebase --skip`. To restore the
original <branch> and remove the .dotest working files, use the command
original <branch> and remove the .git/rebase working files, use the command
`git rebase --abort` instead.
Assume the following history exists and the current branch is "topic":

View File

@@ -8,8 +8,11 @@ git-stash - Stash the changes in a dirty working directory away
SYNOPSIS
--------
[verse]
'git stash' (list | show [<stash>] | apply [<stash>] | clear | drop [<stash>] | pop [<stash>])
'git stash' list
'git stash' (show | apply | drop | pop ) [<stash>]
'git stash' branch <branchname> [<stash>]
'git stash' [save [<message>]]
'git stash' clear
DESCRIPTION
-----------
@@ -36,12 +39,15 @@ is also possible).
OPTIONS
-------
save [<message>]::
save [--keep-index] [<message>]::
Save your local modifications to a new 'stash', and run `git reset
--hard` to revert them. This is the default action when no
subcommand is given. The <message> part is optional and gives
the description along with the stashed state.
+
If the `--keep-index` option is used, all changes already added to the
index are left intact.
list [<options>]::
@@ -81,6 +87,20 @@ tree's changes, but also the index's ones. However, this can fail, when you
have conflicts (which are stored in the index, where you therefore can no
longer apply the changes as they were originally).
branch <branchname> [<stash>]::
Creates and checks out a new branch named `<branchname>` starting from
the commit at which the `<stash>` was originally created, applies the
changes recorded in `<stash>` to the new working tree and index, then
drops the `<stash>` if that completes successfully. When no `<stash>`
is given, applies the latest one.
+
This is useful if the branch on which you ran `git stash save` has
changed enough that `git stash apply` fails due to conflicts. Since
the stash is applied on top of the commit that was HEAD at the time
`git stash` was run, it restores the originally stashed state with
no conflicts.
clear::
Remove all the stashed states. Note that those states will then
be subject to pruning, and may be difficult or impossible to recover.
@@ -169,6 +189,24 @@ $ git stash apply
... continue hacking ...
----------------------------------------------------------------
Testing partial commits::
You can use `git stash save --keep-index` when you want to make two or
more commits out of the changes in the work tree, and you want to test
each change before committing:
+
----------------------------------------------------------------
... hack hack hack ...
$ git add --patch foo # add just first part to the index
$ git stash save --keep-index # save all other changes to the stash
$ edit/build/test first part
$ git commit foo -m 'First part' # commit fully tested change
$ git stash pop # prepare to work on all other changes
... repeat above five steps until one commit remains ...
$ edit/build/test remaining parts
$ git commit foo -m 'Remaining parts'
----------------------------------------------------------------
SEE ALSO
--------
linkgit:git-checkout[1],

View File

@@ -9,7 +9,7 @@ git-submodule - Initialize, update or inspect submodules
SYNOPSIS
--------
[verse]
'git submodule' [--quiet] add [-b branch] [--] <repository> [<path>]
'git submodule' [--quiet] add [-b branch] [--] <repository> <path>
'git submodule' [--quiet] status [--cached] [--] [<path>...]
'git submodule' [--quiet] init [--] [<path>...]
'git submodule' [--quiet] update [--init] [--] [<path>...]
@@ -20,14 +20,31 @@ COMMANDS
--------
add::
Add the given repository as a submodule at the given path
to the changeset to be committed next. If path is a valid
repository within the project, it is added as is. Otherwise,
repository is cloned at the specified path. path is added to the
changeset and registered in .gitmodules. If no path is
specified, the path is deduced from the repository specification.
If the repository url begins with ./ or ../, it is stored as
given but resolved as a relative path from the main project's
url when cloning.
to the changeset to be committed next to the current
project: the current project is termed termed the "superproject".
+
This requires two arguments: <repository> and <path>.
+
<repository> is the URL of the new submodule's origin repository.
This may be either an absolute URL, or (if it begins with ./
or ../), the location relative to the superproject's origin
repository.
+
<path> is the relative location for the cloned submodule to
exist in the superproject. If <path> does not exist, then the
submodule is created by cloning from the named URL. If <path> does
exist and is already a valid git repository, then this is added
to the changeset without cloning. This second form is provided
to ease creating a new submodule from scratch, and presumes
the user will later push the submodule to the given URL.
+
In either case, the given URL is recorded into .gitmodules for
use by subsequent users cloning the superproject. If the URL is
given relative to the superproject's repository, the presumption
is the superproject and submodule repositories will be kept
together in the same relative location, and only the
superproject's URL need be provided: git-submodule will correctly
locate the submodule using the relative URL in .gitmodules.
status::
Show the status of the submodules. This will print the SHA-1 of the
@@ -85,6 +102,7 @@ OPTIONS
<path>::
Path to submodule(s). When specified this will restrict the command
to only operate on the submodules found at the specified paths.
(This argument is required with add).
FILES
-----

View File

@@ -43,9 +43,10 @@ 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.6.2/git.html[documentation for release 1.5.6.2]
* link:v1.5.6.3/git.html[documentation for release 1.5.6.3]
* release notes for
link:RelNotes-1.5.6.3.txt[1.5.6.3].
link:RelNotes-1.5.6.2.txt[1.5.6.2].
link:RelNotes-1.5.6.1.txt[1.5.6.1].
link:RelNotes-1.5.6.txt[1.5.6].

View File

@@ -274,7 +274,7 @@ same machine, wants to contribute.
Bob begins with:
------------------------------------------------
$ git clone /home/alice/project myrepo
bob$ git clone /home/alice/project myrepo
------------------------------------------------
This creates a new directory "myrepo" containing a clone of Alice's
@@ -285,7 +285,7 @@ Bob then makes some changes and commits them:
------------------------------------------------
(edit files)
$ git commit -a
bob$ git commit -a
(repeat as necessary)
------------------------------------------------
@@ -293,8 +293,8 @@ When he's ready, he tells Alice to pull changes from the repository
at /home/bob/myrepo. She does this with:
------------------------------------------------
$ cd /home/alice/project
$ git pull /home/bob/myrepo master
alice$ cd /home/alice/project
alice$ git pull /home/bob/myrepo master
------------------------------------------------
This merges the changes from Bob's "master" branch into Alice's
@@ -306,21 +306,47 @@ is the default.)
The "pull" command thus performs two operations: it fetches changes
from a remote branch, then merges them into the current branch.
Note that in general, Alice would want her local changes committed before
initiating this "pull". If Bob's work conflicts with what Alice did since
their histories forked, Alice will use her working tree and the index to
resolve conflicts, and existing local changes will interfere with the
conflict resolution process (git will still perform the fetch but will
refuse to merge --- Alice will have to get rid of her local changes in
some way and pull again when this happens).
Alice can peek at what Bob did without merging first, using the "fetch"
command; this allows Alice to inspect what Bob did, using a special
symbol "FETCH_HEAD", in order to determine if he has anything worth
pulling, like this:
------------------------------------------------
alice$ git fetch /home/bob/myrepo master
alice$ git log -p ..FETCH_HEAD
------------------------------------------------
This operation is safe even if Alice has uncommitted local changes.
After inspecting what Bob did, if there is nothing urgent, Alice may
decide to continue working without pulling from Bob. If Bob's history
does have something Alice would immediately need, Alice may choose to
stash her work-in-progress first, do a "pull", and then finally unstash
her work-in-progress on top of the resulting history.
When you are working in a small closely knit group, it is not
unusual to interact with the same repository over and over
again. By defining 'remote' repository shorthand, you can make
it easier:
------------------------------------------------
$ git remote add bob /home/bob/myrepo
alice$ git remote add bob /home/bob/myrepo
------------------------------------------------
With this, Alice can perform the first operation alone using the
With this, Alice can perform the first part of the "pull" operation alone using the
'git-fetch' command without merging them with her own branch,
using:
-------------------------------------
$ git fetch bob
alice$ git fetch bob
-------------------------------------
Unlike the longhand form, when Alice fetches from Bob using a
@@ -329,7 +355,7 @@ fetched is stored in a remote tracking branch, in this case
`bob/master`. So after this:
-------------------------------------
$ git log -p master..bob/master
alice$ git log -p master..bob/master
-------------------------------------
shows a list of all the changes that Bob made since he branched from
@@ -339,14 +365,14 @@ After examining those changes, Alice
could merge the changes into her master branch:
-------------------------------------
$ git merge bob/master
alice$ git merge bob/master
-------------------------------------
This `merge` can also be done by 'pulling from her own remote
tracking branch', like this:
-------------------------------------
$ git pull . remotes/bob/master
alice$ git pull . remotes/bob/master
-------------------------------------
Note that git pull always merges into the current branch,
@@ -355,7 +381,7 @@ regardless of what else is given on the command line.
Later, Bob can update his repo with Alice's latest changes using
-------------------------------------
$ git pull
bob$ git pull
-------------------------------------
Note that he doesn't need to give the path to Alice's repository;
@@ -364,7 +390,7 @@ repository in the repository configuration, and that location is
used for pulls:
-------------------------------------
$ git config --get remote.origin.url
bob$ git config --get remote.origin.url
/home/alice/project
-------------------------------------
@@ -376,7 +402,7 @@ Git also keeps a pristine copy of Alice's master branch under the
name "origin/master":
-------------------------------------
$ git branch -r
bob$ git branch -r
origin/master
-------------------------------------
@@ -384,7 +410,7 @@ If Bob later decides to work from a different host, he can still
perform clones and pulls using the ssh protocol:
-------------------------------------
$ git clone alice.org:/home/alice/project myrepo
bob$ git clone alice.org:/home/alice/project myrepo
-------------------------------------
Alternatively, git has a native protocol, or can use rsync or http;

View File

@@ -101,6 +101,7 @@ The placeholders are:
- '%P': parent hashes
- '%p': abbreviated parent hashes
- '%an': author name
- '%aN': author name (respecting .mailmap)
- '%ae': author email
- '%ad': author date
- '%aD': author date, RFC2822 style
@@ -108,6 +109,7 @@ The placeholders are:
- '%at': author date, UNIX timestamp
- '%ai': author date, ISO 8601 format
- '%cn': committer name
- '%cN': committer name (respecting .mailmap)
- '%ce': committer email
- '%cd': committer date
- '%cD': committer date, RFC2822 style

View File

@@ -45,6 +45,10 @@ endif::git-rev-list[]
Print the parents of the commit.
--children::
Print the children of the commit.
ifdef::git-rev-list[]
--timestamp::
Print the raw commit timestamp.

View File

@@ -2431,7 +2431,7 @@ $ git rebase origin
-------------------------------------------------
This will remove each of your commits from mywork, temporarily saving
them as patches (in a directory named ".dotest"), update mywork to
them as patches (in a directory named ".git/rebase"), update mywork to
point at the latest version of origin, then apply each of the saved
patches to the new mywork. The result will look like:

View File

@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
SCRIPT_SH += git-merge-octopus.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-merge.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-parse-remote.sh
SCRIPT_SH += git-pull.sh
@@ -363,6 +362,7 @@ LIB_H += quote.h
LIB_H += reflog-walk.h
LIB_H += refs.h
LIB_H += remote.h
LIB_H += rerere.h
LIB_H += revision.h
LIB_H += run-command.h
LIB_H += sha1-lookup.h
@@ -447,6 +447,7 @@ LIB_OBJS += read-cache.o
LIB_OBJS += reflog-walk.o
LIB_OBJS += refs.o
LIB_OBJS += remote.o
LIB_OBJS += rerere.o
LIB_OBJS += revision.o
LIB_OBJS += run-command.o
LIB_OBJS += server-info.o
@@ -513,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
BUILTIN_OBJS += builtin-ls-tree.o
BUILTIN_OBJS += builtin-mailinfo.o
BUILTIN_OBJS += builtin-mailsplit.o
BUILTIN_OBJS += builtin-merge.o
BUILTIN_OBJS += builtin-merge-base.o
BUILTIN_OBJS += builtin-merge-file.o
BUILTIN_OBJS += builtin-merge-ours.o

54
alias.c
View File

@@ -21,3 +21,57 @@ char *alias_lookup(const char *alias)
git_config(alias_lookup_cb, NULL);
return alias_val;
}
int split_cmdline(char *cmdline, const char ***argv)
{
int src, dst, count = 0, size = 16;
char quoted = 0;
*argv = xmalloc(sizeof(char*) * size);
/* split alias_string */
(*argv)[count++] = cmdline;
for (src = dst = 0; cmdline[src];) {
char c = cmdline[src];
if (!quoted && isspace(c)) {
cmdline[dst++] = 0;
while (cmdline[++src]
&& isspace(cmdline[src]))
; /* skip */
if (count >= size) {
size += 16;
*argv = xrealloc(*argv, sizeof(char*) * size);
}
(*argv)[count++] = cmdline + dst;
} else if (!quoted && (c == '\'' || c == '"')) {
quoted = c;
src++;
} else if (c == quoted) {
quoted = 0;
src++;
} else {
if (c == '\\' && quoted != '\'') {
src++;
c = cmdline[src];
if (!c) {
free(*argv);
*argv = NULL;
return error("cmdline ends with \\");
}
}
cmdline[dst++] = c;
src++;
}
}
cmdline[dst] = 0;
if (quoted) {
free(*argv);
*argv = NULL;
return error("unclosed quote");
}
return count;
}

View File

@@ -166,7 +166,7 @@ void create_branch(const char *head,
void remove_branch_state(void)
{
unlink(git_path("MERGE_HEAD"));
unlink(git_path("rr-cache/MERGE_RR"));
unlink(git_path("MERGE_RR"));
unlink(git_path("MERGE_MSG"));
unlink(git_path("SQUASH_MSG"));
}

View File

@@ -18,24 +18,16 @@
#include "cache-tree.h"
#include "path-list.h"
#include "mailmap.h"
#include "parse-options.h"
static char blame_usage[] =
"git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
" -c Use the same output mode as git-annotate (Default: off)\n"
" -b Show blank SHA-1 for boundary commits (Default: off)\n"
" -l Show long commit SHA1 (Default: off)\n"
" --root Do not treat root commits as boundaries (Default: off)\n"
" -t Show raw timestamp (Default: off)\n"
" -f, --show-name Show original filename (Default: auto)\n"
" -n, --show-number Show original linenumber (Default: off)\n"
" -s Suppress author name and timestamp (Default: off)\n"
" -p, --porcelain Show in a format designed for machine consumption\n"
" -w Ignore whitespace differences\n"
" -L n,m Process only line range n,m, counting from 1\n"
" -M, -C Find line movements within and across files\n"
" --incremental Show blame entries as we find them, incrementally\n"
" --contents file Use <file>'s contents as the final image\n"
" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n";
static char blame_usage[] = "git-blame [options] [rev-opts] [rev] [--] file";
static const char *blame_opt_usage[] = {
blame_usage,
"",
"[rev-opts] are documented in git-rev-list(1)",
NULL
};
static int longest_file;
static int longest_author;
@@ -43,6 +35,7 @@ static int max_orig_digits;
static int max_digits;
static int max_score_digits;
static int show_root;
static int reverse;
static int blank_boundary;
static int incremental;
static int cmd_is_annotate;
@@ -91,7 +84,7 @@ struct origin {
* Given an origin, prepare mmfile_t structure to be used by the
* diff machinery
*/
static char *fill_origin_blob(struct origin *o, mmfile_t *file)
static void fill_origin_blob(struct origin *o, mmfile_t *file)
{
if (!o->file.ptr) {
enum object_type type;
@@ -106,7 +99,6 @@ static char *fill_origin_blob(struct origin *o, mmfile_t *file)
}
else
*file = o->file;
return file->ptr;
}
/*
@@ -178,7 +170,7 @@ struct blame_entry {
struct scoreboard {
/* the final commit (i.e. where we started digging from) */
struct commit *final;
struct rev_info *revs;
const char *path;
/*
@@ -1192,18 +1184,48 @@ static void pass_whole_blame(struct scoreboard *sb,
}
}
#define MAXPARENT 16
/*
* We pass blame from the current commit to its parents. We keep saying
* "parent" (and "porigin"), but what we mean is to find scapegoat to
* exonerate ourselves.
*/
static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
{
if (!reverse)
return commit->parents;
return lookup_decoration(&revs->children, &commit->object);
}
static int num_scapegoats(struct rev_info *revs, struct commit *commit)
{
int cnt;
struct commit_list *l = first_scapegoat(revs, commit);
for (cnt = 0; l; l = l->next)
cnt++;
return cnt;
}
#define MAXSG 16
static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
{
int i, pass;
struct rev_info *revs = sb->revs;
int i, pass, num_sg;
struct commit *commit = origin->commit;
struct commit_list *parent;
struct origin *parent_origin[MAXPARENT], *porigin;
struct commit_list *sg;
struct origin *sg_buf[MAXSG];
struct origin *porigin, **sg_origin = sg_buf;
memset(parent_origin, 0, sizeof(parent_origin));
num_sg = num_scapegoats(revs, commit);
if (!num_sg)
goto finish;
else if (num_sg < ARRAY_SIZE(sg_buf))
memset(sg_buf, 0, sizeof(sg_buf));
else
sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
/* The first pass looks for unrenamed path to optimize for
/*
* The first pass looks for unrenamed path to optimize for
* common cases, then we look for renames in the second pass.
*/
for (pass = 0; pass < 2; pass++) {
@@ -1211,13 +1233,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
struct commit *, struct origin *);
find = pass ? find_rename : find_origin;
for (i = 0, parent = commit->parents;
i < MAXPARENT && parent;
parent = parent->next, i++) {
struct commit *p = parent->item;
for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct commit *p = sg->item;
int j, same;
if (parent_origin[i])
if (sg_origin[i])
continue;
if (parse_commit(p))
continue;
@@ -1230,24 +1252,24 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
goto finish;
}
for (j = same = 0; j < i; j++)
if (parent_origin[j] &&
!hashcmp(parent_origin[j]->blob_sha1,
if (sg_origin[j] &&
!hashcmp(sg_origin[j]->blob_sha1,
porigin->blob_sha1)) {
same = 1;
break;
}
if (!same)
parent_origin[i] = porigin;
sg_origin[i] = porigin;
else
origin_decref(porigin);
}
}
num_commits++;
for (i = 0, parent = commit->parents;
i < MAXPARENT && parent;
parent = parent->next, i++) {
struct origin *porigin = parent_origin[i];
for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct origin *porigin = sg_origin[i];
if (!porigin)
continue;
if (pass_blame_to_parent(sb, origin, porigin))
@@ -1258,10 +1280,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
* Optionally find moves in parents' files.
*/
if (opt & PICKAXE_BLAME_MOVE)
for (i = 0, parent = commit->parents;
i < MAXPARENT && parent;
parent = parent->next, i++) {
struct origin *porigin = parent_origin[i];
for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct origin *porigin = sg_origin[i];
if (!porigin)
continue;
if (find_move_in_parent(sb, origin, porigin))
@@ -1272,23 +1294,25 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
* Optionally find copies from parents' files.
*/
if (opt & PICKAXE_BLAME_COPY)
for (i = 0, parent = commit->parents;
i < MAXPARENT && parent;
parent = parent->next, i++) {
struct origin *porigin = parent_origin[i];
if (find_copy_in_parent(sb, origin, parent->item,
for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct origin *porigin = sg_origin[i];
if (find_copy_in_parent(sb, origin, sg->item,
porigin, opt))
goto finish;
}
finish:
for (i = 0; i < MAXPARENT; i++) {
if (parent_origin[i]) {
drop_origin_blob(parent_origin[i]);
origin_decref(parent_origin[i]);
for (i = 0; i < num_sg; i++) {
if (sg_origin[i]) {
drop_origin_blob(sg_origin[i]);
origin_decref(sg_origin[i]);
}
}
drop_origin_blob(origin);
if (sg_buf != sg_origin)
free(sg_origin);
}
/*
@@ -1487,8 +1511,10 @@ static void found_guilty_entry(struct blame_entry *ent)
* is still unknown, pick one blame_entry, and allow its current
* suspect to pass blames to its parents.
*/
static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
static void assign_blame(struct scoreboard *sb, int opt)
{
struct rev_info *revs = sb->revs;
while (1) {
struct blame_entry *ent;
struct commit *commit;
@@ -1509,8 +1535,9 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
commit = suspect->commit;
if (!commit->object.parsed)
parse_commit(commit);
if (!(commit->object.flags & UNINTERESTING) &&
!(revs->max_age != -1 && commit->date < revs->max_age))
if (reverse ||
(!(commit->object.flags & UNINTERESTING) &&
!(revs->max_age != -1 && commit->date < revs->max_age)))
pass_blame(sb, suspect, opt);
else {
commit->object.flags |= UNINTERESTING;
@@ -2006,6 +2033,10 @@ static int git_blame_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
/*
* Prepare a dummy commit that represents the work tree (or staged) item.
* Note that annotating work tree item never works in the reverse.
*/
static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
{
struct commit *commit;
@@ -2122,6 +2153,108 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
return commit;
}
static const char *prepare_final(struct scoreboard *sb)
{
int i;
const char *final_commit_name = NULL;
struct rev_info *revs = sb->revs;
/*
* There must be one and only one positive commit in the
* revs->pending array.
*/
for (i = 0; i < revs->pending.nr; i++) {
struct object *obj = revs->pending.objects[i].item;
if (obj->flags & UNINTERESTING)
continue;
while (obj->type == OBJ_TAG)
obj = deref_tag(obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (sb->final)
die("More than one commit to dig from %s and %s?",
revs->pending.objects[i].name,
final_commit_name);
sb->final = (struct commit *) obj;
final_commit_name = revs->pending.objects[i].name;
}
return final_commit_name;
}
static const char *prepare_initial(struct scoreboard *sb)
{
int i;
const char *final_commit_name = NULL;
struct rev_info *revs = sb->revs;
/*
* There must be one and only one negative commit, and it must be
* the boundary.
*/
for (i = 0; i < revs->pending.nr; i++) {
struct object *obj = revs->pending.objects[i].item;
if (!(obj->flags & UNINTERESTING))
continue;
while (obj->type == OBJ_TAG)
obj = deref_tag(obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (sb->final)
die("More than one commit to dig down to %s and %s?",
revs->pending.objects[i].name,
final_commit_name);
sb->final = (struct commit *) obj;
final_commit_name = revs->pending.objects[i].name;
}
if (!final_commit_name)
die("No commit to dig down to?");
return final_commit_name;
}
static int blame_copy_callback(const struct option *option, const char *arg, int unset)
{
int *opt = option->value;
/*
* -C enables copy from removed files;
* -C -C enables copy from existing files, but only
* when blaming a new file;
* -C -C -C enables copy from existing files for
* everybody
*/
if (*opt & PICKAXE_BLAME_COPY_HARDER)
*opt |= PICKAXE_BLAME_COPY_HARDEST;
if (*opt & PICKAXE_BLAME_COPY)
*opt |= PICKAXE_BLAME_COPY_HARDER;
*opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
if (arg)
blame_copy_score = parse_score(arg);
return 0;
}
static int blame_move_callback(const struct option *option, const char *arg, int unset)
{
int *opt = option->value;
*opt |= PICKAXE_BLAME_MOVE;
if (arg)
blame_move_score = parse_score(arg);
return 0;
}
static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
{
const char **bottomtop = option->value;
if (!arg)
return -1;
if (*bottomtop)
die("More than one '-L n,m' option given");
*bottomtop = arg;
return 0;
}
int cmd_blame(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
@@ -2129,102 +2262,66 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
struct scoreboard sb;
struct origin *o;
struct blame_entry *ent;
int i, seen_dashdash, unk, opt;
long bottom, top, lno;
int output_option = 0;
int show_stats = 0;
const char *revs_file = NULL;
long dashdash_pos, bottom, top, lno;
const char *final_commit_name = NULL;
enum object_type type;
const char *bottomtop = NULL;
const char *contents_from = NULL;
static const char *bottomtop = NULL;
static int output_option = 0, opt = 0;
static int show_stats = 0;
static const char *revs_file = NULL;
static const char *contents_from = NULL;
static const struct option options[] = {
OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
{ OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
{ OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
OPT_END()
};
struct parse_opt_ctx_t ctx;
cmd_is_annotate = !strcmp(argv[0], "annotate");
git_config(git_blame_config, NULL);
init_revisions(&revs, NULL);
save_commit_buffer = 0;
dashdash_pos = 0;
opt = 0;
seen_dashdash = 0;
for (unk = i = 1; i < argc; i++) {
const char *arg = argv[i];
if (*arg != '-')
break;
else if (!strcmp("-b", arg))
blank_boundary = 1;
else if (!strcmp("--root", arg))
show_root = 1;
else if (!strcmp(arg, "--show-stats"))
show_stats = 1;
else if (!strcmp("-c", arg))
output_option |= OUTPUT_ANNOTATE_COMPAT;
else if (!strcmp("-t", arg))
output_option |= OUTPUT_RAW_TIMESTAMP;
else if (!strcmp("-l", arg))
output_option |= OUTPUT_LONG_OBJECT_NAME;
else if (!strcmp("-s", arg))
output_option |= OUTPUT_NO_AUTHOR;
else if (!strcmp("-w", arg))
xdl_opts |= XDF_IGNORE_WHITESPACE;
else if (!strcmp("-S", arg) && ++i < argc)
revs_file = argv[i];
else if (!prefixcmp(arg, "-M")) {
opt |= PICKAXE_BLAME_MOVE;
blame_move_score = parse_score(arg+2);
parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_KEEP_ARGV0);
for (;;) {
switch (parse_options_step(&ctx, options, blame_opt_usage)) {
case PARSE_OPT_HELP:
exit(129);
case PARSE_OPT_DONE:
if (ctx.argv[0])
dashdash_pos = ctx.cpidx;
goto parse_done;
}
else if (!prefixcmp(arg, "-C")) {
/*
* -C enables copy from removed files;
* -C -C enables copy from existing files, but only
* when blaming a new file;
* -C -C -C enables copy from existing files for
* everybody
*/
if (opt & PICKAXE_BLAME_COPY_HARDER)
opt |= PICKAXE_BLAME_COPY_HARDEST;
if (opt & PICKAXE_BLAME_COPY)
opt |= PICKAXE_BLAME_COPY_HARDER;
opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
blame_copy_score = parse_score(arg+2);
if (!strcmp(ctx.argv[0], "--reverse")) {
ctx.argv[0] = "--children";
reverse = 1;
}
else if (!prefixcmp(arg, "-L")) {
if (!arg[2]) {
if (++i >= argc)
usage(blame_usage);
arg = argv[i];
}
else
arg += 2;
if (bottomtop)
die("More than one '-L n,m' option given");
bottomtop = arg;
}
else if (!strcmp("--contents", arg)) {
if (++i >= argc)
usage(blame_usage);
contents_from = argv[i];
}
else if (!strcmp("--incremental", arg))
incremental = 1;
else if (!strcmp("--score-debug", arg))
output_option |= OUTPUT_SHOW_SCORE;
else if (!strcmp("-f", arg) ||
!strcmp("--show-name", arg))
output_option |= OUTPUT_SHOW_NAME;
else if (!strcmp("-n", arg) ||
!strcmp("--show-number", arg))
output_option |= OUTPUT_SHOW_NUMBER;
else if (!strcmp("-p", arg) ||
!strcmp("--porcelain", arg))
output_option |= OUTPUT_PORCELAIN;
else if (!strcmp("--", arg)) {
seen_dashdash = 1;
i++;
break;
}
else
argv[unk++] = arg;
parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
}
parse_done:
argc = parse_options_end(&ctx);
if (!blame_move_score)
blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
@@ -2238,115 +2335,59 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
*
* The remaining are:
*
* (1) if seen_dashdash, its either
* "-options -- <path>" or
* "-options -- <path> <rev>".
* but the latter is allowed only if there is no
* options that we passed to revision machinery.
* (1) if dashdash_pos != 0, its either
* "blame [revisions] -- <path>" or
* "blame -- <path> <rev>"
*
* (2) otherwise, we may have "--" somewhere later and
* might be looking at the first one of multiple 'rev'
* parameters (e.g. " master ^next ^maint -- path").
* See if there is a dashdash first, and give the
* arguments before that to revision machinery.
* After that there must be one 'path'.
* (2) otherwise, its one of the two:
* "blame [revisions] <path>"
* "blame <path> <rev>"
*
* (3) otherwise, its one of the three:
* "-options <path> <rev>"
* "-options <rev> <path>"
* "-options <path>"
* but again the first one is allowed only if
* there is no options that we passed to revision
* machinery.
* Note that we must strip out <path> from the arguments: we do not
* want the path pruning but we may want "bottom" processing.
*/
if (seen_dashdash) {
/* (1) */
if (argc <= i)
usage(blame_usage);
path = add_prefix(prefix, argv[i]);
if (i + 1 == argc - 1) {
if (unk != 1)
usage(blame_usage);
argv[unk++] = argv[i + 1];
if (dashdash_pos) {
switch (argc - dashdash_pos - 1) {
case 2: /* (1b) */
if (argc != 4)
usage_with_options(blame_opt_usage, options);
/* reorder for the new way: <rev> -- <path> */
argv[1] = argv[3];
argv[3] = argv[2];
argv[2] = "--";
/* FALLTHROUGH */
case 1: /* (1a) */
path = add_prefix(prefix, argv[--argc]);
argv[argc] = NULL;
break;
default:
usage_with_options(blame_opt_usage, options);
}
else if (i + 1 != argc)
/* garbage at end */
usage(blame_usage);
}
else {
int j;
for (j = i; !seen_dashdash && j < argc; j++)
if (!strcmp(argv[j], "--"))
seen_dashdash = j;
if (seen_dashdash) {
/* (2) */
if (seen_dashdash + 1 != argc - 1)
usage(blame_usage);
path = add_prefix(prefix, argv[seen_dashdash + 1]);
for (j = i; j < seen_dashdash; j++)
argv[unk++] = argv[j];
} else {
if (argc < 2)
usage_with_options(blame_opt_usage, options);
path = add_prefix(prefix, argv[argc - 1]);
if (argc == 3 && !has_path_in_work_tree(path)) { /* (2b) */
path = add_prefix(prefix, argv[1]);
argv[1] = argv[2];
}
else {
/* (3) */
if (argc <= i)
usage(blame_usage);
path = add_prefix(prefix, argv[i]);
if (i + 1 == argc - 1) {
final_commit_name = argv[i + 1];
argv[argc - 1] = "--";
/* if (unk == 1) we could be getting
* old-style
*/
if (unk == 1 && !has_path_in_work_tree(path)) {
path = add_prefix(prefix, argv[i + 1]);
final_commit_name = argv[i];
}
}
else if (i != argc - 1)
usage(blame_usage); /* garbage at end */
setup_work_tree();
if (!has_path_in_work_tree(path))
die("cannot stat path %s: %s",
path, strerror(errno));
}
setup_work_tree();
if (!has_path_in_work_tree(path))
die("cannot stat path %s: %s", path, strerror(errno));
}
if (final_commit_name)
argv[unk++] = final_commit_name;
/*
* Now we got rev and path. We do not want the path pruning
* but we may want "bottom" processing.
*/
argv[unk++] = "--"; /* terminate the rev name */
argv[unk] = NULL;
init_revisions(&revs, NULL);
setup_revisions(unk, argv, &revs, NULL);
setup_revisions(argc, argv, &revs, NULL);
memset(&sb, 0, sizeof(sb));
/*
* There must be one and only one positive commit in the
* revs->pending array.
*/
for (i = 0; i < revs.pending.nr; i++) {
struct object *obj = revs.pending.objects[i].item;
if (obj->flags & UNINTERESTING)
continue;
while (obj->type == OBJ_TAG)
obj = deref_tag(obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?",
revs.pending.objects[i].name);
if (sb.final)
die("More than one commit to dig from %s and %s?",
revs.pending.objects[i].name,
final_commit_name);
sb.final = (struct commit *) obj;
final_commit_name = revs.pending.objects[i].name;
}
sb.revs = &revs;
if (!reverse)
final_commit_name = prepare_final(&sb);
else if (contents_from)
die("--contents and --children do not blend well.");
else
final_commit_name = prepare_initial(&sb);
if (!sb.final) {
/*
@@ -2425,7 +2466,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
if (!incremental)
setup_pager();
assign_blame(&sb, &revs, opt);
assign_blame(&sb, opt);
if (incremental)
return 0;

View File

@@ -46,7 +46,12 @@ enum color_branch {
COLOR_BRANCH_CURRENT = 4,
};
static int mergefilter = -1;
static enum merge_filter {
NO_FILTER = 0,
SHOW_NOT_MERGED,
SHOW_MERGED,
} merge_filter;
static unsigned char merge_filter_ref[20];
static int parse_branch_color_slot(const char *var, int ofs)
{
@@ -234,13 +239,15 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
if ((kind & ref_list->kinds) == 0)
return 0;
if (mergefilter > -1) {
if (merge_filter != NO_FILTER) {
branch.item = lookup_commit_reference_gently(sha1, 1);
if (!branch.item)
die("Unable to lookup tip of branch %s", refname);
if (mergefilter == 0 && has_commit(head_sha1, &branch))
if (merge_filter == SHOW_NOT_MERGED &&
has_commit(merge_filter_ref, &branch))
return 0;
if (mergefilter == 1 && !has_commit(head_sha1, &branch))
if (merge_filter == SHOW_MERGED &&
!has_commit(merge_filter_ref, &branch))
return 0;
}
@@ -282,6 +289,21 @@ static int ref_cmp(const void *r1, const void *r2)
return strcmp(c1->name, c2->name);
}
static void fill_tracking_info(char *stat, const char *branch_name)
{
int ours, theirs;
struct branch *branch = branch_get(branch_name);
if (!stat_tracking_info(branch, &ours, &theirs) || (!ours && !theirs))
return;
if (!ours)
sprintf(stat, "[behind %d] ", theirs);
else if (!theirs)
sprintf(stat, "[ahead %d] ", ours);
else
sprintf(stat, "[ahead %d, behind %d] ", ours, theirs);
}
static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
int abbrev, int current)
{
@@ -310,8 +332,10 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
if (verbose) {
struct strbuf subject;
const char *sub = " **** invalid ref ****";
char stat[128];
strbuf_init(&subject, 0);
stat[0] = '\0';
commit = lookup_commit(item->sha1);
if (commit && !parse_commit(commit)) {
@@ -319,10 +343,15 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
&subject, 0, NULL, NULL, 0, 0);
sub = subject.buf;
}
printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color),
if (item->kind == REF_LOCAL_BRANCH)
fill_tracking_info(stat, item->name);
printf("%c %s%-*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), sub);
find_unique_abbrev(item->sha1, abbrev),
stat, sub);
strbuf_release(&subject);
} else {
printf("%c %s%s%s\n", c, branch_get_color(color), item->name,
@@ -421,6 +450,20 @@ static int opt_parse_with_commit(const struct option *opt, const char *arg, int
return 0;
}
static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
{
merge_filter = ((opt->long_name[0] == 'n')
? SHOW_NOT_MERGED
: SHOW_MERGED);
if (unset)
merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
if (!arg)
arg = "HEAD";
if (get_sha1(arg, merge_filter_ref))
die("malformed object name %s", arg);
return 0;
}
int cmd_branch(int argc, const char **argv, const char *prefix)
{
int delete = 0, rename = 0, force_create = 0;
@@ -438,13 +481,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"),
OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches",
REF_REMOTE_BRANCH),
OPT_CALLBACK(0, "contains", &with_commit, "commit",
"print only branches that contain the commit",
opt_parse_with_commit),
{
OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
"print only branches that contain the commit",
PARSE_OPT_LASTARG_DEFAULT,
opt_parse_with_commit, (intptr_t)"HEAD",
},
{
OPTION_CALLBACK, 0, "with", &with_commit, "commit",
"print only branches that contain the commit",
PARSE_OPT_HIDDEN, opt_parse_with_commit,
PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
opt_parse_with_commit, (intptr_t) "HEAD",
},
OPT__ABBREV(&abbrev),
@@ -457,7 +504,18 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
OPT_BOOLEAN('f', NULL, &force_create, "force creation (when already exists)"),
OPT_SET_INT(0, "merged", &mergefilter, "list only merged branches", 1),
{
OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
"commit", "print only not merged branches",
PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
opt_parse_merge_filter, (intptr_t) "HEAD",
},
{
OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
"commit", "print only merged branches",
PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
opt_parse_merge_filter, (intptr_t) "HEAD",
},
OPT_END(),
};
@@ -467,9 +525,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
branch_use_color = git_use_color_default;
track = git_branch_track;
argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
if (!!delete + !!rename + !!force_create > 1)
usage_with_options(builtin_branch_usage, options);
head = resolve_ref("HEAD", head_sha1, 0, NULL);
if (!head)
@@ -482,6 +537,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
die("HEAD not found below refs/heads!");
head += 11;
}
hashcpy(merge_filter_ref, head_sha1);
argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
if (!!delete + !!rename + !!force_create > 1)
usage_with_options(builtin_branch_usage, options);
if (delete)
return delete_branches(argc, argv, delete > 1, kinds);

View File

@@ -305,97 +305,15 @@ static int merge_working_tree(struct checkout_opts *opts,
return 0;
}
static void report_tracking(struct branch_info *new, struct checkout_opts *opts)
static void report_tracking(struct branch_info *new)
{
/*
* We have switched to a new branch; is it building on
* top of another branch, and if so does that other branch
* have changes we do not have yet?
*/
char *base;
unsigned char sha1[20];
struct commit *ours, *theirs;
char symmetric[84];
struct rev_info revs;
const char *rev_argv[10];
int rev_argc;
int num_ours, num_theirs;
const char *remote_msg;
struct strbuf sb = STRBUF_INIT;
struct branch *branch = branch_get(new->name);
/*
* Nothing to report unless we are marked to build on top of
* somebody else.
*/
if (!branch || !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
if (!format_tracking_info(branch, &sb))
return;
/*
* If what we used to build on no longer exists, there is
* nothing to report.
*/
base = branch->merge[0]->dst;
if (!resolve_ref(base, sha1, 1, NULL))
return;
theirs = lookup_commit(sha1);
ours = new->commit;
if (!hashcmp(sha1, ours->object.sha1))
return; /* we are the same */
/* Run "rev-list --left-right ours...theirs" internally... */
rev_argc = 0;
rev_argv[rev_argc++] = NULL;
rev_argv[rev_argc++] = "--left-right";
rev_argv[rev_argc++] = symmetric;
rev_argv[rev_argc++] = "--";
rev_argv[rev_argc] = NULL;
strcpy(symmetric, sha1_to_hex(ours->object.sha1));
strcpy(symmetric + 40, "...");
strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1));
init_revisions(&revs, NULL);
setup_revisions(rev_argc, rev_argv, &revs, NULL);
prepare_revision_walk(&revs);
/* ... and count the commits on each side. */
num_ours = 0;
num_theirs = 0;
while (1) {
struct commit *c = get_revision(&revs);
if (!c)
break;
if (c->object.flags & SYMMETRIC_LEFT)
num_ours++;
else
num_theirs++;
}
if (!prefixcmp(base, "refs/remotes/")) {
remote_msg = " remote";
base += strlen("refs/remotes/");
} else {
remote_msg = "";
}
if (!num_theirs)
printf("Your branch is ahead of the tracked%s branch '%s' "
"by %d commit%s.\n",
remote_msg, base,
num_ours, (num_ours == 1) ? "" : "s");
else if (!num_ours)
printf("Your branch is behind the tracked%s branch '%s' "
"by %d commit%s,\n"
"and can be fast-forwarded.\n",
remote_msg, base,
num_theirs, (num_theirs == 1) ? "" : "s");
else
printf("Your branch and the tracked%s branch '%s' "
"have diverged,\nand respectively "
"have %d and %d different commit(s) each.\n",
remote_msg, base,
num_ours, num_theirs);
fputs(sb.buf, stdout);
strbuf_release(&sb);
}
static void update_refs_for_switch(struct checkout_opts *opts,
@@ -441,7 +359,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
remove_branch_state();
strbuf_release(&msg);
if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
report_tracking(new, opts);
report_tracking(new);
}
static int switch_branches(struct checkout_opts *opts, struct branch_info *new)

View File

@@ -45,41 +45,19 @@ static const char commit_utf8_warn[] =
"You may want to amend it after fixing the message, or set the config\n"
"variable i18n.commitencoding to the encoding your project uses.\n";
int cmd_commit_tree(int argc, const char **argv, const char *prefix)
int commit_tree(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret)
{
int i;
struct commit_list *parents = NULL;
unsigned char tree_sha1[20];
unsigned char commit_sha1[20];
struct strbuf buffer;
int encoding_is_utf8;
struct strbuf buffer;
git_config(git_default_config, NULL);
if (argc < 2)
usage(commit_tree_usage);
if (get_sha1(argv[1], tree_sha1))
die("Not a valid object name %s", argv[1]);
check_valid(tree_sha1, OBJ_TREE);
for (i = 2; i < argc; i += 2) {
unsigned char sha1[20];
const char *a, *b;
a = argv[i]; b = argv[i+1];
if (!b || strcmp(a, "-p"))
usage(commit_tree_usage);
if (get_sha1(b, sha1))
die("Not a valid object name %s", b);
check_valid(sha1, OBJ_COMMIT);
new_parent(lookup_commit(sha1), &parents);
}
check_valid(tree, OBJ_TREE);
/* Not having i18n.commitencoding is the same as having utf-8 */
encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1));
strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
/*
* NOTE! This ordering means that the same exact tree merged with a
@@ -102,14 +80,47 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
strbuf_addch(&buffer, '\n');
/* And add the comment */
if (strbuf_read(&buffer, 0, 0) < 0)
die("git-commit-tree: read returned %s", strerror(errno));
strbuf_addstr(&buffer, msg);
/* And check the encoding */
if (encoding_is_utf8 && !is_utf8(buffer.buf))
fprintf(stderr, commit_utf8_warn);
if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) {
return write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
}
int cmd_commit_tree(int argc, const char **argv, const char *prefix)
{
int i;
struct commit_list *parents = NULL;
unsigned char tree_sha1[20];
unsigned char commit_sha1[20];
struct strbuf buffer = STRBUF_INIT;
git_config(git_default_config, NULL);
if (argc < 2)
usage(commit_tree_usage);
if (get_sha1(argv[1], tree_sha1))
die("Not a valid object name %s", argv[1]);
for (i = 2; i < argc; i += 2) {
unsigned char sha1[20];
const char *a, *b;
a = argv[i]; b = argv[i+1];
if (!b || strcmp(a, "-p"))
usage(commit_tree_usage);
if (get_sha1(b, sha1))
die("Not a valid object name %s", b);
check_valid(sha1, OBJ_COMMIT);
new_parent(lookup_commit(sha1), &parents);
}
if (strbuf_read(&buffer, 0, 0) < 0)
die("git-commit-tree: read returned %s", strerror(errno));
if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
printf("%s\n", sha1_to_hex(commit_sha1));
return 0;
}

View File

@@ -22,6 +22,7 @@
#include "utf8.h"
#include "parse-options.h"
#include "path-list.h"
#include "rerere.h"
#include "unpack-trees.h"
static const char * const builtin_commit_usage[] = {

View File

@@ -159,23 +159,24 @@ static int handle_line(char *line)
}
static void print_joined(const char *singular, const char *plural,
struct list *list)
struct list *list, struct strbuf *out)
{
if (list->nr == 0)
return;
if (list->nr == 1) {
printf("%s%s", singular, list->list[0]);
strbuf_addf(out, "%s%s", singular, list->list[0]);
} else {
int i;
printf("%s", plural);
strbuf_addstr(out, plural);
for (i = 0; i < list->nr - 1; i++)
printf("%s%s", i > 0 ? ", " : "", list->list[i]);
printf(" and %s", list->list[list->nr - 1]);
strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);
strbuf_addf(out, " and %s", list->list[list->nr - 1]);
}
}
static void shortlog(const char *name, unsigned char *sha1,
struct commit *head, struct rev_info *rev, int limit)
struct commit *head, struct rev_info *rev, int limit,
struct strbuf *out)
{
int i, count = 0;
struct commit *commit;
@@ -232,15 +233,15 @@ static void shortlog(const char *name, unsigned char *sha1,
}
if (count > limit)
printf("\n* %s: (%d commits)\n", name, count);
strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
else
printf("\n* %s:\n", name);
strbuf_addf(out, "\n* %s:\n", name);
for (i = 0; i < subjects.nr; i++)
if (i >= limit)
printf(" ...\n");
strbuf_addf(out, " ...\n");
else
printf(" %s\n", subjects.list[i]);
strbuf_addf(out, " %s\n", subjects.list[i]);
clear_commit_marks((struct commit *)branch, flags);
clear_commit_marks(head, flags);
@@ -251,15 +252,105 @@ static void shortlog(const char *name, unsigned char *sha1,
free_list(&subjects);
}
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
int limit = 20, i = 0;
int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
int limit = 20, i = 0, pos = 0;
char line[1024];
FILE *in = stdin;
const char *sep = "";
char *p = line, *sep = "";
unsigned char head_sha1[20];
const char *current_branch;
/* get current branch */
current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
if (!current_branch)
die("No current branch");
if (!prefixcmp(current_branch, "refs/heads/"))
current_branch += 11;
/* get a line */
while (pos < in->len) {
int len;
char *newline;
p = in->buf + pos;
newline = strchr(p, '\n');
len = newline ? newline - p : strlen(p);
pos += len + !!newline;
i++;
p[len] = 0;
if (handle_line(p))
die ("Error in line %d: %.*s", i, len, p);
}
strbuf_addstr(out, "Merge ");
for (i = 0; i < srcs.nr; i++) {
struct src_data *src_data = srcs.payload[i];
const char *subsep = "";
strbuf_addstr(out, sep);
sep = "; ";
if (src_data->head_status == 1) {
strbuf_addstr(out, srcs.list[i]);
continue;
}
if (src_data->head_status == 3) {
subsep = ", ";
strbuf_addstr(out, "HEAD");
}
if (src_data->branch.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("branch ", "branches ", &src_data->branch,
out);
}
if (src_data->r_branch.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("remote branch ", "remote branches ",
&src_data->r_branch, out);
}
if (src_data->tag.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("tag ", "tags ", &src_data->tag, out);
}
if (src_data->generic.nr) {
strbuf_addstr(out, subsep);
print_joined("commit ", "commits ", &src_data->generic,
out);
}
if (strcmp(".", srcs.list[i]))
strbuf_addf(out, " of %s", srcs.list[i]);
}
if (!strcmp("master", current_branch))
strbuf_addch(out, '\n');
else
strbuf_addf(out, " into %s\n", current_branch);
if (merge_summary) {
struct commit *head;
struct rev_info rev;
head = lookup_commit(head_sha1);
init_revisions(&rev, NULL);
rev.commit_format = CMIT_FMT_ONELINE;
rev.ignore_merges = 1;
rev.limited = 1;
for (i = 0; i < origins.nr; i++)
shortlog(origins.list[i], origins.payload[i],
head, &rev, limit, out);
}
return 0;
}
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
FILE *in = stdin;
struct strbuf input, output;
int ret;
git_config(fmt_merge_msg_config, NULL);
while (argc > 1) {
@@ -288,82 +379,14 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
if (argc > 1)
usage(fmt_merge_msg_usage);
/* get current branch */
current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
if (!current_branch)
die("No current branch");
if (!prefixcmp(current_branch, "refs/heads/"))
current_branch += 11;
while (fgets(line, sizeof(line), in)) {
i++;
if (line[0] == 0)
continue;
if (handle_line(line))
die ("Error in line %d: %s", i, line);
}
printf("Merge ");
for (i = 0; i < srcs.nr; i++) {
struct src_data *src_data = srcs.payload[i];
const char *subsep = "";
printf(sep);
sep = "; ";
if (src_data->head_status == 1) {
printf(srcs.list[i]);
continue;
}
if (src_data->head_status == 3) {
subsep = ", ";
printf("HEAD");
}
if (src_data->branch.nr) {
printf(subsep);
subsep = ", ";
print_joined("branch ", "branches ", &src_data->branch);
}
if (src_data->r_branch.nr) {
printf(subsep);
subsep = ", ";
print_joined("remote branch ", "remote branches ",
&src_data->r_branch);
}
if (src_data->tag.nr) {
printf(subsep);
subsep = ", ";
print_joined("tag ", "tags ", &src_data->tag);
}
if (src_data->generic.nr) {
printf(subsep);
print_joined("commit ", "commits ", &src_data->generic);
}
if (strcmp(".", srcs.list[i]))
printf(" of %s", srcs.list[i]);
}
if (!strcmp("master", current_branch))
putchar('\n');
else
printf(" into %s\n", current_branch);
if (merge_summary) {
struct commit *head;
struct rev_info rev;
head = lookup_commit(head_sha1);
init_revisions(&rev, prefix);
rev.commit_format = CMIT_FMT_ONELINE;
rev.ignore_merges = 1;
rev.limited = 1;
for (i = 0; i < origins.nr; i++)
shortlog(origins.list[i], origins.payload[i],
head, &rev, limit);
}
/* No cleanup yet; is standalone anyway */
strbuf_init(&input, 0);
if (strbuf_read(&input, fileno(in), 0) < 0)
die("could not read input file %s", strerror(errno));
strbuf_init(&output, 0);
ret = fmt_merge_msg(merge_summary, &input, &output);
if (ret)
return ret;
printf("%s", output.buf);
return 0;
}

View File

@@ -334,7 +334,9 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over
return 1;
if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
for (i = 0; header[i]; i++) {
if (!memcmp("Subject: ", header[i], 9)) {
if (!memcmp("Subject", header[i], 7)) {
if (!hdr_data[i])
hdr_data[i] = xmalloc(linesize + 20);
if (! handle_header(line, hdr_data[i], 0)) {
return 1;
}
@@ -960,7 +962,7 @@ static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
}
static const char mailinfo_usage[] =
"git-mailinfo [-k] [-u | --encoding=<encoding>] msg patch <mail >info";
"git-mailinfo [-k] [-u | --encoding=<encoding> | -n] msg patch <mail >info";
int cmd_mailinfo(int argc, const char **argv, const char *prefix)
{

View File

@@ -9,7 +9,7 @@
#include "path-list.h"
static const char git_mailsplit_usage[] =
"git-mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> <mbox>|<Maildir>...";
"git-mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> [<mbox>|<Maildir>...]";
static int is_from_line(const char *line, int len)
{

View File

@@ -42,14 +42,6 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two)
* - *(int *)commit->object.sha1 set to the virtual id.
*/
static unsigned commit_list_count(const struct commit_list *l)
{
unsigned c = 0;
for (; l; l = l->next )
c++;
return c;
}
static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
{
struct commit *commit = xcalloc(1, sizeof(struct commit));

1156
builtin-merge.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -29,30 +29,6 @@ static int list_tree(unsigned char *sha1)
return 0;
}
static int read_cache_unmerged(void)
{
int i;
struct cache_entry **dst;
struct cache_entry *last = NULL;
read_cache();
dst = active_cache;
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce)) {
remove_name_hash(ce);
if (last && !strcmp(ce->name, last->name))
continue;
cache_tree_invalidate_path(active_cache_tree, ce->name);
last = ce;
continue;
}
*dst++ = ce;
}
active_nr = dst - active_cache;
return !!last;
}
static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
{
struct tree_desc desc;

View File

@@ -29,12 +29,6 @@ static inline int postfixcmp(const char *string, const char *postfix)
return strcmp(string + len1 - len2, postfix);
}
static inline const char *skip_prefix(const char *name, const char *prefix)
{
return !name ? "" :
prefixcmp(name, prefix) ? name : name + strlen(prefix);
}
static int opt_parse_track(const struct option *opt, const char *arg, int not)
{
struct path_list *list = opt->value;
@@ -182,12 +176,18 @@ static int config_read_branches(const char *key, const char *value, void *cb)
info->remote = xstrdup(value);
} else {
char *space = strchr(value, ' ');
value = skip_prefix(value, "refs/heads/");
const char *ptr = skip_prefix(value, "refs/heads/");
if (ptr)
value = ptr;
while (space) {
char *merge;
merge = xstrndup(value, space - value);
path_list_append(merge, &info->merge);
value = skip_prefix(space + 1, "refs/heads/");
ptr = skip_prefix(space + 1, "refs/heads/");
if (ptr)
value = ptr;
else
value = space + 1;
space = strchr(value, ' ');
}
path_list_append(xstrdup(value), &info->merge);
@@ -219,7 +219,12 @@ static int handle_one_branch(const char *refname,
refspec.dst = (char *)refname;
if (!remote_find_tracking(states->remote, &refspec)) {
struct path_list_item *item;
const char *name = skip_prefix(refspec.src, "refs/heads/");
const char *name, *ptr;
ptr = skip_prefix(refspec.src, "refs/heads/");
if (ptr)
name = ptr;
else
name = refspec.src;
/* symbolic refs pointing nowhere were handled already */
if ((flags & REF_ISSYMREF) ||
unsorted_path_list_has_path(&states->tracked,
@@ -248,6 +253,7 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
struct path_list *target = &states->tracked;
unsigned char sha1[20];
void *util = NULL;
const char *ptr;
if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
target = &states->new;
@@ -256,8 +262,10 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
if (hashcmp(sha1, ref->new_sha1))
util = &states;
}
path_list_append(skip_prefix(ref->name, "refs/heads/"),
target)->util = util;
ptr = skip_prefix(ref->name, "refs/heads/");
if (!ptr)
ptr = ref->name;
path_list_append(ptr, target)->util = util;
}
free_refs(fetch_map);
@@ -522,10 +530,15 @@ static int show(int argc, const char **argv)
"es" : "");
for (i = 0; i < states.remote->push_refspec_nr; i++) {
struct refspec *spec = states.remote->push + i;
const char *p = "", *q = "";
if (spec->src)
p = skip_prefix(spec->src, "refs/heads/");
if (spec->dst)
q = skip_prefix(spec->dst, "refs/heads/");
printf(" %s%s%s%s", spec->force ? "+" : "",
skip_prefix(spec->src, "refs/heads/"),
p ? p : spec->src,
spec->dst ? ":" : "",
skip_prefix(spec->dst, "refs/heads/"));
q ? q : spec->dst);
}
printf("\n");
}

View File

@@ -1,11 +1,10 @@
#include "builtin.h"
#include "cache.h"
#include "path-list.h"
#include "rerere.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
#include <time.h>
static const char git_rerere_usage[] =
"git-rerere [clear | status | diff | gc]";
@@ -13,14 +12,6 @@ static const char git_rerere_usage[] =
static int cutoff_noresolve = 15;
static int cutoff_resolve = 60;
/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
static int rerere_enabled = -1;
/* automatically update cleanly resolved paths to the index */
static int rerere_autoupdate;
static char *merge_rr_path;
static const char *rr_path(const char *name, const char *file)
{
return git_path("rr-cache/%s/%s", name, file);
@@ -38,179 +29,6 @@ static int has_resolution(const char *name)
return !stat(rr_path(name, "postimage"), &st);
}
static void read_rr(struct path_list *rr)
{
unsigned char sha1[20];
char buf[PATH_MAX];
FILE *in = fopen(merge_rr_path, "r");
if (!in)
return;
while (fread(buf, 40, 1, in) == 1) {
int i;
char *name;
if (get_sha1_hex(buf, sha1))
die("corrupt MERGE_RR");
buf[40] = '\0';
name = xstrdup(buf);
if (fgetc(in) != '\t')
die("corrupt MERGE_RR");
for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
; /* do nothing */
if (i == sizeof(buf))
die("filename too long");
path_list_insert(buf, rr)->util = name;
}
fclose(in);
}
static struct lock_file write_lock;
static int write_rr(struct path_list *rr, int out_fd)
{
int i;
for (i = 0; i < rr->nr; i++) {
const char *path;
int length;
if (!rr->items[i].util)
continue;
path = rr->items[i].path;
length = strlen(path) + 1;
if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
write_in_full(out_fd, "\t", 1) != 1 ||
write_in_full(out_fd, path, length) != length)
die("unable to write rerere record");
}
if (commit_lock_file(&write_lock) != 0)
die("unable to write rerere record");
return 0;
}
static int handle_file(const char *path,
unsigned char *sha1, const char *output)
{
SHA_CTX ctx;
char buf[1024];
int hunk = 0, hunk_no = 0;
struct strbuf one, two;
FILE *f = fopen(path, "r");
FILE *out = NULL;
if (!f)
return error("Could not open %s", path);
if (output) {
out = fopen(output, "w");
if (!out) {
fclose(f);
return error("Could not write %s", output);
}
}
if (sha1)
SHA1_Init(&ctx);
strbuf_init(&one, 0);
strbuf_init(&two, 0);
while (fgets(buf, sizeof(buf), f)) {
if (!prefixcmp(buf, "<<<<<<< "))
hunk = 1;
else if (!prefixcmp(buf, "======="))
hunk = 2;
else if (!prefixcmp(buf, ">>>>>>> ")) {
if (strbuf_cmp(&one, &two) > 0)
strbuf_swap(&one, &two);
hunk_no++;
hunk = 0;
if (out) {
fputs("<<<<<<<\n", out);
fwrite(one.buf, one.len, 1, out);
fputs("=======\n", out);
fwrite(two.buf, two.len, 1, out);
fputs(">>>>>>>\n", out);
}
if (sha1) {
SHA1_Update(&ctx, one.buf ? one.buf : "",
one.len + 1);
SHA1_Update(&ctx, two.buf ? two.buf : "",
two.len + 1);
}
strbuf_reset(&one);
strbuf_reset(&two);
} else if (hunk == 1)
strbuf_addstr(&one, buf);
else if (hunk == 2)
strbuf_addstr(&two, buf);
else if (out)
fputs(buf, out);
}
strbuf_release(&one);
strbuf_release(&two);
fclose(f);
if (out)
fclose(out);
if (sha1)
SHA1_Final(sha1, &ctx);
if (hunk) {
if (output)
unlink(output);
return error("Could not parse conflict hunks in %s", path);
}
return hunk_no;
}
static int find_conflict(struct path_list *conflict)
{
int i;
if (read_cache() < 0)
return error("Could not read index");
for (i = 0; i+1 < active_nr; i++) {
struct cache_entry *e2 = active_cache[i];
struct cache_entry *e3 = active_cache[i+1];
if (ce_stage(e2) == 2 &&
ce_stage(e3) == 3 &&
ce_same_name(e2, e3) &&
S_ISREG(e2->ce_mode) &&
S_ISREG(e3->ce_mode)) {
path_list_insert((const char *)e2->name, conflict);
i++; /* skip over both #2 and #3 */
}
}
return 0;
}
static int merge(const char *name, const char *path)
{
int ret;
mmfile_t cur, base, other;
mmbuffer_t result = {NULL, 0};
xpparam_t xpp = {XDF_NEED_MINIMAL};
if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
return 1;
if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
read_mmfile(&base, rr_path(name, "preimage")) ||
read_mmfile(&other, rr_path(name, "postimage")))
return 1;
ret = xdl_merge(&base, &cur, "", &other, "",
&xpp, XDL_MERGE_ZEALOUS, &result);
if (!ret) {
FILE *f = fopen(path, "w");
if (!f)
return error("Could not write to %s", path);
fwrite(result.ptr, result.size, 1, f);
fclose(f);
}
free(cur.ptr);
free(base.ptr);
free(other.ptr);
free(result.ptr);
return ret;
}
static void unlink_rr_item(const char *name)
{
unlink(rr_path(name, "thisimage"));
@@ -219,6 +37,17 @@ static void unlink_rr_item(const char *name)
rmdir(git_path("rr-cache/%s", name));
}
static int git_rerere_gc_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "gc.rerereresolved"))
cutoff_resolve = git_config_int(var, value);
else if (!strcmp(var, "gc.rerereunresolved"))
cutoff_noresolve = git_config_int(var, value);
else
return git_default_config(var, value, cb);
return 0;
}
static void garbage_collect(struct path_list *rr)
{
struct path_list to_remove = { NULL, 0, 0, 1 };
@@ -227,6 +56,7 @@ static void garbage_collect(struct path_list *rr)
int i, cutoff;
time_t now = time(NULL), then;
git_config(git_rerere_gc_config, NULL);
dir = opendir(git_path("rr-cache"));
while ((e = readdir(dir))) {
const char *name = e->d_name;
@@ -279,181 +109,25 @@ static int diff_two(const char *file1, const char *label1,
return 0;
}
static struct lock_file index_lock;
static int update_paths(struct path_list *update)
{
int i;
int fd = hold_locked_index(&index_lock, 0);
int status = 0;
if (fd < 0)
return -1;
for (i = 0; i < update->nr; i++) {
struct path_list_item *item = &update->items[i];
if (add_file_to_cache(item->path, ADD_CACHE_IGNORE_ERRORS))
status = -1;
}
if (!status && active_cache_changed) {
if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(&index_lock))
die("Unable to write new index file");
} else if (fd >= 0)
rollback_lock_file(&index_lock);
return status;
}
static int do_plain_rerere(struct path_list *rr, int fd)
{
struct path_list conflict = { NULL, 0, 0, 1 };
struct path_list update = { NULL, 0, 0, 1 };
int i;
find_conflict(&conflict);
/*
* MERGE_RR records paths with conflicts immediately after merge
* failed. Some of the conflicted paths might have been hand resolved
* in the working tree since then, but the initial run would catch all
* and register their preimages.
*/
for (i = 0; i < conflict.nr; i++) {
const char *path = conflict.items[i].path;
if (!path_list_has_path(rr, path)) {
unsigned char sha1[20];
char *hex;
int ret;
ret = handle_file(path, sha1, NULL);
if (ret < 1)
continue;
hex = xstrdup(sha1_to_hex(sha1));
path_list_insert(path, rr)->util = hex;
if (mkdir(git_path("rr-cache/%s", hex), 0755))
continue;;
handle_file(path, NULL, rr_path(hex, "preimage"));
fprintf(stderr, "Recorded preimage for '%s'\n", path);
}
}
/*
* Now some of the paths that had conflicts earlier might have been
* hand resolved. Others may be similar to a conflict already that
* was resolved before.
*/
for (i = 0; i < rr->nr; i++) {
int ret;
const char *path = rr->items[i].path;
const char *name = (const char *)rr->items[i].util;
if (has_resolution(name)) {
if (!merge(name, path)) {
fprintf(stderr, "Resolved '%s' using "
"previous resolution.\n", path);
if (rerere_autoupdate)
path_list_insert(path, &update);
goto mark_resolved;
}
}
/* Let's see if we have resolved it. */
ret = handle_file(path, NULL, NULL);
if (ret)
continue;
fprintf(stderr, "Recorded resolution for '%s'.\n", path);
copy_file(rr_path(name, "postimage"), path, 0666);
mark_resolved:
rr->items[i].util = NULL;
}
if (update.nr)
update_paths(&update);
return write_rr(rr, fd);
}
static int git_rerere_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "gc.rerereresolved"))
cutoff_resolve = git_config_int(var, value);
else if (!strcmp(var, "gc.rerereunresolved"))
cutoff_noresolve = git_config_int(var, value);
else if (!strcmp(var, "rerere.enabled"))
rerere_enabled = git_config_bool(var, value);
else if (!strcmp(var, "rerere.autoupdate"))
rerere_autoupdate = git_config_bool(var, value);
else
return git_default_config(var, value, cb);
return 0;
}
static int is_rerere_enabled(void)
{
struct stat st;
const char *rr_cache;
int rr_cache_exists;
if (!rerere_enabled)
return 0;
rr_cache = git_path("rr-cache");
rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode);
if (rerere_enabled < 0)
return rr_cache_exists;
if (!rr_cache_exists &&
(mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))
die("Could not create directory %s", rr_cache);
return 1;
}
static int setup_rerere(struct path_list *merge_rr)
{
int fd;
git_config(git_rerere_config, NULL);
if (!is_rerere_enabled())
return -1;
merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
read_rr(merge_rr);
return fd;
}
int rerere(void)
{
struct path_list merge_rr = { NULL, 0, 0, 1 };
int fd;
fd = setup_rerere(&merge_rr);
if (fd < 0)
return 0;
return do_plain_rerere(&merge_rr, fd);
}
int cmd_rerere(int argc, const char **argv, const char *prefix)
{
struct path_list merge_rr = { NULL, 0, 0, 1 };
int i, fd;
if (argc < 2)
return rerere();
fd = setup_rerere(&merge_rr);
if (fd < 0)
return 0;
if (argc < 2)
return do_plain_rerere(&merge_rr, fd);
else if (!strcmp(argv[1], "clear")) {
if (!strcmp(argv[1], "clear")) {
for (i = 0; i < merge_rr.nr; i++) {
const char *name = (const char *)merge_rr.items[i].util;
if (!has_resolution(name))
unlink_rr_item(name);
}
unlink(merge_rr_path);
unlink(git_path("rr-cache/MERGE_RR"));
} else if (!strcmp(argv[1], "gc"))
garbage_collect(&merge_rr);
else if (!strcmp(argv[1], "status"))

View File

@@ -37,6 +37,7 @@ static const char rev_list_usage[] =
" --reverse\n"
" formatting output:\n"
" --parents\n"
" --children\n"
" --objects | --objects-edge\n"
" --unpacked\n"
" --header | --pretty\n"
@@ -90,6 +91,15 @@ static void show_commit(struct commit *commit)
parents = parents->next;
}
}
if (revs.children.name) {
struct commit_list *children;
children = lookup_decoration(&revs.children, &commit->object);
while (children) {
printf(" %s", sha1_to_hex(children->item->object.sha1));
children = children->next;
}
}
show_decorations(commit);
if (revs.commit_format == CMIT_FMT_ONELINE)
putchar(' ');

View File

@@ -206,6 +206,7 @@ static int merge_recursive(const char *base_sha1,
{
char buffer[256];
const char *argv[6];
int i = 0;
sprintf(buffer, "GITHEAD_%s", head_sha1);
setenv(buffer, head_name, 1);
@@ -218,12 +219,13 @@ static int merge_recursive(const char *base_sha1,
* and $prev on top of us (when reverting), or the change between
* $prev and $commit on top of us (when cherry-picking or replaying).
*/
argv[0] = "merge-recursive";
argv[1] = base_sha1;
argv[2] = "--";
argv[3] = head_sha1;
argv[4] = next_sha1;
argv[5] = NULL;
argv[i++] = "merge-recursive";
if (base_sha1)
argv[i++] = base_sha1;
argv[i++] = "--";
argv[i++] = head_sha1;
argv[i++] = next_sha1;
argv[i++] = NULL;
return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD);
}
@@ -297,9 +299,12 @@ static int revert_or_cherry_pick(int argc, const char **argv)
discard_cache();
}
if (!commit->parents)
die ("Cannot %s a root commit", me);
if (commit->parents->next) {
if (!commit->parents) {
if (action == REVERT)
die ("Cannot revert a root commit");
parent = NULL;
}
else if (commit->parents->next) {
/* Reverting or cherry-picking a merge commit */
int cnt;
struct commit_list *p;
@@ -368,7 +373,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)
}
}
if (merge_recursive(sha1_to_hex(base->object.sha1),
if (merge_recursive(base == NULL ?
NULL : sha1_to_hex(base->object.sha1),
sha1_to_hex(head), "HEAD",
sha1_to_hex(next->object.sha1), oneline) ||
write_cache_as_tree(head, 0, NULL)) {

View File

@@ -7,9 +7,14 @@
#include "utf8.h"
#include "mailmap.h"
#include "shortlog.h"
#include "parse-options.h"
static const char shortlog_usage[] =
"git-shortlog [-n] [-s] [-e] [-w] [<commit-id>... ]";
static char const * const shortlog_usage[] = {
"git-shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]",
"",
"[rev-opts] are documented in git-rev-list(1)",
NULL
};
static int compare_by_number(const void *a1, const void *a2)
{
@@ -149,6 +154,15 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
if (!author)
die("Missing author: %s",
sha1_to_hex(commit->object.sha1));
if (log->user_format) {
struct strbuf buf = STRBUF_INIT;
pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf,
DEFAULT_ABBREV, "", "", DATE_NORMAL, 0);
insert_one_record(log, author, buf.buf);
strbuf_release(&buf);
return;
}
if (*buffer)
buffer++;
insert_one_record(log, author, !*buffer ? "<none>" : buffer);
@@ -164,21 +178,19 @@ static void get_from_rev(struct rev_info *rev, struct shortlog *log)
shortlog_add_commit(log, commit);
}
static int parse_uint(char const **arg, int comma)
static int parse_uint(char const **arg, int comma, int defval)
{
unsigned long ul;
int ret;
char *endp;
ul = strtoul(*arg, &endp, 10);
if (endp != *arg && *endp && *endp != comma)
if (*endp && *endp != comma)
return -1;
ret = (int) ul;
if (ret != ul)
if (ul > INT_MAX)
return -1;
*arg = endp;
if (**arg)
(*arg)++;
ret = *arg == endp ? defval : (int)ul;
*arg = *endp ? endp + 1 : endp;
return ret;
}
@@ -187,30 +199,30 @@ static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
#define DEFAULT_INDENT1 6
#define DEFAULT_INDENT2 9
static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap)
static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
{
arg += 2; /* skip -w */
struct shortlog *log = opt->value;
*wrap = parse_uint(&arg, ',');
if (*wrap < 0)
die(wrap_arg_usage);
*in1 = parse_uint(&arg, ',');
if (*in1 < 0)
die(wrap_arg_usage);
*in2 = parse_uint(&arg, '\0');
if (*in2 < 0)
die(wrap_arg_usage);
log->wrap_lines = !unset;
if (unset)
return 0;
if (!arg) {
log->wrap = DEFAULT_WRAPLEN;
log->in1 = DEFAULT_INDENT1;
log->in2 = DEFAULT_INDENT2;
return 0;
}
if (!*wrap)
*wrap = DEFAULT_WRAPLEN;
if (!*in1)
*in1 = DEFAULT_INDENT1;
if (!*in2)
*in2 = DEFAULT_INDENT2;
if (*wrap &&
((*in1 && *wrap <= *in1) ||
(*in2 && *wrap <= *in2)))
die(wrap_arg_usage);
log->wrap = parse_uint(&arg, ',', DEFAULT_WRAPLEN);
log->in1 = parse_uint(&arg, ',', DEFAULT_INDENT1);
log->in2 = parse_uint(&arg, '\0', DEFAULT_INDENT2);
if (log->wrap < 0 || log->in1 < 0 || log->in2 < 0)
return error(wrap_arg_usage);
if (log->wrap &&
((log->in1 && log->wrap <= log->in1) ||
(log->in2 && log->wrap <= log->in2)))
return error(wrap_arg_usage);
return 0;
}
void shortlog_init(struct shortlog *log)
@@ -227,38 +239,48 @@ void shortlog_init(struct shortlog *log)
int cmd_shortlog(int argc, const char **argv, const char *prefix)
{
struct shortlog log;
struct rev_info rev;
static struct shortlog log;
static struct rev_info rev;
int nongit;
static const struct option options[] = {
OPT_BOOLEAN('n', "numbered", &log.sort_by_number,
"sort output according to the number of commits per author"),
OPT_BOOLEAN('s', "summary", &log.summary,
"Suppress commit descriptions, only provides commit count"),
OPT_BOOLEAN('e', "email", &log.email,
"Show the email address of each author"),
{ OPTION_CALLBACK, 'w', NULL, &log, "w[,i1[,i2]]",
"Linewrap output", PARSE_OPT_OPTARG, &parse_wrap_args },
OPT_END(),
};
struct parse_opt_ctx_t ctx;
prefix = setup_git_directory_gently(&nongit);
shortlog_init(&log);
/* since -n is a shadowed rev argument, parse our args first */
while (argc > 1) {
if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
log.sort_by_number = 1;
else if (!strcmp(argv[1], "-s") ||
!strcmp(argv[1], "--summary"))
log.summary = 1;
else if (!strcmp(argv[1], "-e") ||
!strcmp(argv[1], "--email"))
log.email = 1;
else if (!prefixcmp(argv[1], "-w")) {
log.wrap_lines = 1;
parse_wrap_args(argv[1], &log.in1, &log.in2, &log.wrap);
}
else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
usage(shortlog_usage);
else
break;
argv++;
argc--;
}
init_revisions(&rev, prefix);
argc = setup_revisions(argc, argv, &rev, NULL);
if (argc > 1)
die ("unrecognized argument: %s", argv[1]);
parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_KEEP_ARGV0);
for (;;) {
switch (parse_options_step(&ctx, options, shortlog_usage)) {
case PARSE_OPT_HELP:
exit(129);
case PARSE_OPT_DONE:
goto parse_done;
}
parse_revision_opt(&rev, &ctx, options, shortlog_usage);
}
parse_done:
argc = parse_options_end(&ctx);
if (setup_revisions(argc, argv, &rev, NULL) != 1) {
error("unrecognized argument: %s", argv[1]);
usage_with_options(shortlog_usage, options);
}
log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
/* assume HEAD if from a tty */
if (!nongit && !rev.pending.nr && isatty(0))

View File

@@ -202,6 +202,7 @@ static int do_sign(struct strbuf *buffer)
const char *args[4];
char *bracket;
int len;
int i, j;
if (!*signingkey) {
if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
@@ -241,6 +242,15 @@ static int do_sign(struct strbuf *buffer)
if (finish_command(&gpg) || !len || len < 0)
return error("gpg failed to sign the tag");
/* Strip CR from the line endings, in case we are on Windows. */
for (i = j = 0; i < buffer->len; i++)
if (buffer->buf[i] != '\r') {
if (i != j)
buffer->buf[j] = buffer->buf[i];
j++;
}
strbuf_setlen(buffer, j);
return 0;
}

View File

@@ -2,6 +2,9 @@
#define BUILTIN_H
#include "git-compat-util.h"
#include "strbuf.h"
#include "cache.h"
#include "commit.h"
extern const char git_version_string[];
extern const char git_usage_string[];
@@ -11,6 +14,10 @@ extern void list_common_cmds_help(void);
extern void help_unknown_cmd(const char *cmd);
extern void prune_packed_objects(int);
extern int read_line_with_nul(char *buf, int size, FILE *file);
extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
struct strbuf *out);
extern int commit_tree(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret);
extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
@@ -57,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
extern int cmd_merge(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix);

View File

@@ -178,6 +178,7 @@ int create_bundle(struct bundle_header *header, const char *path,
int i, ref_count = 0;
char buffer[1024];
struct rev_info revs;
int read_from_stdin = 0;
struct child_process rls;
FILE *rls_fout;
@@ -227,8 +228,16 @@ int create_bundle(struct bundle_header *header, const char *path,
/* write references */
argc = setup_revisions(argc, argv, &revs, NULL);
if (argc > 1)
return error("unrecognized argument: %s'", argv[1]);
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--stdin")) {
if (read_from_stdin++)
die("--stdin given twice?");
read_revisions_from_stdin(&revs);
continue;
}
return error("unrecognized argument: %s'", argv[i]);
}
for (i = 0; i < revs.pending.nr; i++) {
struct object_array_entry *e = revs.pending.objects + i;

View File

@@ -254,6 +254,7 @@ static inline void remove_name_hash(struct cache_entry *ce)
#define read_cache() read_index(&the_index)
#define read_cache_from(path) read_index_from(&the_index, (path))
#define read_cache_unmerged() read_index_unmerged(&the_index)
#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
#define discard_cache() discard_index(&the_index)
#define unmerged_cache() unmerged_index(&the_index)
@@ -356,6 +357,7 @@ extern int init_db(const char *template_dir, unsigned int flags);
/* Initialize and use the cache information */
extern int read_index(struct index_state *);
extern int read_index_from(struct index_state *, const char *path);
extern int read_index_unmerged(struct index_state *);
extern int write_index(const struct index_state *, int newfd);
extern int discard_index(struct index_state *);
extern int unmerged_index(const struct index_state *);
@@ -538,6 +540,9 @@ extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsig
extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
extern int force_object_loose(const unsigned char *sha1, time_t mtime);
/* just like read_sha1_file(), but non fatal in presence of bad objects */
extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size);
extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
extern int move_temp_to_file(const char *tmpfile, const char *filename);
@@ -834,5 +839,6 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
void overlay_tree_on_cache(const char *tree_name, const char *prefix);
char *alias_lookup(const char *alias);
int split_cmdline(char *cmdline, const char ***argv);
#endif /* CACHE_H */

143
commit.c
View File

@@ -325,6 +325,14 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list *
return new_list;
}
unsigned commit_list_count(const struct commit_list *l)
{
unsigned c = 0;
for (; l; l = l->next )
c++;
return c;
}
void free_commit_list(struct commit_list *list)
{
while (list) {
@@ -525,26 +533,34 @@ static struct commit *interesting(struct commit_list *list)
return NULL;
}
static struct commit_list *merge_bases(struct commit *one, struct commit *two)
static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
{
struct commit_list *list = NULL;
struct commit_list *result = NULL;
int i;
if (one == two)
/* We do not mark this even with RESULT so we do not
* have to clean it up.
*/
return commit_list_insert(one, &result);
for (i = 0; i < n; i++) {
if (one == twos[i])
/*
* We do not mark this even with RESULT so we do not
* have to clean it up.
*/
return commit_list_insert(one, &result);
}
if (parse_commit(one))
return NULL;
if (parse_commit(two))
return NULL;
for (i = 0; i < n; i++) {
if (parse_commit(twos[i]))
return NULL;
}
one->object.flags |= PARENT1;
two->object.flags |= PARENT2;
insert_by_date(one, &list);
insert_by_date(two, &list);
for (i = 0; i < n; i++) {
twos[i]->object.flags |= PARENT2;
insert_by_date(twos[i], &list);
}
while (interesting(list)) {
struct commit *commit;
@@ -592,21 +608,53 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
return result;
}
struct commit_list *get_merge_bases(struct commit *one,
struct commit *two, int cleanup)
struct commit_list *get_octopus_merge_bases(struct commit_list *in)
{
struct commit_list *i, *j, *k, *ret = NULL;
struct commit_list **pptr = &ret;
for (i = in; i; i = i->next) {
if (!ret)
pptr = &commit_list_insert(i->item, pptr)->next;
else {
struct commit_list *new = NULL, *end = NULL;
for (j = ret; j; j = j->next) {
struct commit_list *bases;
bases = get_merge_bases(i->item, j->item, 1);
if (!new)
new = bases;
else
end->next = bases;
for (k = bases; k; k = k->next)
end = k;
}
ret = new;
}
}
return ret;
}
struct commit_list *get_merge_bases_many(struct commit *one,
int n,
struct commit **twos,
int cleanup)
{
struct commit_list *list;
struct commit **rslt;
struct commit_list *result;
int cnt, i, j;
result = merge_bases(one, two);
if (one == two)
return result;
result = merge_bases_many(one, n, twos);
for (i = 0; i < n; i++) {
if (one == twos[i])
return result;
}
if (!result || !result->next) {
if (cleanup) {
clear_commit_marks(one, all_flags);
clear_commit_marks(two, all_flags);
for (i = 0; i < n; i++)
clear_commit_marks(twos[i], all_flags);
}
return result;
}
@@ -624,12 +672,13 @@ struct commit_list *get_merge_bases(struct commit *one,
free_commit_list(result);
clear_commit_marks(one, all_flags);
clear_commit_marks(two, all_flags);
for (i = 0; i < n; i++)
clear_commit_marks(twos[i], all_flags);
for (i = 0; i < cnt - 1; i++) {
for (j = i+1; j < cnt; j++) {
if (!rslt[i] || !rslt[j])
continue;
result = merge_bases(rslt[i], rslt[j]);
result = merge_bases_many(rslt[i], 1, &rslt[j]);
clear_commit_marks(rslt[i], all_flags);
clear_commit_marks(rslt[j], all_flags);
for (list = result; list; list = list->next) {
@@ -651,6 +700,12 @@ struct commit_list *get_merge_bases(struct commit *one,
return result;
}
struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
int cleanup)
{
return get_merge_bases_many(one, 1, &two, cleanup);
}
int in_merge_bases(struct commit *commit, struct commit **reference, int num)
{
struct commit_list *bases, *b;
@@ -670,3 +725,55 @@ int in_merge_bases(struct commit *commit, struct commit **reference, int num)
free_commit_list(bases);
return ret;
}
struct commit_list *reduce_heads(struct commit_list *heads)
{
struct commit_list *p;
struct commit_list *result = NULL, **tail = &result;
struct commit **other;
size_t num_head, num_other;
if (!heads)
return NULL;
/* Avoid unnecessary reallocations */
for (p = heads, num_head = 0; p; p = p->next)
num_head++;
other = xcalloc(sizeof(*other), num_head);
/* For each commit, see if it can be reached by others */
for (p = heads; p; p = p->next) {
struct commit_list *q, *base;
/* Do we already have this in the result? */
for (q = result; q; q = q->next)
if (p->item == q->item)
break;
if (q)
continue;
num_other = 0;
for (q = heads; q; q = q->next) {
if (p->item == q->item)
continue;
other[num_other++] = q->item;
}
if (num_other)
base = get_merge_bases_many(p->item, num_other, other, 1);
else
base = NULL;
/*
* If p->item does not have anything common with other
* commits, there won't be any merge base. If it is
* reachable from some of the others, p->item will be
* the merge base. If its history is connected with
* others, but p->item is not reachable by others, we
* will get something other than p->item back.
*/
if (!base || (base->item != p->item))
tail = &(commit_list_insert(p->item, tail)->next);
free_commit_list(base);
}
free(other);
return result;
}

View File

@@ -41,6 +41,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
int parse_commit(struct commit *item);
struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
unsigned commit_list_count(const struct commit_list *l);
struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
void free_commit_list(struct commit_list *list);
@@ -120,6 +121,7 @@ int read_graft_file(const char *graft_file);
struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
extern int register_shallow(const unsigned char *sha1);
extern int unregister_shallow(const unsigned char *sha1);
@@ -131,11 +133,12 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
int in_merge_bases(struct commit *, struct commit **, int);
extern int interactive_add(int argc, const char **argv, const char *prefix);
extern int rerere(void);
static inline int single_parent(struct commit *commit)
{
return commit->parents && !commit->parents->next;
}
struct commit_list *reduce_heads(struct commit_list *heads);
#endif /* COMMIT_H */

View File

@@ -45,6 +45,11 @@
# git@vger.kernel.org
#
case "$COMP_WORDBREAKS" in
*:*) : great ;;
*) COMP_WORDBREAKS="$COMP_WORDBREAKS:"
esac
__gitdir ()
{
if [ -z "$1" ]; then
@@ -68,26 +73,26 @@ __git_ps1 ()
if [ -n "$g" ]; then
local r
local b
if [ -d "$g/../.dotest" ]
if [ -d "$g/rebase" ]
then
if test -f "$g/../.dotest/rebasing"
if test -f "$g/rebase/rebasing"
then
r="|REBASE"
elif test -f "$g/../.dotest/applying"
elif test -f "$g/rebase/applying"
then
r="|AM"
else
r="|AM/REBASE"
fi
b="$(git symbolic-ref HEAD 2>/dev/null)"
elif [ -f "$g/.dotest-merge/interactive" ]
elif [ -f "$g/rebase-merge/interactive" ]
then
r="|REBASE-i"
b="$(cat "$g/.dotest-merge/head-name")"
elif [ -d "$g/.dotest-merge" ]
b="$(cat "$g/rebase-merge/head-name")"
elif [ -d "$g/rebase-merge" ]
then
r="|REBASE-m"
b="$(cat "$g/.dotest-merge/head-name")"
b="$(cat "$g/rebase-merge/head-name")"
elif [ -f "$g/MERGE_HEAD" ]
then
r="|MERGING"
@@ -114,9 +119,20 @@ __git_ps1 ()
fi
}
__gitcomp_1 ()
{
local c IFS=' '$'\t'$'\n'
for c in $1; do
case "$c$2" in
--*=*) printf %s$'\n' "$c$2" ;;
*.) printf %s$'\n' "$c$2" ;;
*) printf %s$'\n' "$c$2 " ;;
esac
done
}
__gitcomp ()
{
local all c s=$'\n' IFS=' '$'\t'$'\n'
local cur="${COMP_WORDS[COMP_CWORD]}"
if [ $# -gt 2 ]; then
cur="$3"
@@ -124,21 +140,14 @@ __gitcomp ()
case "$cur" in
--*=)
COMPREPLY=()
return
;;
*)
for c in $1; do
case "$c$4" in
--*=*) all="$all$c$4$s" ;;
*.) all="$all$c$4$s" ;;
*) all="$all$c$4 $s" ;;
esac
done
local IFS=$'\n'
COMPREPLY=($(compgen -P "$2" \
-W "$(__gitcomp_1 "$1" "$4")" \
-- "$cur"))
;;
esac
IFS=$s
COMPREPLY=($(compgen -P "$2" -W "$all" -- "$cur"))
return
}
__git_heads ()
@@ -290,9 +299,23 @@ __git_complete_file ()
ls="$ref"
;;
esac
case "$COMP_WORDBREAKS" in
*:*) : great ;;
*) pfx="$ref:$pfx" ;;
esac
local IFS=$'\n'
COMPREPLY=($(compgen -P "$pfx" \
-W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
| sed '/^100... blob /s,^.* ,,
| sed '/^100... blob /{
s,^.* ,,
s,$, ,
}
/^120000 blob /{
s,^.* ,,
s,$, ,
}
/^040000 tree /{
s,^.* ,,
s,$,/,
@@ -320,9 +343,6 @@ __git_complete_revlist ()
cur="${cur#*..}"
__gitcomp "$(__git_refs)" "$pfx" "$cur"
;;
*.)
__gitcomp "$cur."
;;
*)
__gitcomp "$(__git_refs)"
;;
@@ -467,8 +487,8 @@ __git_whitespacelist="nowarn warn error error-all strip"
_git_am ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
if [ -d .dotest ]; then
local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
if [ -d "$dir"/rebase ]; then
__gitcomp "--skip --resolved"
return
fi
@@ -691,7 +711,12 @@ _git_fetch ()
*)
case "$cur" in
*:*)
__gitcomp "$(__git_refs)" "" "${cur#*:}"
local pfx=""
case "$COMP_WORDBREAKS" in
*:*) : great ;;
*) pfx="${cur%%:*}:" ;;
esac
__gitcomp "$(__git_refs)" "$pfx" "${cur#*:}"
;;
*)
local remote
@@ -867,7 +892,14 @@ _git_push ()
git-push) remote="${COMP_WORDS[1]}" ;;
git) remote="${COMP_WORDS[2]}" ;;
esac
__gitcomp "$(__git_refs "$remote")" "" "${cur#*:}"
local pfx=""
case "$COMP_WORDBREAKS" in
*:*) : great ;;
*) pfx="${cur%%:*}:" ;;
esac
__gitcomp "$(__git_refs "$remote")" "$pfx" "${cur#*:}"
;;
+*)
__gitcomp "$(__git_refs)" + "${cur#+}"
@@ -883,7 +915,7 @@ _git_push ()
_git_rebase ()
{
local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
if [ -d .dotest ] || [ -d "$dir"/.dotest-merge ]; then
if [ -d "$dir"/rebase ] || [ -d "$dir"/rebase-merge ]; then
__gitcomp "--continue --skip --abort"
return
fi
@@ -904,6 +936,24 @@ _git_rebase ()
__gitcomp "$(__git_refs)"
}
_git_send_email ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--bcc --cc --cc-cmd --chain-reply-to --compose
--dry-run --envelope-sender --from --identity
--in-reply-to --no-chain-reply-to --no-signed-off-by-cc
--no-suppress-from --no-thread --quiet
--signed-off-by-cc --smtp-pass --smtp-server
--smtp-server-port --smtp-ssl --smtp-user --subject
--suppress-cc --suppress-from --thread --to"
return
;;
esac
COMPREPLY=()
}
_git_config ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -1163,8 +1213,19 @@ _git_show ()
_git_stash ()
{
local subcommands='save list show apply clear drop pop create'
if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
local subcommand="$(__git_find_subcommand "$subcommands")"
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
else
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$subcommand,$cur" in
save,--*)
__gitcomp "--keep-index"
;;
*)
COMPREPLY=()
;;
esac
fi
}
@@ -1364,6 +1425,7 @@ _git ()
rebase) _git_rebase ;;
remote) _git_remote ;;
reset) _git_reset ;;
send-email) _git_send_email ;;
shortlog) _git_shortlog ;;
show) _git_show ;;
show-branch) _git_log ;;
@@ -1397,64 +1459,11 @@ _gitk ()
complete -o default -o nospace -F _git git
complete -o default -o nospace -F _gitk gitk
complete -o default -o nospace -F _git_am git-am
complete -o default -o nospace -F _git_apply git-apply
complete -o default -o nospace -F _git_bisect git-bisect
complete -o default -o nospace -F _git_branch git-branch
complete -o default -o nospace -F _git_bundle git-bundle
complete -o default -o nospace -F _git_checkout git-checkout
complete -o default -o nospace -F _git_cherry git-cherry
complete -o default -o nospace -F _git_cherry_pick git-cherry-pick
complete -o default -o nospace -F _git_commit git-commit
complete -o default -o nospace -F _git_describe git-describe
complete -o default -o nospace -F _git_diff git-diff
complete -o default -o nospace -F _git_fetch git-fetch
complete -o default -o nospace -F _git_format_patch git-format-patch
complete -o default -o nospace -F _git_gc git-gc
complete -o default -o nospace -F _git_log git-log
complete -o default -o nospace -F _git_ls_remote git-ls-remote
complete -o default -o nospace -F _git_ls_tree git-ls-tree
complete -o default -o nospace -F _git_merge git-merge
complete -o default -o nospace -F _git_merge_base git-merge-base
complete -o default -o nospace -F _git_name_rev git-name-rev
complete -o default -o nospace -F _git_pull git-pull
complete -o default -o nospace -F _git_push git-push
complete -o default -o nospace -F _git_rebase git-rebase
complete -o default -o nospace -F _git_config git-config
complete -o default -o nospace -F _git_remote git-remote
complete -o default -o nospace -F _git_reset git-reset
complete -o default -o nospace -F _git_shortlog git-shortlog
complete -o default -o nospace -F _git_show git-show
complete -o default -o nospace -F _git_stash git-stash
complete -o default -o nospace -F _git_submodule git-submodule
complete -o default -o nospace -F _git_svn git-svn
complete -o default -o nospace -F _git_log git-show-branch
complete -o default -o nospace -F _git_tag git-tag
complete -o default -o nospace -F _git_log git-whatchanged
# The following are necessary only for Cygwin, and only are needed
# when the user has tab-completed the executable name and consequently
# included the '.exe' suffix.
#
if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
complete -o default -o nospace -F _git_add git-add.exe
complete -o default -o nospace -F _git_apply git-apply.exe
complete -o default -o nospace -F _git git.exe
complete -o default -o nospace -F _git_branch git-branch.exe
complete -o default -o nospace -F _git_bundle git-bundle.exe
complete -o default -o nospace -F _git_cherry git-cherry.exe
complete -o default -o nospace -F _git_describe git-describe.exe
complete -o default -o nospace -F _git_diff git-diff.exe
complete -o default -o nospace -F _git_format_patch git-format-patch.exe
complete -o default -o nospace -F _git_log git-log.exe
complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
complete -o default -o nospace -F _git_merge_base git-merge-base.exe
complete -o default -o nospace -F _git_name_rev git-name-rev.exe
complete -o default -o nospace -F _git_push git-push.exe
complete -o default -o nospace -F _git_config git-config
complete -o default -o nospace -F _git_shortlog git-shortlog.exe
complete -o default -o nospace -F _git_show git-show.exe
complete -o default -o nospace -F _git_log git-show-branch.exe
complete -o default -o nospace -F _git_tag git-tag.exe
complete -o default -o nospace -F _git_log git-whatchanged.exe
fi

View File

@@ -1252,8 +1252,8 @@ Return the list of files that haven't been handled."
"\n")
(when subject (insert subject "\n\n"))
(cond (msg (insert msg "\n"))
((file-readable-p ".dotest/msg")
(insert-file-contents ".dotest/msg"))
((file-readable-p ".git/rebase/msg")
(insert-file-contents ".git/rebase/msg"))
((file-readable-p ".git/MERGE_MSG")
(insert-file-contents ".git/MERGE_MSG")))
; delete empty lines at end
@@ -1272,9 +1272,9 @@ Return the list of files that haven't been handled."
(coding-system (git-get-commits-coding-system))
author-name author-email subject date)
(when (eq 0 (buffer-size buffer))
(when (file-readable-p ".dotest/info")
(when (file-readable-p ".git/rebase/info")
(with-temp-buffer
(insert-file-contents ".dotest/info")
(insert-file-contents ".git/rebase/info")
(goto-char (point-min))
(when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
(setq author-name (match-string 1))

View File

@@ -61,6 +61,10 @@ static void gather_stats(const char *buf, unsigned long size, struct text_stat *
else
stats->printable++;
}
/* If file ends with EOF then don't count this EOF as non-printable. */
if (size >= 1 && buf[size-1] == '\032')
stats->nonprintable--;
}
/*

View File

@@ -18,6 +18,18 @@ my ($fraginfo_color) =
$diff_use_color ? (
$repo->get_color('color.diff.frag', 'cyan'),
) : ();
my ($diff_plain_color) =
$diff_use_color ? (
$repo->get_color('color.diff.plain', ''),
) : ();
my ($diff_old_color) =
$diff_use_color ? (
$repo->get_color('color.diff.old', 'red'),
) : ();
my ($diff_new_color) =
$diff_use_color ? (
$repo->get_color('color.diff.new', 'green'),
) : ();
my $normal_color = $repo->get_color("", "reset");
@@ -42,7 +54,7 @@ sub colored {
my $patch_mode;
sub run_cmd_pipe {
if ($^O eq 'MSWin32') {
if ($^O eq 'MSWin32' || $^O eq 'msys') {
my @invalid = grep {m/[":*]/} @_;
die "$^O does not support: @invalid\n" if @invalid;
my @args = map { m/ /o ? "\"$_\"": $_ } @_;
@@ -394,9 +406,9 @@ sub list_and_choose {
if ($choice =~ s/^-//) {
$choose = 0;
}
# A range can be specified like 5-7
if ($choice =~ /^(\d+)-(\d+)$/) {
($bottom, $top) = ($1, $2);
# A range can be specified like 5-7 or 5-.
if ($choice =~ /^(\d+)-(\d*)$/) {
($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
}
elsif ($choice =~ /^\d+$/) {
$bottom = $top = $choice;
@@ -682,92 +694,104 @@ sub split_hunk {
return @split;
}
sub find_last_o_ctx {
my ($it) = @_;
my $text = $it->{TEXT};
my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
my $i = @{$text};
my $last_o_ctx = $o_ofs + $o_cnt;
while (0 < --$i) {
my $line = $text->[$i];
if ($line =~ /^ /) {
$last_o_ctx--;
next;
}
last;
}
return $last_o_ctx;
sub color_diff {
return map {
colored((/^@/ ? $fraginfo_color :
/^\+/ ? $diff_new_color :
/^-/ ? $diff_old_color :
$diff_plain_color),
$_);
} @_;
}
sub merge_hunk {
my ($prev, $this) = @_;
my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
parse_hunk_header($prev->{TEXT}[0]);
my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
parse_hunk_header($this->{TEXT}[0]);
sub edit_hunk_manually {
my ($oldtext) = @_;
my (@line, $i, $ofs, $o_cnt, $n_cnt);
$ofs = $o0_ofs;
$o_cnt = $n_cnt = 0;
for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
my $line = $prev->{TEXT}[$i];
if ($line =~ /^\+/) {
$n_cnt++;
push @line, $line;
next;
}
my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
my $fh;
open $fh, '>', $hunkfile
or die "failed to open hunk edit file for writing: " . $!;
print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
print $fh @$oldtext;
print $fh <<EOF;
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
#
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging. If it does not apply cleanly, you will be given
# an opportunity to edit again. If all lines of the hunk are removed,
# then the edit is aborted and the hunk is left unchanged.
EOF
close $fh;
last if ($o1_ofs <= $ofs);
my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor")
|| $ENV{VISUAL} || $ENV{EDITOR} || "vi";
system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
$o_cnt++;
$ofs++;
if ($line =~ /^ /) {
$n_cnt++;
}
push @line, $line;
open $fh, '<', $hunkfile
or die "failed to open hunk edit file for reading: " . $!;
my @newtext = grep { !/^#/ } <$fh>;
close $fh;
unlink $hunkfile;
# Abort if nothing remains
if (!grep { /\S/ } @newtext) {
return undef;
}
for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
my $line = $this->{TEXT}[$i];
if ($line =~ /^\+/) {
$n_cnt++;
push @line, $line;
next;
}
$ofs++;
$o_cnt++;
if ($line =~ /^ /) {
$n_cnt++;
}
push @line, $line;
# Reinsert the first hunk header if the user accidentally deleted it
if ($newtext[0] !~ /^@/) {
unshift @newtext, $oldtext->[0];
}
my $head = ("@@ -$o0_ofs" .
(($o_cnt != 1) ? ",$o_cnt" : '') .
" +$n0_ofs" .
(($n_cnt != 1) ? ",$n_cnt" : '') .
" @@\n");
@{$prev->{TEXT}} = ($head, @line);
return \@newtext;
}
sub coalesce_overlapping_hunks {
my (@in) = @_;
my @out = ();
sub diff_applies {
my $fh;
open $fh, '| git apply --recount --cached --check';
for my $h (@_) {
print $fh @{$h->{TEXT}};
}
return close $fh;
}
my ($last_o_ctx);
sub prompt_yesno {
my ($prompt) = @_;
while (1) {
print colored $prompt_color, $prompt;
my $line = <STDIN>;
return 0 if $line =~ /^n/i;
return 1 if $line =~ /^y/i;
}
}
for (grep { $_->{USE} } @in) {
my $text = $_->{TEXT};
my ($o_ofs) = parse_hunk_header($text->[0]);
if (defined $last_o_ctx &&
$o_ofs <= $last_o_ctx) {
merge_hunk($out[-1], $_);
sub edit_hunk_loop {
my ($head, $hunk, $ix) = @_;
my $text = $hunk->[$ix]->{TEXT};
while (1) {
$text = edit_hunk_manually($text);
if (!defined $text) {
return undef;
}
my $newhunk = { TEXT => $text, USE => 1 };
if (diff_applies($head,
@{$hunk}[0..$ix-1],
$newhunk,
@{$hunk}[$ix+1..$#{$hunk}])) {
$newhunk->{DISPLAY} = [color_diff(@{$text})];
return $newhunk;
}
else {
push @out, $_;
prompt_yesno(
'Your edited hunk does not apply. Edit again '
. '(saying "no" discards!) [y/n]? '
) or return undef;
}
$last_o_ctx = find_last_o_ctx($out[-1]);
}
return @out;
}
sub help_patch_cmd {
@@ -781,6 +805,7 @@ J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help
EOF
}
@@ -885,6 +910,7 @@ sub patch_update_file {
if (hunk_splittable($hunk[$ix]{TEXT})) {
$other .= '/s';
}
$other .= '/e';
for (@{$hunk[$ix]{DISPLAY}}) {
print;
}
@@ -949,6 +975,12 @@ sub patch_update_file {
$num = scalar @hunk;
next;
}
elsif ($line =~ /^e/) {
my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
if (defined $newhunk) {
splice @hunk, $ix, 1, $newhunk;
}
}
else {
help_patch_cmd($other);
next;
@@ -962,47 +994,21 @@ sub patch_update_file {
}
}
@hunk = coalesce_overlapping_hunks(@hunk);
my $n_lofs = 0;
my @result = ();
if ($mode->{USE}) {
push @result, @{$mode->{TEXT}};
}
for (@hunk) {
my $text = $_->{TEXT};
my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
parse_hunk_header($text->[0]);
if (!$_->{USE}) {
# We would have added ($n_cnt - $o_cnt) lines
# to the postimage if we were to use this hunk,
# but we didn't. So the line number that the next
# hunk starts at would be shifted by that much.
$n_lofs -= ($n_cnt - $o_cnt);
next;
}
else {
if ($n_lofs) {
$n_ofs += $n_lofs;
$text->[0] = ("@@ -$o_ofs" .
(($o_cnt != 1)
? ",$o_cnt" : '') .
" +$n_ofs" .
(($n_cnt != 1)
? ",$n_cnt" : '') .
" @@\n");
}
for (@$text) {
push @result, $_;
}
if ($_->{USE}) {
push @result, @{$_->{TEXT}};
}
}
if (@result) {
my $fh;
open $fh, '| git apply --cached';
open $fh, '| git apply --cached --recount';
for (@{$head->{TEXT}}, @result) {
print $fh $_;
}

View File

@@ -5,7 +5,7 @@
SUBDIRECTORY_OK=Yes
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
git-am [options] <mbox>|<Maildir>...
git-am [options] [<mbox>|<Maildir>...]
git-am [options] --resolved
git-am [options] --skip
--
@@ -30,7 +30,8 @@ set_reflog_action am
require_work_tree
cd_to_toplevel
git var GIT_COMMITTER_IDENT >/dev/null || exit
git var GIT_COMMITTER_IDENT >/dev/null ||
die "You need to set your committer info first"
stop_here () {
echo "$1" >"$dotest/next"
@@ -118,7 +119,7 @@ It does not apply to blobs recorded in its index."
}
prec=4
dotest=".dotest"
dotest="$GIT_DIR/rebase"
sign= utf8=t keep= skip= interactive= resolved= binary= rebasing=
resolvemsg= resume=
git_apply_opt=
@@ -194,7 +195,7 @@ then
false
;;
esac ||
die "previous dotest directory $dotest still exists but mbox given."
die "previous rebase directory $dotest still exists but mbox given."
resume=yes
else
# Make sure we are not given --skip nor --resolved
@@ -241,6 +242,7 @@ else
: >"$dotest/rebasing"
else
: >"$dotest/applying"
git update-ref ORIG_HEAD HEAD
fi
fi
@@ -323,7 +325,7 @@ do
<"$dotest"/info >/dev/null &&
go_next && continue
test -s $dotest/patch || {
test -s "$dotest/patch" || {
echo "Patch is empty. Was it split wrong?"
stop_here $this
}

View File

@@ -157,6 +157,12 @@ extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
extern int prefixcmp(const char *str, const char *prefix);
extern time_t tm_to_time_t(const struct tm *tm);
static inline const char *skip_prefix(const char *str, const char *prefix)
{
size_t len = strlen(prefix);
return strncmp(str, prefix, len) ? NULL : str + len;
}
#ifdef NO_MMAP
#ifndef PROT_READ

View File

@@ -1995,9 +1995,13 @@ if {[is_enabled multicommit]} {
}
}
.mbar.repository add command -label [mc Quit] \
-command do_quit \
-accelerator $M1T-Q
if {[is_MacOSX]} {
proc ::tk::mac::Quit {args} { do_quit }
} else {
.mbar.repository add command -label [mc Quit] \
-command do_quit \
-accelerator $M1T-Q
}
# -- Edit Menu
#

View File

@@ -257,6 +257,7 @@ proc _reset_wait {fd} {
catch {file delete [gitdir MERGE_HEAD]}
catch {file delete [gitdir rr-cache MERGE_RR]}
catch {file delete [gitdir MERGE_RR]}
catch {file delete [gitdir SQUASH_MSG]}
catch {file delete [gitdir MERGE_MSG]}
catch {file delete [gitdir GITGUI_MSG]}

View File

@@ -53,7 +53,7 @@ if ! [ -d "$QUILT_PATCHES" ] ; then
fi
# Temporary directories
tmp_dir=.dotest
tmp_dir="$GIT_DIR"/rebase
tmp_msg="$tmp_dir/msg"
tmp_patch="$tmp_dir/patch"
tmp_info="$tmp_dir/info"

View File

@@ -10,14 +10,28 @@
# The original idea comes from Eric W. Biederman, in
# http://article.gmane.org/gmane.comp.version-control.git/22407
USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose]
[--onto <branch>] <upstream> [<branch>])'
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
git-rebase [-i] [options] [--] <upstream> [<branch>]
git-rebase [-i] (--continue | --abort | --skip)
--
Available options are
v,verbose display a diffstat of what changed upstream
onto= rebase onto given branch instead of upstream
p,preserve-merges try to recreate merges instead of ignoring them
s,strategy= use the given merge strategy
m,merge always used (no-op)
i,interactive always used (no-op)
Actions:
continue continue rebasing process
abort abort rebasing process and restore original branch
skip skip current patch and continue rebasing process
"
OPTIONS_SPEC=
. git-sh-setup
require_work_tree
DOTEST="$GIT_DIR/.dotest-merge"
DOTEST="$GIT_DIR/rebase-merge"
TODO="$DOTEST"/git-rebase-todo
DONE="$DOTEST"/done
MSG="$DOTEST"/message
@@ -25,10 +39,8 @@ SQUASH_MSG="$DOTEST"/message-squash
REWRITTEN="$DOTEST"/rewritten
PRESERVE_MERGES=
STRATEGY=
ONTO=
VERBOSE=
test -d "$REWRITTEN" && PRESERVE_MERGES=t
test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
test -f "$DOTEST"/verbose && VERBOSE=t
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
@@ -366,10 +378,27 @@ do_rest () {
done
}
# check if no other options are set
is_standalone () {
test $# -eq 2 -a "$2" = '--' &&
test -z "$ONTO" &&
test -z "$PRESERVE_MERGES" &&
test -z "$STRATEGY" &&
test -z "$VERBOSE"
}
get_saved_options () {
test -d "$REWRITTEN" && PRESERVE_MERGES=t
test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
test -f "$DOTEST"/verbose && VERBOSE=t
}
while test $# != 0
do
case "$1" in
--continue)
is_standalone "$@" || usage
get_saved_options
comment_for_reflog continue
test -d "$DOTEST" || die "No interactive rebase running"
@@ -402,6 +431,8 @@ do
do_rest
;;
--abort)
is_standalone "$@" || usage
get_saved_options
comment_for_reflog abort
git rerere clear
@@ -419,6 +450,8 @@ do
exit
;;
--skip)
is_standalone "$@" || usage
get_saved_options
comment_for_reflog skip
git rerere clear
@@ -426,7 +459,7 @@ do
output git reset --hard && do_rest
;;
-s|--strategy)
-s)
case "$#,$1" in
*,*=*)
STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
@@ -437,25 +470,26 @@ do
shift ;;
esac
;;
-m|--merge)
-m)
# we use merge anyway
;;
-C*)
die "Interactive rebase uses merge, so $1 does not make sense"
;;
-v|--verbose)
-v)
VERBOSE=t
;;
-p|--preserve-merges)
-p)
PRESERVE_MERGES=t
;;
-i|--interactive)
-i)
# yeah, we know
;;
''|-h)
usage
--onto)
shift
ONTO=$(git rev-parse --verify "$1") ||
die "Does not point to a valid commit: $1"
;;
*)
--)
shift
test $# -eq 1 -o $# -eq 2 || usage
test -d "$DOTEST" &&
die "Interactive rebase already started"
@@ -464,15 +498,6 @@ do
comment_for_reflog start
ONTO=
case "$1" in
--onto)
ONTO=$(git rev-parse --verify "$2") ||
die "Does not point to a valid commit: $2"
shift; shift
;;
esac
require_clean_work_tree
UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
@@ -549,6 +574,7 @@ EOF
has_action "$TODO" ||
die_abort "Nothing to do"
git update-ref ORIG_HEAD $HEAD
output git checkout $ONTO && do_rest
;;
esac

View File

@@ -14,7 +14,7 @@ It is possible that a merge failure will prevent this process from being
completely automatic. You will have to resolve any such merge failure
and run git rebase --continue. Another option is to bypass the commit
that caused the merge failure with git rebase --skip. To restore the
original <branch> and remove the .dotest working files, use the command
original <branch> and remove the .git/rebase working files, use the command
git rebase --abort instead.
Note that if <branch> is not specified on the command line, the
@@ -42,7 +42,7 @@ To restore the original branch and stop rebasing run \"git rebase --abort\".
unset newbase
strategy=recursive
do_merge=
dotest=$GIT_DIR/.dotest-merge
dotest="$GIT_DIR"/rebase-merge
prec=4
verbose=
git_am_opt=
@@ -150,7 +150,7 @@ while test $# != 0
do
case "$1" in
--continue)
test -d "$dotest" -o -d .dotest ||
test -d "$dotest" -o -d "$GIT_DIR"/rebase ||
die "No rebase in progress?"
git diff-files --quiet --ignore-submodules || {
@@ -173,15 +173,15 @@ do
finish_rb_merge
exit
fi
head_name=$(cat .dotest/head-name) &&
onto=$(cat .dotest/onto) &&
orig_head=$(cat .dotest/orig-head) &&
head_name=$(cat "$GIT_DIR"/rebase/head-name) &&
onto=$(cat "$GIT_DIR"/rebase/onto) &&
orig_head=$(cat "$GIT_DIR"/rebase/orig-head) &&
git am --resolved --3way --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
exit
;;
--skip)
test -d "$dotest" -o -d .dotest ||
test -d "$dotest" -o -d "$GIT_DIR"/rebase ||
die "No rebase in progress?"
git reset --hard HEAD || exit $?
@@ -201,15 +201,15 @@ do
finish_rb_merge
exit
fi
head_name=$(cat .dotest/head-name) &&
onto=$(cat .dotest/onto) &&
orig_head=$(cat .dotest/orig-head) &&
head_name=$(cat "$GIT_DIR"/rebase/head-name) &&
onto=$(cat "$GIT_DIR"/rebase/onto) &&
orig_head=$(cat "$GIT_DIR"/rebase/orig-head) &&
git am -3 --skip --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
exit
;;
--abort)
test -d "$dotest" -o -d .dotest ||
test -d "$dotest" -o -d "$GIT_DIR"/rebase ||
die "No rebase in progress?"
git rerere clear
@@ -217,7 +217,7 @@ do
then
move_to_original_branch
else
dotest=.dotest
dotest="$GIT_DIR"/rebase
move_to_original_branch
fi
git reset --hard $(cat "$dotest/orig-head")
@@ -265,24 +265,24 @@ do
shift
done
# Make sure we do not have .dotest
# Make sure we do not have $GIT_DIR/rebase
if test -z "$do_merge"
then
if mkdir .dotest
if mkdir "$GIT_DIR"/rebase
then
rmdir .dotest
rmdir "$GIT_DIR"/rebase
else
echo >&2 '
It seems that I cannot create a .dotest directory, and I wonder if you
It seems that I cannot create a '"$GIT_DIR"'/rebase directory, and I wonder if you
are in the middle of patch application or another rebase. If that is not
the case, please rm -fr .dotest and run me again. I am stopping in case
the case, please rm -fr '"$GIT_DIR"'/rebase and run me again. I am stopping in case
you still have something valuable there.'
exit 1
fi
else
if test -d "$dotest"
then
die "previous dotest directory $dotest still exists." \
die "previous rebase directory $dotest still exists." \
'try git-rebase < --continue | --abort >'
fi
fi
@@ -378,7 +378,7 @@ fi
echo "First, rewinding head to replay your work on top of it..."
git checkout "$onto^0" >/dev/null 2>&1 ||
die "could not detach HEAD"
# git reset --hard "$onto^0"
git update-ref ORIG_HEAD $branch
# If the $onto is a proper descendant of the tip of the branch, then
# we just fast forwarded.
@@ -396,10 +396,10 @@ then
git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
ret=$?
test 0 != $ret -a -d .dotest &&
echo $head_name > .dotest/head-name &&
echo $onto > .dotest/onto &&
echo $orig_head > .dotest/orig-head
test 0 != $ret -a -d "$GIT_DIR"/rebase &&
echo $head_name > "$GIT_DIR"/rebase/head-name &&
echo $onto > "$GIT_DIR"/rebase/onto &&
echo $orig_head > "$GIT_DIR"/rebase/orig-head
exit $ret
fi

View File

@@ -26,7 +26,7 @@ merge_base=`git merge-base $baserev $headrev` ||
die "fatal: No commits in common between $base and $head"
url=$(get_remote_url "$url")
branch=$(git peek-remote "$url" \
branch=$(git ls-remote "$url" \
| sed -n -e "/^$headrev refs.heads./{
s/^.* refs.heads.//
p

View File

@@ -86,6 +86,13 @@ create_stash () {
}
save_stash () {
keep_index=
case "$1" in
--keep-index)
keep_index=t
shift
esac
stash_msg="$1"
if no_changes
@@ -104,6 +111,13 @@ save_stash () {
git update-ref -m "$stash_msg" $ref_stash $w_commit ||
die "Cannot save the current status"
printf 'Saved working directory and index state "%s"\n' "$stash_msg"
git reset --hard
if test -n "$keep_index" && test -n $i_tree
then
git read-tree --reset -u $i_tree
fi
}
have_stash () {
@@ -153,7 +167,8 @@ apply_stash () {
die "$*: no valid stashed state found"
unstashed_index_tree=
if test -n "$unstash_index" && test "$b_tree" != "$i_tree"
if test -n "$unstash_index" && test "$b_tree" != "$i_tree" &&
test "$c_tree" != "$i_tree"
then
git diff-tree --binary $s^2^..$s^2 | git apply --cached
test $? -ne 0 &&
@@ -218,6 +233,23 @@ drop_stash () {
git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
}
apply_to_branch () {
have_stash || die 'Nothing to apply'
test -n "$1" || die 'No branch name specified'
branch=$1
if test -z "$2"
then
set x "$ref_stash@{0}"
fi
stash=$2
git-checkout -b $branch $stash^ &&
apply_stash --index $stash &&
drop_stash $stash
}
# Main command set
case "$1" in
list)
@@ -235,7 +267,7 @@ show)
;;
save)
shift
save_stash "$*" && git-reset --hard
save_stash "$*"
;;
apply)
shift
@@ -264,12 +296,15 @@ pop)
drop_stash "$@"
fi
;;
branch)
shift
apply_to_branch "$@"
;;
*)
if test $# -eq 0
then
save_stash &&
echo '(To restore them type "git stash apply")' &&
git-reset --hard
echo '(To restore them type "git stash apply")'
else
usage
fi

View File

@@ -5,7 +5,7 @@
# Copyright (c) 2007 Lars Hjemli
USAGE="[--quiet] [--cached] \
[add <repo> [-b branch]|status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
[--] [<path>...]"
OPTIONS_SPEC=
. git-sh-setup
@@ -27,18 +27,6 @@ say()
fi
}
# NEEDSWORK: identical function exists in get_repo_base in clone.sh
get_repo_base() {
(
cd "`/bin/pwd`" &&
cd "$1" || cd "$1.git" &&
{
cd .git
pwd
}
) 2>/dev/null
}
# Resolve relative url by appending to parent's url
resolve_relative_url ()
{
@@ -115,7 +103,7 @@ module_clone()
#
# Add a new submodule to the working tree, .gitmodules and the index
#
# $@ = repo [path]
# $@ = repo path
#
# optional branch is stored in global branch variable
#
@@ -150,16 +138,27 @@ cmd_add()
repo=$1
path=$2
if test -z "$repo"; then
if test -z "$repo" -o -z "$path"; then
usage
fi
# Guess path from repo if not specified or strip trailing slashes
if test -z "$path"; then
path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
else
path=$(echo "$path" | sed -e 's|/*$||')
fi
# assure repo is absolute or relative to parent
case "$repo" in
./*|../*)
# dereference source url relative to parent's url
realrepo=$(resolve_relative_url "$repo") || exit
;;
*:*|/*)
# absolute url
realrepo=$repo
;;
*)
die "repo URL: '$repo' must be absolute or begin with ./|../"
;;
esac
# strip trailing slashes from path
path=$(echo "$path" | sed -e 's|/*$||')
git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
die "'$path' already exists in the index"
@@ -173,21 +172,17 @@ cmd_add()
else
die "'$path' already exists and is not a valid git repo"
fi
else
case "$repo" in
./*|../*)
# dereference source url relative to parent's url
realrepo=$(resolve_relative_url "$repo") || exit
;;
url=$(resolve_relative_url "$repo") || exit
;;
*)
# Turn the source into an absolute path if
# it is local
if base=$(get_repo_base "$repo"); then
repo="$base"
fi
realrepo=$repo
url="$repo"
;;
esac
git config submodule."$path".url "$url"
else
module_clone "$path" "$realrepo" || exit
(unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||

View File

@@ -537,13 +537,13 @@ sub cmd_find_rev {
my $head = shift;
$head ||= 'HEAD';
my @refs;
my (undef, undef, undef, $gs) = working_head_info($head, \@refs);
my (undef, undef, $uuid, $gs) = working_head_info($head, \@refs);
unless ($gs) {
die "Unable to determine upstream SVN information from ",
"$head history\n";
}
my $desired_revision = substr($revision_or_hash, 1);
$result = $gs->rev_map_get($desired_revision);
$result = $gs->rev_map_get($desired_revision, $uuid);
} else {
my (undef, $rev, undef) = cmt_metadata($revision_or_hash);
$result = $rev;
@@ -1162,7 +1162,7 @@ sub working_head_info {
if (defined $url && defined $rev) {
next if $max{$url} and $max{$url} < $rev;
if (my $gs = Git::SVN->find_by_url($url)) {
my $c = $gs->rev_map_get($rev);
my $c = $gs->rev_map_get($rev, $uuid);
if ($c && $c eq $hash) {
close $fh; # break the pipe
return ($url, $rev, $uuid, $gs);
@@ -1416,11 +1416,17 @@ sub fetch_all {
sub read_all_remotes {
my $r = {};
my $use_svm_props = eval { command_oneline(qw/config --bool
svn.useSvmProps/) };
$use_svm_props = $use_svm_props eq 'true' if $use_svm_props;
foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) {
my ($remote, $local_ref, $remote_ref) = ($1, $2, $3);
$local_ref =~ s{^/}{};
$r->{$remote}->{fetch}->{$local_ref} = $remote_ref;
$r->{$remote}->{svm} = {} if $use_svm_props;
} elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) {
$r->{$1}->{svm} = {};
} elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
$r->{$1}->{url} = $2;
} elsif (m!^(.+)\.(branches|tags)=
@@ -1437,6 +1443,23 @@ sub read_all_remotes {
}
}
}
map {
if (defined $r->{$_}->{svm}) {
my $svm;
eval {
my $section = "svn-remote.$_";
$svm = {
source => tmp_config('--get',
"$section.svm-source"),
replace => tmp_config('--get',
"$section.svm-replace"),
}
};
$r->{$_}->{svm} = $svm;
}
} keys %$r;
$r;
}
@@ -1563,13 +1586,21 @@ sub find_by_url { # repos_root and, path are optional
}
my $p = $path;
my $rwr = rewrite_root({repo_id => $repo_id});
my $svm = $remotes->{$repo_id}->{svm}
if defined $remotes->{$repo_id}->{svm};
unless (defined $p) {
$p = $full_url;
my $z = $u;
my $prefix = '';
if ($rwr) {
$z = $rwr;
} elsif (defined $svm) {
$z = $svm->{source};
$prefix = $svm->{replace};
$prefix =~ s#^\Q$u\E(?:/|$)##;
$prefix =~ s#/$##;
}
$p =~ s#^\Q$z\E(?:/|$)## or next;
$p =~ s#^\Q$z\E(?:/|$)#$prefix# or next;
}
foreach my $f (keys %$fetch) {
next if $f ne $p;
@@ -4619,7 +4650,7 @@ sub migrate_from_v1 {
mkpath([$svn_dir]);
print STDERR "Data from a previous version of git-svn exists, but\n\t",
"$svn_dir\n\t(required for this version ",
"($::VERSION) of git-svn) does not. exist\n";
"($::VERSION) of git-svn) does not exist.\n";
my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
while (<$fh>) {
my $x = $_;

54
git.c
View File

@@ -127,59 +127,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
return handled;
}
static int split_cmdline(char *cmdline, const char ***argv)
{
int src, dst, count = 0, size = 16;
char quoted = 0;
*argv = xmalloc(sizeof(char*) * size);
/* split alias_string */
(*argv)[count++] = cmdline;
for (src = dst = 0; cmdline[src];) {
char c = cmdline[src];
if (!quoted && isspace(c)) {
cmdline[dst++] = 0;
while (cmdline[++src]
&& isspace(cmdline[src]))
; /* skip */
if (count >= size) {
size += 16;
*argv = xrealloc(*argv, sizeof(char*) * size);
}
(*argv)[count++] = cmdline + dst;
} else if(!quoted && (c == '\'' || c == '"')) {
quoted = c;
src++;
} else if (c == quoted) {
quoted = 0;
src++;
} else {
if (c == '\\' && quoted != '\'') {
src++;
c = cmdline[src];
if (!c) {
free(*argv);
*argv = NULL;
return error("cmdline ends with \\");
}
}
cmdline[dst++] = c;
src++;
}
}
cmdline[dst] = 0;
if (quoted) {
free(*argv);
*argv = NULL;
return error("unclosed quote");
}
return count;
}
static int handle_alias(int *argcp, const char ***argv)
{
int envchanged = 0, ret = 0, saved_errno = errno;
@@ -366,6 +313,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "ls-remote", cmd_ls_remote },
{ "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit },
{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
{ "merge-ours", cmd_merge_ours, RUN_SETUP },

View File

@@ -1,33 +1,10 @@
#include "git-compat-util.h"
#include "parse-options.h"
#include "cache.h"
#define OPT_SHORT 1
#define OPT_UNSET 2
struct optparse_t {
const char **argv;
const char **out;
int argc, cpidx;
const char *opt;
};
static inline const char *get_arg(struct optparse_t *p)
{
if (p->opt) {
const char *res = p->opt;
p->opt = NULL;
return res;
}
p->argc--;
return *++p->argv;
}
static inline const char *skip_prefix(const char *str, const char *prefix)
{
size_t len = strlen(prefix);
return strncmp(str, prefix, len) ? NULL : str + len;
}
static int opterror(const struct option *opt, const char *reason, int flags)
{
if (flags & OPT_SHORT)
@@ -37,8 +14,24 @@ static int opterror(const struct option *opt, const char *reason, int flags)
return error("option `%s' %s", opt->long_name, reason);
}
static int get_value(struct optparse_t *p,
const struct option *opt, int flags)
static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
int flags, const char **arg)
{
if (p->opt) {
*arg = p->opt;
p->opt = NULL;
} else if (p->argc == 1 && (opt->flags & PARSE_OPT_LASTARG_DEFAULT)) {
*arg = (const char *)opt->defval;
} else if (p->argc) {
p->argc--;
*arg = *++p->argv;
} else
return opterror(opt, "requires a value", flags);
return 0;
}
static int get_value(struct parse_opt_ctx_t *p,
const struct option *opt, int flags)
{
const char *s, *arg;
const int unset = flags & OPT_UNSET;
@@ -64,7 +57,6 @@ static int get_value(struct optparse_t *p,
}
}
arg = p->opt ? p->opt : (p->argc > 1 ? p->argv[1] : NULL);
switch (opt->type) {
case OPTION_BIT:
if (unset)
@@ -86,29 +78,24 @@ static int get_value(struct optparse_t *p,
return 0;
case OPTION_STRING:
if (unset) {
if (unset)
*(const char **)opt->value = NULL;
return 0;
}
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
*(const char **)opt->value = (const char *)opt->defval;
return 0;
}
if (!arg)
return opterror(opt, "requires a value", flags);
*(const char **)opt->value = get_arg(p);
else
return get_arg(p, opt, flags, (const char **)opt->value);
return 0;
case OPTION_CALLBACK:
if (unset)
return (*opt->callback)(opt, NULL, 1);
return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
if (opt->flags & PARSE_OPT_NOARG)
return (*opt->callback)(opt, NULL, 0);
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
return (*opt->callback)(opt, NULL, 0);
if (!arg)
return opterror(opt, "requires a value", flags);
return (*opt->callback)(opt, get_arg(p), 0);
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
if (get_arg(p, opt, flags, &arg))
return -1;
return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
case OPTION_INTEGER:
if (unset) {
@@ -119,9 +106,9 @@ static int get_value(struct optparse_t *p,
*(int *)opt->value = opt->defval;
return 0;
}
if (!arg)
return opterror(opt, "requires a value", flags);
*(int *)opt->value = strtol(get_arg(p), (char **)&s, 10);
if (get_arg(p, opt, flags, &arg))
return -1;
*(int *)opt->value = strtol(arg, (char **)&s, 10);
if (*s)
return opterror(opt, "expects a numerical value", flags);
return 0;
@@ -131,7 +118,7 @@ static int get_value(struct optparse_t *p,
}
}
static int parse_short_opt(struct optparse_t *p, const struct option *options)
static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
{
for (; options->type != OPTION_END; options++) {
if (options->short_name == *p->opt) {
@@ -139,10 +126,10 @@ static int parse_short_opt(struct optparse_t *p, const struct option *options)
return get_value(p, options, OPT_SHORT);
}
}
return error("unknown switch `%c'", *p->opt);
return -2;
}
static int parse_long_opt(struct optparse_t *p, const char *arg,
static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
const struct option *options)
{
const char *arg_end = strchr(arg, '=');
@@ -224,7 +211,7 @@ is_abbreviated:
abbrev_option->long_name);
if (abbrev_option)
return get_value(p, abbrev_option, abbrev_flags);
return error("unknown option `%s'", arg);
return -2;
}
void check_typos(const char *arg, const struct option *options)
@@ -247,67 +234,126 @@ void check_typos(const char *arg, const struct option *options)
}
}
static NORETURN void usage_with_options_internal(const char * const *,
const struct option *, int);
int parse_options(int argc, const char **argv, const struct option *options,
const char * const usagestr[], int flags)
void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, int flags)
{
struct optparse_t args = { argv + 1, argv, argc - 1, 0, NULL };
memset(ctx, 0, sizeof(*ctx));
ctx->argc = argc - 1;
ctx->argv = argv + 1;
ctx->out = argv;
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
ctx->flags = flags;
}
for (; args.argc; args.argc--, args.argv++) {
const char *arg = args.argv[0];
static int usage_with_options_internal(const char * const *,
const struct option *, int);
int parse_options_step(struct parse_opt_ctx_t *ctx,
const struct option *options,
const char * const usagestr[])
{
/* we must reset ->opt, unknown short option leave it dangling */
ctx->opt = NULL;
for (; ctx->argc; ctx->argc--, ctx->argv++) {
const char *arg = ctx->argv[0];
if (*arg != '-' || !arg[1]) {
if (flags & PARSE_OPT_STOP_AT_NON_OPTION)
if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
break;
args.out[args.cpidx++] = args.argv[0];
ctx->out[ctx->cpidx++] = ctx->argv[0];
continue;
}
if (arg[1] != '-') {
args.opt = arg + 1;
if (*args.opt == 'h')
usage_with_options(usagestr, options);
if (parse_short_opt(&args, options) < 0)
usage_with_options(usagestr, options);
if (args.opt)
ctx->opt = arg + 1;
if (*ctx->opt == 'h')
return parse_options_usage(usagestr, options);
switch (parse_short_opt(ctx, options)) {
case -1:
return parse_options_usage(usagestr, options);
case -2:
return PARSE_OPT_UNKNOWN;
}
if (ctx->opt)
check_typos(arg + 1, options);
while (args.opt) {
if (*args.opt == 'h')
usage_with_options(usagestr, options);
if (parse_short_opt(&args, options) < 0)
usage_with_options(usagestr, options);
while (ctx->opt) {
if (*ctx->opt == 'h')
return parse_options_usage(usagestr, options);
switch (parse_short_opt(ctx, options)) {
case -1:
return parse_options_usage(usagestr, options);
case -2:
/* fake a short option thing to hide the fact that we may have
* started to parse aggregated stuff
*
* This is leaky, too bad.
*/
ctx->argv[0] = xstrdup(ctx->opt - 1);
*(char *)ctx->argv[0] = '-';
return PARSE_OPT_UNKNOWN;
}
}
continue;
}
if (!arg[2]) { /* "--" */
if (!(flags & PARSE_OPT_KEEP_DASHDASH)) {
args.argc--;
args.argv++;
if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
ctx->argc--;
ctx->argv++;
}
break;
}
if (!strcmp(arg + 2, "help-all"))
usage_with_options_internal(usagestr, options, 1);
return usage_with_options_internal(usagestr, options, 1);
if (!strcmp(arg + 2, "help"))
usage_with_options(usagestr, options);
if (parse_long_opt(&args, arg + 2, options))
usage_with_options(usagestr, options);
return parse_options_usage(usagestr, options);
switch (parse_long_opt(ctx, arg + 2, options)) {
case -1:
return parse_options_usage(usagestr, options);
case -2:
return PARSE_OPT_UNKNOWN;
}
}
return PARSE_OPT_DONE;
}
int parse_options_end(struct parse_opt_ctx_t *ctx)
{
memmove(ctx->out + ctx->cpidx, ctx->argv, ctx->argc * sizeof(*ctx->out));
ctx->out[ctx->cpidx + ctx->argc] = NULL;
return ctx->cpidx + ctx->argc;
}
int parse_options(int argc, const char **argv, const struct option *options,
const char * const usagestr[], int flags)
{
struct parse_opt_ctx_t ctx;
parse_options_start(&ctx, argc, argv, flags);
switch (parse_options_step(&ctx, options, usagestr)) {
case PARSE_OPT_HELP:
exit(129);
case PARSE_OPT_DONE:
break;
default: /* PARSE_OPT_UNKNOWN */
if (ctx.argv[0][1] == '-') {
error("unknown option `%s'", ctx.argv[0] + 2);
} else {
error("unknown switch `%c'", *ctx.opt);
}
usage_with_options(usagestr, options);
}
memmove(args.out + args.cpidx, args.argv, args.argc * sizeof(*args.out));
args.out[args.cpidx + args.argc] = NULL;
return args.cpidx + args.argc;
return parse_options_end(&ctx);
}
#define USAGE_OPTS_WIDTH 24
#define USAGE_GAP 2
void usage_with_options_internal(const char * const *usagestr,
const struct option *opts, int full)
int usage_with_options_internal(const char * const *usagestr,
const struct option *opts, int full)
{
fprintf(stderr, "usage: %s\n", *usagestr++);
while (*usagestr && **usagestr)
@@ -392,15 +438,23 @@ void usage_with_options_internal(const char * const *usagestr,
}
fputc('\n', stderr);
exit(129);
return PARSE_OPT_HELP;
}
void usage_with_options(const char * const *usagestr,
const struct option *opts)
const struct option *opts)
{
usage_with_options_internal(usagestr, opts, 0);
exit(129);
}
int parse_options_usage(const char * const *usagestr,
const struct option *opts)
{
return usage_with_options_internal(usagestr, opts, 0);
}
/*----- some often used options -----*/
#include "cache.h"

View File

@@ -20,6 +20,7 @@ enum parse_opt_type {
enum parse_opt_flags {
PARSE_OPT_KEEP_DASHDASH = 1,
PARSE_OPT_STOP_AT_NON_OPTION = 2,
PARSE_OPT_KEEP_ARGV0 = 4,
};
enum parse_opt_option_flags {
@@ -27,6 +28,7 @@ enum parse_opt_option_flags {
PARSE_OPT_NOARG = 2,
PARSE_OPT_NONEG = 4,
PARSE_OPT_HIDDEN = 8,
PARSE_OPT_LASTARG_DEFAULT = 16,
};
struct option;
@@ -111,6 +113,40 @@ extern int parse_options(int argc, const char **argv,
extern NORETURN void usage_with_options(const char * const *usagestr,
const struct option *options);
/*----- incremantal advanced APIs -----*/
enum {
PARSE_OPT_HELP = -1,
PARSE_OPT_DONE,
PARSE_OPT_UNKNOWN,
};
/*
* It's okay for the caller to consume argv/argc in the usual way.
* Other fields of that structure are private to parse-options and should not
* be modified in any way.
*/
struct parse_opt_ctx_t {
const char **argv;
const char **out;
int argc, cpidx;
const char *opt;
int flags;
};
extern int parse_options_usage(const char * const *usagestr,
const struct option *opts);
extern void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, int flags);
extern int parse_options_step(struct parse_opt_ctx_t *ctx,
const struct option *options,
const char * const usagestr[]);
extern int parse_options_end(struct parse_opt_ctx_t *ctx);
/*----- some often used options -----*/
extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
extern int parse_opt_approxidate_cb(const struct option *, const char *, int);

2
path.c
View File

@@ -272,7 +272,7 @@ int adjust_shared_perm(const char *path)
int tweak = shared_repository;
if (!(mode & S_IWUSR))
tweak &= ~0222;
mode = (mode & ~0777) | tweak;
mode |= tweak;
} else {
/* Preserve old PERM_UMASK behaviour */
if (mode & S_IWUSR)

View File

@@ -3,6 +3,8 @@
#include "utf8.h"
#include "diff.h"
#include "revision.h"
#include "path-list.h"
#include "mailmap.h"
static char *user_format;
@@ -288,6 +290,25 @@ static char *logmsg_reencode(const struct commit *commit,
return out;
}
static int mailmap_name(struct strbuf *sb, const char *email)
{
static struct path_list *mail_map;
char buffer[1024];
if (!mail_map) {
mail_map = xcalloc(1, sizeof(*mail_map));
read_mailmap(mail_map, ".mailmap", NULL);
}
if (!mail_map->nr)
return -1;
if (!map_email(mail_map, email, buffer, sizeof(buffer)))
return -1;
strbuf_addstr(sb, buffer);
return 0;
}
static size_t format_person_part(struct strbuf *sb, char part,
const char *msg, int len)
{
@@ -309,10 +330,12 @@ static size_t format_person_part(struct strbuf *sb, char part,
if (end >= len - 2)
goto skip;
if (part == 'n') { /* name */
if (part == 'n' || part == 'N') { /* name */
while (end > 0 && isspace(msg[end - 1]))
end--;
strbuf_add(sb, msg, end);
if (part != 'N' || !msg[end] || !msg[end + 1] ||
mailmap_name(sb, msg + end + 2) < 0)
strbuf_add(sb, msg, end);
return placeholder_len;
}
start = ++end; /* save email start position */

View File

@@ -1410,3 +1410,34 @@ int write_index(const struct index_state *istate, int newfd)
}
return ce_flush(&c, newfd);
}
/*
* Read the index file that is potentially unmerged into given
* index_state, dropping any unmerged entries. Returns true is
* the index is unmerged. Callers who want to refuse to work
* from an unmerged state can call this and check its return value,
* instead of calling read_cache().
*/
int read_index_unmerged(struct index_state *istate)
{
int i;
struct cache_entry **dst;
struct cache_entry *last = NULL;
read_index(istate);
dst = istate->cache;
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
if (ce_stage(ce)) {
remove_name_hash(ce);
if (last && !strcmp(ce->name, last->name))
continue;
cache_tree_invalidate_path(istate->cache_tree, ce->name);
last = ce;
continue;
}
*dst++ = ce;
}
istate->cache_nr = dst - istate->cache;
return !!last;
}

117
remote.c
View File

@@ -1,6 +1,9 @@
#include "cache.h"
#include "remote.h"
#include "refs.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
static struct refspec s_tag_refspec = {
0,
@@ -1222,3 +1225,117 @@ int resolve_remote_symref(struct ref *ref, struct ref *list)
}
return 1;
}
/*
* Return true if there is anything to report, otherwise false.
*/
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
{
unsigned char sha1[20];
struct commit *ours, *theirs;
char symmetric[84];
struct rev_info revs;
const char *rev_argv[10], *base;
int rev_argc;
/*
* Nothing to report unless we are marked to build on top of
* somebody else.
*/
if (!branch ||
!branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
return 0;
/*
* If what we used to build on no longer exists, there is
* nothing to report.
*/
base = branch->merge[0]->dst;
if (!resolve_ref(base, sha1, 1, NULL))
return 0;
theirs = lookup_commit(sha1);
if (!theirs)
return 0;
if (!resolve_ref(branch->refname, sha1, 1, NULL))
return 0;
ours = lookup_commit(sha1);
if (!ours)
return 0;
/* are we the same? */
if (theirs == ours)
return 0;
/* Run "rev-list --left-right ours...theirs" internally... */
rev_argc = 0;
rev_argv[rev_argc++] = NULL;
rev_argv[rev_argc++] = "--left-right";
rev_argv[rev_argc++] = symmetric;
rev_argv[rev_argc++] = "--";
rev_argv[rev_argc] = NULL;
strcpy(symmetric, sha1_to_hex(ours->object.sha1));
strcpy(symmetric + 40, "...");
strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1));
init_revisions(&revs, NULL);
setup_revisions(rev_argc, rev_argv, &revs, NULL);
prepare_revision_walk(&revs);
/* ... and count the commits on each side. */
*num_ours = 0;
*num_theirs = 0;
while (1) {
struct commit *c = get_revision(&revs);
if (!c)
break;
if (c->object.flags & SYMMETRIC_LEFT)
(*num_ours)++;
else
(*num_theirs)++;
}
/* clear object flags smudged by the above traversal */
clear_commit_marks(ours, ALL_REV_FLAGS);
clear_commit_marks(theirs, ALL_REV_FLAGS);
return 1;
}
/*
* Return true when there is anything to report, otherwise false.
*/
int format_tracking_info(struct branch *branch, struct strbuf *sb)
{
int num_ours, num_theirs;
const char *base, *remote_msg;
if (!stat_tracking_info(branch, &num_ours, &num_theirs))
return 0;
base = branch->merge[0]->dst;
if (!prefixcmp(base, "refs/remotes/")) {
remote_msg = " remote";
base += strlen("refs/remotes/");
} else {
remote_msg = "";
}
if (!num_theirs)
strbuf_addf(sb, "Your branch is ahead of the tracked%s branch '%s' "
"by %d commit%s.\n",
remote_msg, base,
num_ours, (num_ours == 1) ? "" : "s");
else if (!num_ours)
strbuf_addf(sb, "Your branch is behind the tracked%s branch '%s' "
"by %d commit%s,\n"
"and can be fast-forwarded.\n",
remote_msg, base,
num_theirs, (num_theirs == 1) ? "" : "s");
else
strbuf_addf(sb, "Your branch and the tracked%s branch '%s' "
"have diverged,\nand respectively "
"have %d and %d different commit(s) each.\n",
remote_msg, base,
num_ours, num_theirs);
return 1;
}

View File

@@ -129,4 +129,8 @@ enum match_refs_flags {
MATCH_REFS_MIRROR = (1 << 1),
};
/* Reporting of tracking info */
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs);
int format_tracking_info(struct branch *branch, struct strbuf *sb);
#endif

360
rerere.c Normal file
View File

@@ -0,0 +1,360 @@
#include "cache.h"
#include "path-list.h"
#include "rerere.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
static int rerere_enabled = -1;
/* automatically update cleanly resolved paths to the index */
static int rerere_autoupdate;
static char *merge_rr_path;
static const char *rr_path(const char *name, const char *file)
{
return git_path("rr-cache/%s/%s", name, file);
}
static int has_resolution(const char *name)
{
struct stat st;
return !stat(rr_path(name, "postimage"), &st);
}
static void read_rr(struct path_list *rr)
{
unsigned char sha1[20];
char buf[PATH_MAX];
FILE *in = fopen(merge_rr_path, "r");
if (!in)
return;
while (fread(buf, 40, 1, in) == 1) {
int i;
char *name;
if (get_sha1_hex(buf, sha1))
die("corrupt MERGE_RR");
buf[40] = '\0';
name = xstrdup(buf);
if (fgetc(in) != '\t')
die("corrupt MERGE_RR");
for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
; /* do nothing */
if (i == sizeof(buf))
die("filename too long");
path_list_insert(buf, rr)->util = name;
}
fclose(in);
}
static struct lock_file write_lock;
static int write_rr(struct path_list *rr, int out_fd)
{
int i;
for (i = 0; i < rr->nr; i++) {
const char *path;
int length;
if (!rr->items[i].util)
continue;
path = rr->items[i].path;
length = strlen(path) + 1;
if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
write_in_full(out_fd, "\t", 1) != 1 ||
write_in_full(out_fd, path, length) != length)
die("unable to write rerere record");
}
if (commit_lock_file(&write_lock) != 0)
die("unable to write rerere record");
return 0;
}
static int handle_file(const char *path,
unsigned char *sha1, const char *output)
{
SHA_CTX ctx;
char buf[1024];
int hunk = 0, hunk_no = 0;
struct strbuf one, two;
FILE *f = fopen(path, "r");
FILE *out = NULL;
if (!f)
return error("Could not open %s", path);
if (output) {
out = fopen(output, "w");
if (!out) {
fclose(f);
return error("Could not write %s", output);
}
}
if (sha1)
SHA1_Init(&ctx);
strbuf_init(&one, 0);
strbuf_init(&two, 0);
while (fgets(buf, sizeof(buf), f)) {
if (!prefixcmp(buf, "<<<<<<< ")) {
if (hunk)
goto bad;
hunk = 1;
} else if (!prefixcmp(buf, "=======") && isspace(buf[7])) {
if (hunk != 1)
goto bad;
hunk = 2;
} else if (!prefixcmp(buf, ">>>>>>> ")) {
if (hunk != 2)
goto bad;
if (strbuf_cmp(&one, &two) > 0)
strbuf_swap(&one, &two);
hunk_no++;
hunk = 0;
if (out) {
fputs("<<<<<<<\n", out);
fwrite(one.buf, one.len, 1, out);
fputs("=======\n", out);
fwrite(two.buf, two.len, 1, out);
fputs(">>>>>>>\n", out);
}
if (sha1) {
SHA1_Update(&ctx, one.buf ? one.buf : "",
one.len + 1);
SHA1_Update(&ctx, two.buf ? two.buf : "",
two.len + 1);
}
strbuf_reset(&one);
strbuf_reset(&two);
} else if (hunk == 1)
strbuf_addstr(&one, buf);
else if (hunk == 2)
strbuf_addstr(&two, buf);
else if (out)
fputs(buf, out);
continue;
bad:
hunk = 99; /* force error exit */
break;
}
strbuf_release(&one);
strbuf_release(&two);
fclose(f);
if (out)
fclose(out);
if (sha1)
SHA1_Final(sha1, &ctx);
if (hunk) {
if (output)
unlink(output);
return error("Could not parse conflict hunks in %s", path);
}
return hunk_no;
}
static int find_conflict(struct path_list *conflict)
{
int i;
if (read_cache() < 0)
return error("Could not read index");
for (i = 0; i+1 < active_nr; i++) {
struct cache_entry *e2 = active_cache[i];
struct cache_entry *e3 = active_cache[i+1];
if (ce_stage(e2) == 2 &&
ce_stage(e3) == 3 &&
ce_same_name(e2, e3) &&
S_ISREG(e2->ce_mode) &&
S_ISREG(e3->ce_mode)) {
path_list_insert((const char *)e2->name, conflict);
i++; /* skip over both #2 and #3 */
}
}
return 0;
}
static int merge(const char *name, const char *path)
{
int ret;
mmfile_t cur, base, other;
mmbuffer_t result = {NULL, 0};
xpparam_t xpp = {XDF_NEED_MINIMAL};
if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
return 1;
if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
read_mmfile(&base, rr_path(name, "preimage")) ||
read_mmfile(&other, rr_path(name, "postimage")))
return 1;
ret = xdl_merge(&base, &cur, "", &other, "",
&xpp, XDL_MERGE_ZEALOUS, &result);
if (!ret) {
FILE *f = fopen(path, "w");
if (!f)
return error("Could not write to %s", path);
fwrite(result.ptr, result.size, 1, f);
fclose(f);
}
free(cur.ptr);
free(base.ptr);
free(other.ptr);
free(result.ptr);
return ret;
}
static struct lock_file index_lock;
static int update_paths(struct path_list *update)
{
int i;
int fd = hold_locked_index(&index_lock, 0);
int status = 0;
if (fd < 0)
return -1;
for (i = 0; i < update->nr; i++) {
struct path_list_item *item = &update->items[i];
if (add_file_to_cache(item->path, ADD_CACHE_IGNORE_ERRORS))
status = -1;
}
if (!status && active_cache_changed) {
if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(&index_lock))
die("Unable to write new index file");
} else if (fd >= 0)
rollback_lock_file(&index_lock);
return status;
}
static int do_plain_rerere(struct path_list *rr, int fd)
{
struct path_list conflict = { NULL, 0, 0, 1 };
struct path_list update = { NULL, 0, 0, 1 };
int i;
find_conflict(&conflict);
/*
* MERGE_RR records paths with conflicts immediately after merge
* failed. Some of the conflicted paths might have been hand resolved
* in the working tree since then, but the initial run would catch all
* and register their preimages.
*/
for (i = 0; i < conflict.nr; i++) {
const char *path = conflict.items[i].path;
if (!path_list_has_path(rr, path)) {
unsigned char sha1[20];
char *hex;
int ret;
ret = handle_file(path, sha1, NULL);
if (ret < 1)
continue;
hex = xstrdup(sha1_to_hex(sha1));
path_list_insert(path, rr)->util = hex;
if (mkdir(git_path("rr-cache/%s", hex), 0755))
continue;;
handle_file(path, NULL, rr_path(hex, "preimage"));
fprintf(stderr, "Recorded preimage for '%s'\n", path);
}
}
/*
* Now some of the paths that had conflicts earlier might have been
* hand resolved. Others may be similar to a conflict already that
* was resolved before.
*/
for (i = 0; i < rr->nr; i++) {
int ret;
const char *path = rr->items[i].path;
const char *name = (const char *)rr->items[i].util;
if (has_resolution(name)) {
if (!merge(name, path)) {
fprintf(stderr, "Resolved '%s' using "
"previous resolution.\n", path);
if (rerere_autoupdate)
path_list_insert(path, &update);
goto mark_resolved;
}
}
/* Let's see if we have resolved it. */
ret = handle_file(path, NULL, NULL);
if (ret)
continue;
fprintf(stderr, "Recorded resolution for '%s'.\n", path);
copy_file(rr_path(name, "postimage"), path, 0666);
mark_resolved:
rr->items[i].util = NULL;
}
if (update.nr)
update_paths(&update);
return write_rr(rr, fd);
}
static int git_rerere_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "rerere.enabled"))
rerere_enabled = git_config_bool(var, value);
else if (!strcmp(var, "rerere.autoupdate"))
rerere_autoupdate = git_config_bool(var, value);
else
return git_default_config(var, value, cb);
return 0;
}
static int is_rerere_enabled(void)
{
struct stat st;
const char *rr_cache;
int rr_cache_exists;
if (!rerere_enabled)
return 0;
rr_cache = git_path("rr-cache");
rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode);
if (rerere_enabled < 0)
return rr_cache_exists;
if (!rr_cache_exists &&
(mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))
die("Could not create directory %s", rr_cache);
return 1;
}
int setup_rerere(struct path_list *merge_rr)
{
int fd;
git_config(git_rerere_config, NULL);
if (!is_rerere_enabled())
return -1;
merge_rr_path = xstrdup(git_path("MERGE_RR"));
fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
read_rr(merge_rr);
return fd;
}
int rerere(void)
{
struct path_list merge_rr = { NULL, 0, 0, 1 };
int fd;
fd = setup_rerere(&merge_rr);
if (fd < 0)
return 0;
return do_plain_rerere(&merge_rr, fd);
}

9
rerere.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef RERERE_H
#define RERERE_H
#include "path-list.h"
extern int setup_rerere(struct path_list *);
extern int rerere(void);
#endif

View File

@@ -10,6 +10,7 @@
#include "grep.h"
#include "reflog-walk.h"
#include "patch-ids.h"
#include "decorate.h"
volatile show_early_output_fn_t show_early_output;
@@ -412,10 +413,26 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
commit->object.flags |= TREESAME;
}
static int add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
static void insert_by_date_cached(struct commit *p, struct commit_list **head,
struct commit_list *cached_base, struct commit_list **cache)
{
struct commit_list *new_entry;
if (cached_base && p->date < cached_base->item->date)
new_entry = insert_by_date(p, &cached_base->next);
else
new_entry = insert_by_date(p, head);
if (cache && (!*cache || p->date < (*cache)->item->date))
*cache = new_entry;
}
static int add_parents_to_list(struct rev_info *revs, struct commit *commit,
struct commit_list **list, struct commit_list **cache_ptr)
{
struct commit_list *parent = commit->parents;
unsigned left_flag;
struct commit_list *cached_base = cache_ptr ? *cache_ptr : NULL;
if (commit->object.flags & ADDED)
return 0;
@@ -445,7 +462,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
if (p->object.flags & SEEN)
continue;
p->object.flags |= SEEN;
insert_by_date(p, list);
insert_by_date_cached(p, list, cached_base, cache_ptr);
}
return 0;
}
@@ -470,7 +487,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
p->object.flags |= left_flag;
if (!(p->object.flags & SEEN)) {
p->object.flags |= SEEN;
insert_by_date(p, list);
insert_by_date_cached(p, list, cached_base, cache_ptr);
}
if(revs->first_parent_only)
break;
@@ -611,7 +628,7 @@ static int limit_list(struct rev_info *revs)
if (revs->max_age != -1 && (commit->date < revs->max_age))
obj->flags |= UNINTERESTING;
if (add_parents_to_list(revs, commit, &list) < 0)
if (add_parents_to_list(revs, commit, &list, NULL) < 0)
return -1;
if (obj->flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
@@ -973,6 +990,226 @@ static void add_ignore_packed(struct rev_info *revs, const char *name)
revs->ignore_packed[num] = NULL;
}
static int handle_revision_opt(struct rev_info *revs, int argc, const char **argv,
int *unkc, const char **unkv)
{
const char *arg = argv[0];
/* pseudo revision arguments */
if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") ||
!strcmp(arg, "--tags") || !strcmp(arg, "--remotes") ||
!strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
!strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk"))
{
unkv[(*unkc)++] = arg;
return 0;
}
if (!prefixcmp(arg, "--max-count=")) {
revs->max_count = atoi(arg + 12);
} else if (!prefixcmp(arg, "--skip=")) {
revs->skip_count = atoi(arg + 7);
} else if ((*arg == '-') && isdigit(arg[1])) {
/* accept -<digit>, like traditional "head" */
revs->max_count = atoi(arg + 1);
} else if (!strcmp(arg, "-n")) {
if (argc <= 1)
return error("-n requires an argument");
revs->max_count = atoi(argv[1]);
return 2;
} else if (!prefixcmp(arg, "-n")) {
revs->max_count = atoi(arg + 2);
} else if (!prefixcmp(arg, "--max-age=")) {
revs->max_age = atoi(arg + 10);
} else if (!prefixcmp(arg, "--since=")) {
revs->max_age = approxidate(arg + 8);
} else if (!prefixcmp(arg, "--after=")) {
revs->max_age = approxidate(arg + 8);
} else if (!prefixcmp(arg, "--min-age=")) {
revs->min_age = atoi(arg + 10);
} else if (!prefixcmp(arg, "--before=")) {
revs->min_age = approxidate(arg + 9);
} else if (!prefixcmp(arg, "--until=")) {
revs->min_age = approxidate(arg + 8);
} else if (!strcmp(arg, "--first-parent")) {
revs->first_parent_only = 1;
} else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
init_reflog_walk(&revs->reflog_info);
} else if (!strcmp(arg, "--default")) {
if (argc <= 1)
return error("bad --default argument");
revs->def = argv[1];
return 2;
} else if (!strcmp(arg, "--merge")) {
revs->show_merge = 1;
} else if (!strcmp(arg, "--topo-order")) {
revs->lifo = 1;
revs->topo_order = 1;
} else if (!strcmp(arg, "--date-order")) {
revs->lifo = 0;
revs->topo_order = 1;
} else if (!prefixcmp(arg, "--early-output")) {
int count = 100;
switch (arg[14]) {
case '=':
count = atoi(arg+15);
/* Fallthrough */
case 0:
revs->topo_order = 1;
revs->early_output = count;
}
} else if (!strcmp(arg, "--parents")) {
revs->rewrite_parents = 1;
revs->print_parents = 1;
} else if (!strcmp(arg, "--dense")) {
revs->dense = 1;
} else if (!strcmp(arg, "--sparse")) {
revs->dense = 0;
} else if (!strcmp(arg, "--show-all")) {
revs->show_all = 1;
} else if (!strcmp(arg, "--remove-empty")) {
revs->remove_empty_trees = 1;
} else if (!strcmp(arg, "--no-merges")) {
revs->no_merges = 1;
} else if (!strcmp(arg, "--boundary")) {
revs->boundary = 1;
} else if (!strcmp(arg, "--left-right")) {
revs->left_right = 1;
} else if (!strcmp(arg, "--cherry-pick")) {
revs->cherry_pick = 1;
revs->limited = 1;
} else if (!strcmp(arg, "--objects")) {
revs->tag_objects = 1;
revs->tree_objects = 1;
revs->blob_objects = 1;
} else if (!strcmp(arg, "--objects-edge")) {
revs->tag_objects = 1;
revs->tree_objects = 1;
revs->blob_objects = 1;
revs->edge_hint = 1;
} else if (!strcmp(arg, "--unpacked")) {
revs->unpacked = 1;
free(revs->ignore_packed);
revs->ignore_packed = NULL;
revs->num_ignore_packed = 0;
} else if (!prefixcmp(arg, "--unpacked=")) {
revs->unpacked = 1;
add_ignore_packed(revs, arg+11);
} else if (!strcmp(arg, "-r")) {
revs->diff = 1;
DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
} else if (!strcmp(arg, "-t")) {
revs->diff = 1;
DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
DIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE);
} else if (!strcmp(arg, "-m")) {
revs->ignore_merges = 0;
} else if (!strcmp(arg, "-c")) {
revs->diff = 1;
revs->dense_combined_merges = 0;
revs->combine_merges = 1;
} else if (!strcmp(arg, "--cc")) {
revs->diff = 1;
revs->dense_combined_merges = 1;
revs->combine_merges = 1;
} else if (!strcmp(arg, "-v")) {
revs->verbose_header = 1;
} else if (!strcmp(arg, "--pretty")) {
revs->verbose_header = 1;
get_commit_format(arg+8, revs);
} else if (!prefixcmp(arg, "--pretty=")) {
revs->verbose_header = 1;
get_commit_format(arg+9, revs);
} else if (!strcmp(arg, "--graph")) {
revs->topo_order = 1;
revs->rewrite_parents = 1;
revs->graph = graph_init(revs);
} else if (!strcmp(arg, "--root")) {
revs->show_root_diff = 1;
} else if (!strcmp(arg, "--no-commit-id")) {
revs->no_commit_id = 1;
} else if (!strcmp(arg, "--always")) {
revs->always_show_header = 1;
} else if (!strcmp(arg, "--no-abbrev")) {
revs->abbrev = 0;
} else if (!strcmp(arg, "--abbrev")) {
revs->abbrev = DEFAULT_ABBREV;
} else if (!prefixcmp(arg, "--abbrev=")) {
revs->abbrev = strtoul(arg + 9, NULL, 10);
if (revs->abbrev < MINIMUM_ABBREV)
revs->abbrev = MINIMUM_ABBREV;
else if (revs->abbrev > 40)
revs->abbrev = 40;
} else if (!strcmp(arg, "--abbrev-commit")) {
revs->abbrev_commit = 1;
} else if (!strcmp(arg, "--full-diff")) {
revs->diff = 1;
revs->full_diff = 1;
} else if (!strcmp(arg, "--full-history")) {
revs->simplify_history = 0;
} else if (!strcmp(arg, "--relative-date")) {
revs->date_mode = DATE_RELATIVE;
} else if (!strncmp(arg, "--date=", 7)) {
revs->date_mode = parse_date_format(arg + 7);
} else if (!strcmp(arg, "--log-size")) {
revs->show_log_size = 1;
}
/*
* Grepping the commit log
*/
else if (!prefixcmp(arg, "--author=")) {
add_header_grep(revs, "author", arg+9);
} else if (!prefixcmp(arg, "--committer=")) {
add_header_grep(revs, "committer", arg+12);
} else if (!prefixcmp(arg, "--grep=")) {
add_message_grep(revs, arg+7);
} else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
if (revs->grep_filter)
revs->grep_filter->regflags |= REG_EXTENDED;
} else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
if (revs->grep_filter)
revs->grep_filter->regflags |= REG_ICASE;
} else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
if (revs->grep_filter)
revs->grep_filter->fixed = 1;
} else if (!strcmp(arg, "--all-match")) {
if (revs->grep_filter)
revs->grep_filter->all_match = 1;
} else if (!prefixcmp(arg, "--encoding=")) {
arg += 11;
if (strcmp(arg, "none"))
git_log_output_encoding = xstrdup(arg);
else
git_log_output_encoding = "";
} else if (!strcmp(arg, "--reverse")) {
revs->reverse ^= 1;
} else if (!strcmp(arg, "--children")) {
revs->children.name = "children";
revs->limited = 1;
} else {
int opts = diff_opt_parse(&revs->diffopt, argv, argc);
if (!opts)
unkv[(*unkc)++] = arg;
return opts;
}
return 1;
}
void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
const struct option *options,
const char * const usagestr[])
{
int n = handle_revision_opt(revs, ctx->argc, ctx->argv,
&ctx->cpidx, ctx->out);
if (n <= 0) {
error("unknown option `%s'", ctx->argv[0]);
usage_with_options(usagestr, options);
}
ctx->argv += n;
ctx->argc -= n;
}
/*
* Parse revision information, filling in the "rev_info" structure,
* and removing the used arguments from the argument list.
@@ -982,12 +1219,7 @@ static void add_ignore_packed(struct rev_info *revs, const char *name)
*/
int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
{
int i, flags, seen_dashdash, show_merge;
const char **unrecognized = argv + 1;
int left = 1;
int all_match = 0;
int regflags = 0;
int fixed = 0;
int i, flags, left, seen_dashdash;
/* First, search for "--" */
seen_dashdash = 0;
@@ -1003,58 +1235,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
break;
}
flags = show_merge = 0;
for (i = 1; i < argc; i++) {
/* Second, deal with arguments and options */
flags = 0;
for (left = i = 1; i < argc; i++) {
const char *arg = argv[i];
if (*arg == '-') {
int opts;
if (!prefixcmp(arg, "--max-count=")) {
revs->max_count = atoi(arg + 12);
continue;
}
if (!prefixcmp(arg, "--skip=")) {
revs->skip_count = atoi(arg + 7);
continue;
}
/* accept -<digit>, like traditional "head" */
if ((*arg == '-') && isdigit(arg[1])) {
revs->max_count = atoi(arg + 1);
continue;
}
if (!strcmp(arg, "-n")) {
if (argc <= i + 1)
die("-n requires an argument");
revs->max_count = atoi(argv[++i]);
continue;
}
if (!prefixcmp(arg, "-n")) {
revs->max_count = atoi(arg + 2);
continue;
}
if (!prefixcmp(arg, "--max-age=")) {
revs->max_age = atoi(arg + 10);
continue;
}
if (!prefixcmp(arg, "--since=")) {
revs->max_age = approxidate(arg + 8);
continue;
}
if (!prefixcmp(arg, "--after=")) {
revs->max_age = approxidate(arg + 8);
continue;
}
if (!prefixcmp(arg, "--min-age=")) {
revs->min_age = atoi(arg + 10);
continue;
}
if (!prefixcmp(arg, "--before=")) {
revs->min_age = approxidate(arg + 9);
continue;
}
if (!prefixcmp(arg, "--until=")) {
revs->min_age = approxidate(arg + 8);
continue;
}
if (!strcmp(arg, "--all")) {
handle_refs(revs, flags, for_each_ref);
continue;
@@ -1071,265 +1258,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
handle_refs(revs, flags, for_each_remote_ref);
continue;
}
if (!strcmp(arg, "--first-parent")) {
revs->first_parent_only = 1;
continue;
}
if (!strcmp(arg, "--reflog")) {
handle_reflog(revs, flags);
continue;
}
if (!strcmp(arg, "-g") ||
!strcmp(arg, "--walk-reflogs")) {
init_reflog_walk(&revs->reflog_info);
continue;
}
if (!strcmp(arg, "--not")) {
flags ^= UNINTERESTING;
continue;
}
if (!strcmp(arg, "--default")) {
if (++i >= argc)
die("bad --default argument");
def = argv[i];
continue;
}
if (!strcmp(arg, "--merge")) {
show_merge = 1;
continue;
}
if (!strcmp(arg, "--topo-order")) {
revs->lifo = 1;
revs->topo_order = 1;
continue;
}
if (!strcmp(arg, "--date-order")) {
revs->lifo = 0;
revs->topo_order = 1;
continue;
}
if (!prefixcmp(arg, "--early-output")) {
int count = 100;
switch (arg[14]) {
case '=':
count = atoi(arg+15);
/* Fallthrough */
case 0:
revs->topo_order = 1;
revs->early_output = count;
continue;
}
}
if (!strcmp(arg, "--parents")) {
revs->rewrite_parents = 1;
revs->print_parents = 1;
continue;
}
if (!strcmp(arg, "--dense")) {
revs->dense = 1;
continue;
}
if (!strcmp(arg, "--sparse")) {
revs->dense = 0;
continue;
}
if (!strcmp(arg, "--show-all")) {
revs->show_all = 1;
continue;
}
if (!strcmp(arg, "--remove-empty")) {
revs->remove_empty_trees = 1;
continue;
}
if (!strcmp(arg, "--no-merges")) {
revs->no_merges = 1;
continue;
}
if (!strcmp(arg, "--boundary")) {
revs->boundary = 1;
continue;
}
if (!strcmp(arg, "--left-right")) {
revs->left_right = 1;
continue;
}
if (!strcmp(arg, "--cherry-pick")) {
revs->cherry_pick = 1;
revs->limited = 1;
continue;
}
if (!strcmp(arg, "--objects")) {
revs->tag_objects = 1;
revs->tree_objects = 1;
revs->blob_objects = 1;
continue;
}
if (!strcmp(arg, "--objects-edge")) {
revs->tag_objects = 1;
revs->tree_objects = 1;
revs->blob_objects = 1;
revs->edge_hint = 1;
continue;
}
if (!strcmp(arg, "--unpacked")) {
revs->unpacked = 1;
free(revs->ignore_packed);
revs->ignore_packed = NULL;
revs->num_ignore_packed = 0;
continue;
}
if (!prefixcmp(arg, "--unpacked=")) {
revs->unpacked = 1;
add_ignore_packed(revs, arg+11);
continue;
}
if (!strcmp(arg, "-r")) {
revs->diff = 1;
DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
continue;
}
if (!strcmp(arg, "-t")) {
revs->diff = 1;
DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
DIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE);
continue;
}
if (!strcmp(arg, "-m")) {
revs->ignore_merges = 0;
continue;
}
if (!strcmp(arg, "-c")) {
revs->diff = 1;
revs->dense_combined_merges = 0;
revs->combine_merges = 1;
continue;
}
if (!strcmp(arg, "--cc")) {
revs->diff = 1;
revs->dense_combined_merges = 1;
revs->combine_merges = 1;
continue;
}
if (!strcmp(arg, "-v")) {
revs->verbose_header = 1;
continue;
}
if (!strcmp(arg, "--pretty")) {
revs->verbose_header = 1;
get_commit_format(arg+8, revs);
continue;
}
if (!prefixcmp(arg, "--pretty=")) {
revs->verbose_header = 1;
get_commit_format(arg+9, revs);
continue;
}
if (!strcmp(arg, "--graph")) {
revs->topo_order = 1;
revs->rewrite_parents = 1;
revs->graph = graph_init(revs);
continue;
}
if (!strcmp(arg, "--root")) {
revs->show_root_diff = 1;
continue;
}
if (!strcmp(arg, "--no-commit-id")) {
revs->no_commit_id = 1;
continue;
}
if (!strcmp(arg, "--always")) {
revs->always_show_header = 1;
continue;
}
if (!strcmp(arg, "--no-abbrev")) {
revs->abbrev = 0;
continue;
}
if (!strcmp(arg, "--abbrev")) {
revs->abbrev = DEFAULT_ABBREV;
continue;
}
if (!prefixcmp(arg, "--abbrev=")) {
revs->abbrev = strtoul(arg + 9, NULL, 10);
if (revs->abbrev < MINIMUM_ABBREV)
revs->abbrev = MINIMUM_ABBREV;
else if (revs->abbrev > 40)
revs->abbrev = 40;
continue;
}
if (!strcmp(arg, "--abbrev-commit")) {
revs->abbrev_commit = 1;
continue;
}
if (!strcmp(arg, "--full-diff")) {
revs->diff = 1;
revs->full_diff = 1;
continue;
}
if (!strcmp(arg, "--full-history")) {
revs->simplify_history = 0;
continue;
}
if (!strcmp(arg, "--relative-date")) {
revs->date_mode = DATE_RELATIVE;
continue;
}
if (!strncmp(arg, "--date=", 7)) {
revs->date_mode = parse_date_format(arg + 7);
continue;
}
if (!strcmp(arg, "--log-size")) {
revs->show_log_size = 1;
continue;
}
/*
* Grepping the commit log
*/
if (!prefixcmp(arg, "--author=")) {
add_header_grep(revs, "author", arg+9);
continue;
}
if (!prefixcmp(arg, "--committer=")) {
add_header_grep(revs, "committer", arg+12);
continue;
}
if (!prefixcmp(arg, "--grep=")) {
add_message_grep(revs, arg+7);
continue;
}
if (!strcmp(arg, "--extended-regexp") ||
!strcmp(arg, "-E")) {
regflags |= REG_EXTENDED;
continue;
}
if (!strcmp(arg, "--regexp-ignore-case") ||
!strcmp(arg, "-i")) {
regflags |= REG_ICASE;
continue;
}
if (!strcmp(arg, "--fixed-strings") ||
!strcmp(arg, "-F")) {
fixed = 1;
continue;
}
if (!strcmp(arg, "--all-match")) {
all_match = 1;
continue;
}
if (!prefixcmp(arg, "--encoding=")) {
arg += 11;
if (strcmp(arg, "none"))
git_log_output_encoding = xstrdup(arg);
else
git_log_output_encoding = "";
continue;
}
if (!strcmp(arg, "--reverse")) {
revs->reverse ^= 1;
continue;
}
if (!strcmp(arg, "--no-walk")) {
revs->no_walk = 1;
continue;
@@ -1339,13 +1275,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
continue;
}
opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
opts = handle_revision_opt(revs, argc - i, argv + i, &left, argv);
if (opts > 0) {
i += opts - 1;
continue;
}
*unrecognized++ = arg;
left++;
if (opts < 0)
exit(128);
continue;
}
@@ -1369,21 +1305,18 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
}
}
if (revs->grep_filter) {
revs->grep_filter->regflags |= regflags;
revs->grep_filter->fixed = fixed;
}
if (show_merge)
if (revs->def == NULL)
revs->def = def;
if (revs->show_merge)
prepare_show_merge(revs);
if (def && !revs->pending.nr) {
if (revs->def && !revs->pending.nr) {
unsigned char sha1[20];
struct object *object;
unsigned mode;
if (get_sha1_with_mode(def, sha1, &mode))
die("bad default revision '%s'", def);
object = get_reference(revs, def, sha1, 0);
add_pending_object_with_mode(revs, object, def, mode);
if (get_sha1_with_mode(revs->def, sha1, &mode))
die("bad default revision '%s'", revs->def);
object = get_reference(revs, revs->def, sha1, 0);
add_pending_object_with_mode(revs, object, revs->def, mode);
}
/* Did the user ask for any diff output? Run the diff! */
@@ -1417,12 +1350,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
die("diff_setup_done failed");
if (revs->grep_filter) {
revs->grep_filter->all_match = all_match;
compile_grep_patterns(revs->grep_filter);
}
if (revs->reverse && revs->reflog_info)
die("cannot combine --reverse with --walk-reflogs");
if (revs->rewrite_parents && revs->children.name)
die("cannot combine --parents and --children");
/*
* Limitations on the graph functionality
@@ -1436,6 +1370,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
return left;
}
static void add_child(struct rev_info *revs, struct commit *parent, struct commit *child)
{
struct commit_list *l = xcalloc(1, sizeof(*l));
l->item = child;
l->next = add_decoration(&revs->children, &parent->object, l);
}
static void set_children(struct rev_info *revs)
{
struct commit_list *l;
for (l = revs->commits; l; l = l->next) {
struct commit *commit = l->item;
struct commit_list *p;
for (p = commit->parents; p; p = p->next)
add_child(revs, p->item, commit);
}
}
int prepare_revision_walk(struct rev_info *revs)
{
int nr = revs->pending.nr;
@@ -1464,6 +1418,8 @@ int prepare_revision_walk(struct rev_info *revs)
return -1;
if (revs->topo_order)
sort_in_topological_order(&revs->commits, revs->lifo);
if (revs->children.name)
set_children(revs);
return 0;
}
@@ -1475,10 +1431,12 @@ enum rewrite_result {
static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
{
struct commit_list *cache = NULL;
for (;;) {
struct commit *p = *pp;
if (!revs->limited)
if (add_parents_to_list(revs, p, &revs->commits) < 0)
if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0)
return rewrite_one_error;
if (p->parents && p->parents->next)
return rewrite_one_ok;
@@ -1541,6 +1499,11 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
commit->buffer, strlen(commit->buffer));
}
static inline int want_ancestry(struct rev_info *revs)
{
return (revs->rewrite_parents || revs->children.name);
}
enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
{
if (commit->object.flags & SHOWN)
@@ -1561,13 +1524,13 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
/* Commit without changes? */
if (commit->object.flags & TREESAME) {
/* drop merges unless we want parenthood */
if (!revs->rewrite_parents)
if (!want_ancestry(revs))
return commit_ignore;
/* non-merge - always ignore it */
if (!commit->parents || !commit->parents->next)
return commit_ignore;
}
if (revs->rewrite_parents && rewrite_parents(revs, commit) < 0)
if (want_ancestry(revs) && rewrite_parents(revs, commit) < 0)
return commit_error;
}
return commit_show;
@@ -1597,7 +1560,7 @@ static struct commit *get_revision_1(struct rev_info *revs)
if (revs->max_age != -1 &&
(commit->date < revs->max_age))
continue;
if (add_parents_to_list(revs, commit, &revs->commits) < 0)
if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0)
return NULL;
}

View File

@@ -1,6 +1,8 @@
#ifndef REVISION_H
#define REVISION_H
#include "parse-options.h"
#define SEEN (1u<<0)
#define UNINTERESTING (1u<<1)
#define TREESAME (1u<<2)
@@ -11,6 +13,7 @@
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
#define TOPOSORT (1u<<9) /* In the active toposort list.. */
#define ALL_REV_FLAGS ((1u<<10)-1)
struct rev_info;
struct log_info;
@@ -25,6 +28,7 @@ struct rev_info {
/* Basic information */
const char *prefix;
const char *def;
void *prune_data;
unsigned int early_output;
@@ -65,6 +69,7 @@ struct rev_info {
/* Format info */
unsigned int shown_one:1,
show_merge:1,
abbrev_commit:1,
use_terminator:1,
missing_newline:1;
@@ -104,6 +109,7 @@ struct rev_info {
struct diff_options pruning;
struct reflog_walk_info *reflog_info;
struct decoration children;
};
#define REV_TREE_SAME 0
@@ -118,6 +124,9 @@ volatile show_early_output_fn_t show_early_output;
extern void init_revisions(struct rev_info *revs, const char *prefix);
extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
const struct option *options,
const char * const usagestr[]);
extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);
extern int prepare_revision_walk(struct rev_info *revs);

View File

@@ -1006,6 +1006,18 @@ static void mark_bad_packed_object(struct packed_git *p,
p->num_bad_objects++;
}
static int has_packed_and_bad(const unsigned char *sha1)
{
struct packed_git *p;
unsigned i;
for (p = packed_git; p; p = p->next)
for (i = 0; i < p->num_bad_objects; i++)
if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
return 1;
return 0;
}
int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
{
unsigned char real_sha1[20];
@@ -1647,7 +1659,7 @@ static void *unpack_delta_entry(struct packed_git *p,
sha1_to_hex(base_sha1), (uintmax_t)base_offset,
p->pack_name);
mark_bad_packed_object(p, base_sha1);
base = read_sha1_file(base_sha1, type, &base_size);
base = read_object(base_sha1, type, &base_size);
if (!base)
return NULL;
}
@@ -1945,7 +1957,7 @@ static void *read_packed_sha1(const unsigned char *sha1,
error("failed to read object %s at offset %"PRIuMAX" from %s",
sha1_to_hex(sha1), (uintmax_t)e.offset, e.p->pack_name);
mark_bad_packed_object(e.p, sha1);
data = read_sha1_file(sha1, type, size);
data = read_object(sha1, type, size);
}
return data;
}
@@ -2010,8 +2022,8 @@ int pretend_sha1_file(void *buf, unsigned long len, enum object_type type,
return 0;
}
void *read_sha1_file(const unsigned char *sha1, enum object_type *type,
unsigned long *size)
void *read_object(const unsigned char *sha1, enum object_type *type,
unsigned long *size)
{
unsigned long mapsize;
void *map, *buf;
@@ -2037,6 +2049,16 @@ void *read_sha1_file(const unsigned char *sha1, enum object_type *type,
return read_packed_sha1(sha1, type, size);
}
void *read_sha1_file(const unsigned char *sha1, enum object_type *type,
unsigned long *size)
{
void *data = read_object(sha1, type, size);
/* legacy behavior is to die on corrupted objects */
if (!data && (has_loose_object(sha1) || has_packed_and_bad(sha1)))
die("object %s is corrupted", sha1_to_hex(sha1));
return data;
}
void *read_object_with_reference(const unsigned char *sha1,
const char *required_type_name,
unsigned long *size,

View File

@@ -11,6 +11,7 @@ struct shortlog {
int wrap;
int in1;
int in2;
int user_format;
char *common_repo_prefix;
int email;

View File

@@ -15,6 +15,7 @@ test_expect_success setup '
>file &&
git add file &&
test_tick &&
git commit -m initial &&
echo >file &&
git add file
@@ -24,11 +25,11 @@ test_expect_success setup '
test_expect_success 'write-tree should notice unwritable repository' '
(
chmod a-w .git/objects
chmod a-w .git/objects .git/objects/?? &&
test_must_fail git write-tree
)
status=$?
chmod 775 .git/objects
chmod 775 .git/objects .git/objects/??
(exit $status)
'
@@ -36,11 +37,11 @@ test_expect_success 'write-tree should notice unwritable repository' '
test_expect_success 'commit should notice unwritable repository' '
(
chmod a-w .git/objects
chmod a-w .git/objects .git/objects/?? &&
test_must_fail git commit -m second
)
status=$?
chmod 775 .git/objects
chmod 775 .git/objects .git/objects/??
(exit $status)
'
@@ -48,12 +49,12 @@ test_expect_success 'commit should notice unwritable repository' '
test_expect_success 'update-index should notice unwritable repository' '
(
echo a >file &&
chmod a-w .git/objects
echo 6O >file &&
chmod a-w .git/objects .git/objects/?? &&
test_must_fail git update-index file
)
status=$?
chmod 775 .git/objects
chmod 775 .git/objects .git/objects/??
(exit $status)
'
@@ -62,11 +63,11 @@ test_expect_success 'add should notice unwritable repository' '
(
echo b >file &&
chmod a-w .git/objects
chmod a-w .git/objects .git/objects/?? &&
test_must_fail git add file
)
status=$?
chmod 775 .git/objects
chmod 775 .git/objects .git/objects/??
(exit $status)
'

View File

@@ -17,6 +17,29 @@ test_expect_success 'shared = 0400 (faulty permission u-w)' '
test $ret != "0"
'
for u in 002 022
do
test_expect_success "shared=1 does not clear bits preset by umask $u" '
mkdir sub && (
cd sub &&
umask $u &&
git init --shared=1 &&
test 1 = "$(git config core.sharedrepository)"
) &&
actual=$(ls -l sub/.git/HEAD)
case "$actual" in
-rw-rw-r--*)
: happy
;;
*)
echo Oops, .git/HEAD is not 0664 but $actual
false
;;
esac
'
rm -rf sub
done
test_expect_success 'shared=all' '
mkdir sub &&
cd sub &&

View File

@@ -50,12 +50,12 @@ test_debug \
test_expect_success \
'rebase topic branch against new master and check git-am did not get halted' \
'git-rebase master && test ! -d .dotest'
'git-rebase master && test ! -d .git/rebase'
test_expect_success \
'rebase --merge topic branch that was partially merged upstream' \
'git-checkout -f my-topic-branch-merge &&
git-rebase --merge master-merge &&
test ! -d .git/.dotest-merge'
test ! -d .git/rebase-merge'
test_done

View File

@@ -159,19 +159,19 @@ test_expect_success 'stop on conflicting pick' '
git tag new-branch1 &&
test_must_fail git rebase -i master &&
test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
test_cmp expect .git/.dotest-merge/patch &&
test_cmp expect .git/rebase-merge/patch &&
test_cmp expect2 file1 &&
test "$(git-diff --name-status |
sed -n -e "/^U/s/^U[^a-z]*//p")" = file1 &&
test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
test 0 = $(grep -c "^[^#]" < .git/.dotest-merge/git-rebase-todo)
test 4 = $(grep -v "^#" < .git/rebase-merge/done | wc -l) &&
test 0 = $(grep -c "^[^#]" < .git/rebase-merge/git-rebase-todo)
'
test_expect_success 'abort' '
git rebase --abort &&
test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
! test -d .git/.dotest-merge
! test -d .git/rebase-merge
'
test_expect_success 'retain authorship' '
@@ -211,7 +211,7 @@ test_expect_success 'preserve merges with -p' '
git add unrelated-file &&
test_tick &&
git commit -m "unrelated" &&
git checkout -b to-be-rebased master &&
git checkout -b another-branch master &&
echo B > file1 &&
test_tick &&
git commit -m J file1 &&
@@ -220,12 +220,28 @@ test_expect_success 'preserve merges with -p' '
echo C > file1 &&
test_tick &&
git commit -m K file1 &&
echo D > file1 &&
test_tick &&
git commit -m L1 file1 &&
git checkout HEAD^ &&
echo 1 > unrelated-file &&
test_tick &&
git commit -m L2 unrelated-file &&
test_tick &&
git merge another-branch &&
echo E > file1 &&
test_tick &&
git commit -m M file1 &&
git checkout -b to-be-rebased &&
test_tick &&
git rebase -i -p --onto branch1 master &&
test $(git rev-parse HEAD^^2) = $(git rev-parse to-be-preserved) &&
test $(git rev-parse HEAD~3) = $(git rev-parse branch1) &&
test $(git show HEAD:file1) = C &&
test $(git show HEAD~2:file1) = B
test $(git rev-parse HEAD~6) = $(git rev-parse branch1) &&
test $(git rev-parse HEAD~4^2) = $(git rev-parse to-be-preserved) &&
test $(git rev-parse HEAD^^2^) = $(git rev-parse HEAD^^^) &&
test $(git show HEAD~5:file1) = B &&
test $(git show HEAD~3:file1) = C &&
test $(git show HEAD:file1) = E &&
test $(git show HEAD:unrelated-file) = 1
'
test_expect_success '--continue tries to commit' '

View File

@@ -74,7 +74,7 @@ testrebase() {
'
}
testrebase "" .dotest
testrebase " --merge" .git/.dotest-merge
testrebase "" .git/rebase
testrebase " --merge" .git/rebase-merge
test_done

30
t/t3503-cherry-pick-root.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/sh
test_description='test cherry-picking a root commit'
. ./test-lib.sh
test_expect_success setup '
echo first > file1 &&
git add file1 &&
test_tick &&
git commit -m "first" &&
git symbolic-ref HEAD refs/heads/second &&
rm .git/index file1 &&
echo second > file2 &&
git add file2 &&
test_tick &&
git commit -m "second"
'
test_expect_success 'cherry-pick a root commit' '
git cherry-pick master &&
test first = $(cat file1)
'
test_done

View File

@@ -66,6 +66,73 @@ test_expect_success 'revert works (commit)' '
grep "unchanged *+3/-0 file" output
'
cat >expected <<EOF
EOF
cat >fake_editor.sh <<EOF
EOF
chmod a+x fake_editor.sh
test_set_editor "$(pwd)/fake_editor.sh"
test_expect_success 'dummy edit works' '
(echo e; echo a) | git add -p &&
git diff > diff &&
test_cmp expected diff
'
cat >patch <<EOF
@@ -1,1 +1,4 @@
this
+patch
-doesn't
apply
EOF
echo "#!$SHELL_PATH" >fake_editor.sh
cat >>fake_editor.sh <<\EOF
mv -f "$1" oldpatch &&
mv -f patch "$1"
EOF
chmod a+x fake_editor.sh
test_set_editor "$(pwd)/fake_editor.sh"
test_expect_success 'bad edit rejected' '
git reset &&
(echo e; echo n; echo d) | git add -p >output &&
grep "hunk does not apply" output
'
cat >patch <<EOF
this patch
is garbage
EOF
test_expect_success 'garbage edit rejected' '
git reset &&
(echo e; echo n; echo d) | git add -p >output &&
grep "hunk does not apply" output
'
cat >patch <<EOF
@@ -1,0 +1,0 @@
baseline
+content
+newcontent
+lines
EOF
cat >expected <<EOF
diff --git a/file b/file
index b5dd6c9..f910ae9 100644
--- a/file
+++ b/file
@@ -1,4 +1,4 @@
baseline
content
-newcontent
+more
lines
EOF
test_expect_success 'real edit works' '
(echo e; echo n; echo d) | git add -p &&
git diff >output &&
test_cmp expected output
'
if test "$(git config --bool core.filemode)" = false
then
say 'skipping filemode tests (filesystem does not properly support modes)'

View File

@@ -125,4 +125,64 @@ $test_expect 'stash pop' '
test 0 = $(git stash list | wc -l)
'
cat > expect << EOF
diff --git a/file2 b/file2
new file mode 100644
index 0000000..1fe912c
--- /dev/null
+++ b/file2
@@ -0,0 +1 @@
+bar2
EOF
cat > expect1 << EOF
diff --git a/file b/file
index 257cc56..5716ca5 100644
--- a/file
+++ b/file
@@ -1 +1 @@
-foo
+bar
EOF
cat > expect2 << EOF
diff --git a/file b/file
index 7601807..5716ca5 100644
--- a/file
+++ b/file
@@ -1 +1 @@
-baz
+bar
diff --git a/file2 b/file2
new file mode 100644
index 0000000..1fe912c
--- /dev/null
+++ b/file2
@@ -0,0 +1 @@
+bar2
EOF
test_expect_success 'stash branch' '
echo foo > file &&
git commit file -m first
echo bar > file &&
echo bar2 > file2 &&
git add file2 &&
git stash &&
echo baz > file &&
git commit file -m second &&
git stash branch stashbranch &&
test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
test $(git rev-parse HEAD) = $(git rev-parse master^) &&
git diff --cached > output &&
test_cmp output expect &&
git diff > output &&
test_cmp output expect1 &&
git add file &&
git commit -m alternate\ second &&
git diff master..stashbranch > output &&
test_cmp output expect2 &&
test 0 = $(git stash list | wc -l)
'
test_done

View File

@@ -102,7 +102,7 @@ test_expect_success 'am applies patch correctly' '
git checkout first &&
test_tick &&
git am <patch1 &&
! test -d .dotest &&
! test -d .git/rebase &&
test -z "$(git diff second)" &&
test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
@@ -123,7 +123,7 @@ test_expect_success 'am changes committer and keeps author' '
test_tick &&
git checkout first &&
git am patch2 &&
! test -d .dotest &&
! test -d .git/rebase &&
test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
test -z "$(git diff master..HEAD)" &&
test -z "$(git diff master^..HEAD^)" &&
@@ -163,7 +163,7 @@ test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
test_expect_success 'am --keep really keeps the subject' '
git checkout HEAD^ &&
git am --keep patch4 &&
! test -d .dotest &&
! test -d .git/rebase &&
git-cat-file commit HEAD |
grep -q -F "Re: Re: Re: [PATCH 1/5 v2] third"
'
@@ -176,19 +176,19 @@ test_expect_success 'am -3 falls back to 3-way merge' '
test_tick &&
git commit -m "copied stuff" &&
git am -3 lorem-move.patch &&
! test -d .dotest &&
! test -d .git/rebase &&
test -z "$(git diff lorem)"
'
test_expect_success 'am pauses on conflict' '
git checkout lorem2^^ &&
! git am lorem-move.patch &&
test -d .dotest
test -d .git/rebase
'
test_expect_success 'am --skip works' '
git am --skip &&
! test -d .dotest &&
! test -d .git/rebase &&
test -z "$(git diff lorem2^^ -- file)" &&
test goodbye = "$(cat another)"
'
@@ -196,31 +196,31 @@ test_expect_success 'am --skip works' '
test_expect_success 'am --resolved works' '
git checkout lorem2^^ &&
! git am lorem-move.patch &&
test -d .dotest &&
test -d .git/rebase &&
echo resolved >>file &&
git add file &&
git am --resolved &&
! test -d .dotest &&
! test -d .git/rebase &&
test goodbye = "$(cat another)"
'
test_expect_success 'am takes patches from a Pine mailbox' '
git checkout first &&
cat pine patch1 | git am &&
! test -d .dotest &&
! test -d .git/rebase &&
test -z "$(git diff master^..HEAD)"
'
test_expect_success 'am fails on mail without patch' '
! git am <failmail &&
rm -r .dotest/
rm -r .git/rebase/
'
test_expect_success 'am fails on empty patch' '
echo "---" >>failmail &&
! git am <failmail &&
git am --skip &&
! test -d .dotest
! test -d .git/rebase
'
test_expect_success 'am works from stdin in subdirectory' '

View File

@@ -9,6 +9,8 @@ test_description='git rerere
. ./test-lib.sh
cat > a1 << EOF
Some title
==========
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
Or to take arms against a sea of troubles,
@@ -24,6 +26,8 @@ git commit -q -a -m initial
git checkout -b first
cat >> a1 << EOF
Some title
==========
To die, to sleep;
To sleep: perchance to dream: ay, there's the rub;
For in that sleep of death what dreams may come
@@ -35,7 +39,7 @@ git commit -q -a -m first
git checkout -b second master
git show first:a1 |
sed -e 's/To die, t/To die! T/' > a1
sed -e 's/To die, t/To die! T/' -e 's/Some title/Some Title/' > a1
echo "* END *" >>a1
git commit -q -a -m second
@@ -53,16 +57,16 @@ test_expect_success 'conflicting merge' '
! git merge first
'
sha1=$(sed -e 's/ .*//' .git/rr-cache/MERGE_RR)
sha1=$(sed -e 's/ .*//' .git/MERGE_RR)
rr=.git/rr-cache/$sha1
test_expect_success 'recorded preimage' "grep ======= $rr/preimage"
test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage"
test_expect_success 'rerere.enabled works, too' '
rm -rf .git/rr-cache &&
git config rerere.enabled true &&
git reset --hard &&
! git merge first &&
grep ======= $rr/preimage
grep ^=======$ $rr/preimage
'
test_expect_success 'no postimage or thisimage yet' \
@@ -71,7 +75,7 @@ test_expect_success 'no postimage or thisimage yet' \
test_expect_success 'preimage has right number of lines' '
cnt=$(sed -ne "/^<<<<<<</,/^>>>>>>>/p" $rr/preimage | wc -l) &&
test $cnt = 9
test $cnt = 13
'
@@ -80,13 +84,23 @@ git show first:a1 > a1
cat > expect << EOF
--- a/a1
+++ b/a1
@@ -6,17 +6,9 @@
@@ -1,4 +1,4 @@
-Some Title
+Some title
==========
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
@@ -8,21 +8,11 @@
The heart-ache and the thousand natural shocks
That flesh is heir to, 'tis a consummation
Devoutly to be wish'd.
-<<<<<<<
-Some Title
-==========
-To die! To sleep;
-=======
Some title
==========
To die, to sleep;
->>>>>>>
To sleep: perchance to dream: ay, there's the rub;
@@ -124,12 +138,12 @@ test_expect_success 'another conflicting merge' '
'
git show first:a1 | sed 's/To die: t/To die! T/' > expect
test_expect_success 'rerere kicked in' "! grep ======= a1"
test_expect_success 'rerere kicked in' "! grep ^=======$ a1"
test_expect_success 'rerere prefers first change' 'test_cmp a1 expect'
rm $rr/postimage
echo "$sha1 a1" | perl -pe 'y/\012/\000/' > .git/rr-cache/MERGE_RR
echo "$sha1 a1" | perl -pe 'y/\012/\000/' > .git/MERGE_RR
test_expect_success 'rerere clear' 'git rerere clear'
@@ -176,7 +190,7 @@ test_expect_success 'file2 added differently in two branches' '
git add file2 &&
git commit -m version2 &&
! git merge fourth &&
sha1=$(sed -e "s/ .*//" .git/rr-cache/MERGE_RR) &&
sha1=$(sed -e "s/ .*//" .git/MERGE_RR) &&
rr=.git/rr-cache/$sha1 &&
echo Cello > file2 &&
git add file2 &&

View File

@@ -11,7 +11,7 @@ test_expect_success 'split sample box' \
'git mailsplit -o. ../t5100/sample.mbox >last &&
last=`cat last` &&
echo total is $last &&
test `cat last` = 9'
test `cat last` = 10'
for mail in `echo 00*`
do

35
t/t5100/0010 Normal file
View File

@@ -0,0 +1,35 @@
From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= <lukass@etek.chalmers.se>
Date: Thu, 10 Jul 2008 23:41:33 +0200
Subject: Re: discussion that lead to this patch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[PATCH] git-mailinfo: Fix getting the subject from the body
"Subject: " isn't in the static array "header", and thus
memcmp("Subject: ", header[i], 7) will never match.
Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
builtin-mailinfo.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 962aa34..2d1520f 100644
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over
return 1;
if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
for (i = 0; header[i]; i++) {
- if (!memcmp("Subject: ", header[i], 9)) {
+ if (!memcmp("Subject", header[i], 7)) {
if (! handle_header(line, hdr_data[i], 0)) {
return 1;
}
--
1.5.6.2.455.g1efb2

5
t/t5100/info0010 Normal file
View File

@@ -0,0 +1,5 @@
Author: Lukas Sandström
Email: lukass@etek.chalmers.se
Subject: git-mailinfo: Fix getting the subject from the body
Date: Thu, 10 Jul 2008 23:41:33 +0200

5
t/t5100/msg0010 Normal file
View File

@@ -0,0 +1,5 @@
"Subject: " isn't in the static array "header", and thus
memcmp("Subject: ", header[i], 7) will never match.
Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

20
t/t5100/patch0010 Normal file
View File

@@ -0,0 +1,20 @@
---
builtin-mailinfo.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 962aa34..2d1520f 100644
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over
return 1;
if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
for (i = 0; header[i]; i++) {
- if (!memcmp("Subject: ", header[i], 9)) {
+ if (!memcmp("Subject", header[i], 7)) {
if (! handle_header(line, hdr_data[i], 0)) {
return 1;
}
--
1.5.6.2.455.g1efb2

View File

@@ -430,3 +430,38 @@ index b426a14..97756ec 100644
=20
=20
2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= <lukass@etek.chalmers.se>
Date: Thu, 10 Jul 2008 23:41:33 +0200
Subject: Re: discussion that lead to this patch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[PATCH] git-mailinfo: Fix getting the subject from the body
"Subject: " isn't in the static array "header", and thus
memcmp("Subject: ", header[i], 7) will never match.
Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
builtin-mailinfo.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 962aa34..2d1520f 100644
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over
return 1;
if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
for (i = 0; header[i]; i++) {
- if (!memcmp("Subject: ", header[i], 9)) {
+ if (!memcmp("Subject", header[i], 7)) {
if (! handle_header(line, hdr_data[i], 0)) {
return 1;
}
--
1.5.6.2.455.g1efb2

View File

@@ -23,7 +23,7 @@ test_expect_success 'setup' '
: > super-file &&
git add super-file &&
git submodule add . sub &&
git submodule add "$(pwd)" sub &&
git symbolic-ref HEAD refs/heads/super &&
test_tick &&
git commit -m super-initial &&

View File

@@ -0,0 +1,60 @@
#!/bin/sh
test_description='git rev-list should notice bad commits'
. ./test-lib.sh
# Note:
# - compression level is set to zero to make "corruptions" easier to perform
# - reflog is disabled to avoid extra references which would twart the test
test_expect_success 'setup' \
'
git init &&
git config core.compression 0 &&
git config core.logallrefupdates false &&
echo "foo" > foo &&
git add foo &&
git commit -m "first commit" &&
echo "bar" > bar &&
git add bar &&
git commit -m "second commit" &&
echo "baz" > baz &&
git add baz &&
git commit -m "third commit" &&
echo "foo again" >> foo &&
git add foo &&
git commit -m "fourth commit" &&
git repack -a -f -d
'
test_expect_success 'verify number of revisions' \
'
revs=$(git rev-list --all | wc -l) &&
test $revs -eq 4 &&
first_commit=$(git rev-parse HEAD~3)
'
test_expect_success 'corrupt second commit object' \
'
perl -i.bak -pe "s/second commit/socond commit/" .git/objects/pack/*.pack &&
test_must_fail git fsck --full
'
test_expect_success 'rev-list should fail' \
'
test_must_fail git rev-list --all > /dev/null
'
test_expect_success 'git repack _MUST_ fail' \
'
test_must_fail git repack -a -f -d
'
test_expect_success 'first commit is still available' \
'
git log $first_commit
'
test_done

View File

@@ -89,4 +89,8 @@ EOF
test_expect_success 'Criss-cross merge result' 'cmp file file-expect'
test_expect_success 'Criss-cross merge fails (-s resolve)' \
'git reset --hard A^ &&
test_must_fail git merge -s resolve -m "final merge" B'
test_done

70
t/t6040-tracking-info.sh Executable file
View File

@@ -0,0 +1,70 @@
#!/bin/sh
test_description='remote tracking stats'
. ./test-lib.sh
advance () {
echo "$1" >"$1" &&
git add "$1" &&
test_tick &&
git commit -m "$1"
}
test_expect_success setup '
for i in a b c;
do
advance $i || break
done &&
git clone . test &&
(
cd test &&
git checkout -b b1 origin &&
git reset --hard HEAD^ &&
advance d &&
git checkout -b b2 origin &&
git reset --hard b1 &&
git checkout -b b3 origin &&
git reset --hard HEAD^ &&
git checkout -b b4 origin &&
advance e &&
advance f
)
'
script='s/^..\(b.\)[ 0-9a-f]*\[\([^]]*\)\].*/\1 \2/p'
cat >expect <<\EOF
b1 ahead 1, behind 1
b2 ahead 1, behind 1
b3 behind 1
b4 ahead 2
EOF
test_expect_success 'branch -v' '
(
cd test &&
git branch -v
) |
sed -n -e "$script" >actual &&
test_cmp expect actual
'
test_expect_success 'checkout' '
(
cd test && git checkout b1
) >actual &&
grep -e "have 1 and 1 different" actual
'
test_expect_success 'status' '
(
cd test &&
git checkout b1 >/dev/null &&
# reports nothing to commit
test_must_fail git status
) >actual &&
grep -e "have 1 and 1 different" actual
'
test_done

View File

@@ -465,11 +465,51 @@ test_expect_success 'merge log message' '
git merge --no-log c2 &&
git show -s --pretty=format:%b HEAD >msg.act &&
verify_diff msg.nolog msg.act "[OOPS] bad merge log message" &&
git merge --log c3 &&
git show -s --pretty=format:%b HEAD >msg.act &&
verify_diff msg.log msg.act "[OOPS] bad merge log message" &&
git reset --hard HEAD^ &&
git config merge.log yes &&
git merge c3 &&
git show -s --pretty=format:%b HEAD >msg.act &&
verify_diff msg.log msg.act "[OOPS] bad merge log message"
'
test_debug 'gitk --all'
test_expect_success 'merge c1 with c0, c2, c0, and c1' '
git reset --hard c1 &&
git config branch.master.mergeoptions "" &&
test_tick &&
git merge c0 c2 c0 c1 &&
verify_merge file result.1-5 &&
verify_parents $c1 $c2
'
test_debug 'gitk --all'
test_expect_success 'merge c1 with c0, c2, c0, and c1' '
git reset --hard c1 &&
git config branch.master.mergeoptions "" &&
test_tick &&
git merge c0 c2 c0 c1 &&
verify_merge file result.1-5 &&
verify_parents $c1 $c2
'
test_debug 'gitk --all'
test_expect_success 'merge c1 with c1 and c2' '
git reset --hard c1 &&
git config branch.master.mergeoptions "" &&
test_tick &&
git merge c1 c2 &&
verify_merge file result.1-5 &&
verify_parents $c1 $c2
'
test_debug 'gitk --all'
test_done

129
t/t7601-merge-pull-config.sh Executable file
View File

@@ -0,0 +1,129 @@
#!/bin/sh
test_description='git-merge
Testing pull.* configuration parsing.'
. ./test-lib.sh
test_expect_success 'setup' '
echo c0 >c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
echo c1 >c1.c &&
git add c1.c &&
git commit -m c1 &&
git tag c1 &&
git reset --hard c0 &&
echo c2 >c2.c &&
git add c2.c &&
git commit -m c2 &&
git tag c2 &&
git reset --hard c0 &&
echo c3 >c3.c &&
git add c3.c &&
git commit -m c3 &&
git tag c3
'
test_expect_success 'merge c1 with c2' '
git reset --hard c1 &&
test -f c0.c &&
test -f c1.c &&
test ! -f c2.c &&
test ! -f c3.c &&
git merge c2 &&
test -f c1.c &&
test -f c2.c
'
test_expect_success 'merge c1 with c2 (ours in pull.twohead)' '
git reset --hard c1 &&
git config pull.twohead ours &&
git merge c2 &&
test -f c1.c &&
! test -f c2.c
'
test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' '
git reset --hard c1 &&
git config pull.octopus "recursive" &&
test_must_fail git merge c2 c3 &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD)"
'
test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
git reset --hard c1 &&
git config pull.octopus "recursive octopus" &&
git merge c2 c3 &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
git diff --exit-code &&
test -f c0.c &&
test -f c1.c &&
test -f c2.c &&
test -f c3.c
'
conflict_count()
{
{
git diff-files --name-only
git ls-files --unmerged
} | wc -l
}
# c4 - c5
# \ c6
#
# There are two conflicts here:
#
# 1) Because foo.c is renamed to bar.c, recursive will handle this,
# resolve won't.
#
# 2) One in conflict.c and that will always fail.
test_expect_success 'setup conflicted merge' '
git reset --hard c0 &&
echo A >conflict.c &&
git add conflict.c &&
echo contents >foo.c &&
git add foo.c &&
git commit -m c4 &&
git tag c4 &&
echo B >conflict.c &&
git add conflict.c &&
git mv foo.c bar.c &&
git commit -m c5 &&
git tag c5 &&
git reset --hard c4 &&
echo C >conflict.c &&
git add conflict.c &&
echo secondline >> foo.c &&
git add foo.c &&
git commit -m c6 &&
git tag c6
'
# First do the merge with resolve and recursive then verify that
# recusive is choosen.
test_expect_success 'merge picks up the best result' '
git config pull.twohead "recursive resolve" &&
git reset --hard c5 &&
git merge -s resolve c6
resolve_count=$(conflict_count) &&
git reset --hard c5 &&
git merge -s recursive c6
recursive_count=$(conflict_count) &&
git reset --hard c5 &&
git merge c6
auto_count=$(conflict_count) &&
test $auto_count = $recursive_count &&
test $auto_count != $resolve_count
'
test_done

52
t/t7602-merge-octopus-many.sh Executable file
View File

@@ -0,0 +1,52 @@
#!/bin/sh
test_description='git-merge
Testing octopus merge with more than 25 refs.'
. ./test-lib.sh
test_expect_success 'setup' '
echo c0 > c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
i=1 &&
while test $i -le 30
do
git reset --hard c0 &&
echo c$i > c$i.c &&
git add c$i.c &&
git commit -m c$i &&
git tag c$i &&
i=`expr $i + 1` || return 1
done
'
test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
git reset --hard c1 &&
i=2 &&
refs="" &&
while test $i -le 30
do
refs="$refs c$i"
i=`expr $i + 1`
done
git merge $refs &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
i=1 &&
while test $i -le 30
do
test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" &&
i=`expr $i + 1` || return 1
done &&
git diff --exit-code &&
i=1 &&
while test $i -le 30
do
test -f c$i.c &&
i=`expr $i + 1` || return 1
done
'
test_done

63
t/t7603-merge-reduce-heads.sh Executable file
View File

@@ -0,0 +1,63 @@
#!/bin/sh
test_description='git-merge
Testing octopus merge when reducing parents to independent branches.'
. ./test-lib.sh
# 0 - 1
# \ 2
# \ 3
# \ 4 - 5
#
# So 1, 2, 3 and 5 should be kept, 4 should be avoided.
test_expect_success 'setup' '
echo c0 > c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
echo c1 > c1.c &&
git add c1.c &&
git commit -m c1 &&
git tag c1 &&
git reset --hard c0 &&
echo c2 > c2.c &&
git add c2.c &&
git commit -m c2 &&
git tag c2 &&
git reset --hard c0 &&
echo c3 > c3.c &&
git add c3.c &&
git commit -m c3 &&
git tag c3 &&
git reset --hard c0 &&
echo c4 > c4.c &&
git add c4.c &&
git commit -m c4 &&
git tag c4 &&
echo c5 > c5.c &&
git add c5.c &&
git commit -m c5 &&
git tag c5
'
test_expect_success 'merge c1 with c2, c3, c4, c5' '
git reset --hard c1 &&
git merge c2 c3 c4 c5 &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
git diff --exit-code &&
test -f c0.c &&
test -f c1.c &&
test -f c2.c &&
test -f c3.c &&
test -f c4.c &&
test -f c5.c
'
test_done

37
t/t7604-merge-custom-message.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/bin/sh
test_description='git-merge
Testing merge when using a custom message for the merge commit.'
. ./test-lib.sh
test_expect_success 'setup' '
echo c0 > c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
echo c1 > c1.c &&
git add c1.c &&
git commit -m c1 &&
git tag c1 &&
git reset --hard c0 &&
echo c2 > c2.c &&
git add c2.c &&
git commit -m c2 &&
git tag c2
'
cat >expected <<\EOF
custom message
Merge commit 'c2'
EOF
test_expect_success 'merge c2 with a custom message' '
git reset --hard c1 &&
git merge -m "custom message" c2 &&
git cat-file commit HEAD | sed -e "1,/^$/d" > actual &&
test_cmp expected actual
'
test_done

46
t/t7605-merge-resolve.sh Executable file
View File

@@ -0,0 +1,46 @@
#!/bin/sh
test_description='git-merge
Testing the resolve strategy.'
. ./test-lib.sh
test_expect_success 'setup' '
echo c0 > c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
echo c1 > c1.c &&
git add c1.c &&
git commit -m c1 &&
git tag c1 &&
git reset --hard c0 &&
echo c2 > c2.c &&
git add c2.c &&
git commit -m c2 &&
git tag c2 &&
git reset --hard c0 &&
echo c3 > c2.c &&
git add c2.c &&
git commit -m c3 &&
git tag c3
'
test_expect_success 'merge c1 to c2' '
git reset --hard c1 &&
git merge -s resolve c2 &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
git diff --exit-code &&
test -f c0.c &&
test -f c1.c &&
test -f c2.c
'
test_expect_success 'merge c2 to c3 (fails)' '
git reset --hard c2 &&
test_must_fail git merge -s resolve c3
'
test_done

View File

@@ -87,7 +87,7 @@ test_expect_success 'multiple dcommit from git-svn will not clobber svn' "
"
test_expect_success 'check that rebase really failed' 'test -d .dotest'
test_expect_success 'check that rebase really failed' 'test -d .git/rebase'
test_expect_success 'resolve, continue the rebase and dcommit' "
echo clobber and I really mean it > file &&

View File

@@ -49,4 +49,13 @@ test_expect_success 'verify metadata for /dir' "
grep '^git-svn-id: $dir_url@1 $uuid$'
"
test_expect_success 'find commit based on SVN revision number' "
git-svn find-rev r12 |
grep `git rev-parse HEAD`
"
test_expect_success 'empty rebase' "
git-svn rebase
"
test_done

View File

@@ -18,7 +18,7 @@ fi
cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
case "$cvsps_version" in
2.1)
2.1 | 2.2*)
;;
'')
say 'skipping cvsimport tests, cvsps not found'
@@ -26,7 +26,7 @@ case "$cvsps_version" in
exit
;;
*)
say 'skipping cvsimport tests, cvsps too old'
say 'skipping cvsimport tests, unsupported cvsps version'
test_done
exit
;;

Some files were not shown because too many files have changed in this diff Show More