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.
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
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
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
--- /dev/null
+GIT v1.5.6.4 Release Notes
+==========================
+
+Fixes since v1.5.6.3
+--------------------
+
+* Various commands could overflow its internal buffer on a platform
+ with small PATH_MAX value in a repository that has contents with
+ long pathnames.
+
+* There wasn't a way to make --pretty=format:%<> specifiers to honor
+ .mailmap name rewriting for authors and committers. Now you can with
+ %aN and %cN.
+
+* Bash completion wasted too many cycles; this has been optimized to be
+ usable again.
+
+* Bash completion lost ref part when completing something like "git show
+ pu:Makefile".
+
+* "git-cvsserver" did not clean up its temporary working area after annotate
+ request.
+
+* "git-daemon" called syslog() from its signal handler, which was a
+ no-no.
+
+* "git-fetch" into an empty repository used to remind that the fetch will
+ be huge by saying "no common commits", but this was an unnecessary
+ noise; it is already known by the user anyway.
+
+* "git-mailinfo" (hence "git-am") did not correctly handle in-body [PATCH]
+ line to override the commit title taken from the mail Subject header.
+
+* "git-rebase -i -p" lost parents that are not involved in the history
+ being rewritten.
+
+Contains other various documentation fixes.
+
+--
+exec >/var/tmp/1
+echo O=$(git describe maint)
+O=v1.5.6.3-21-gebcce31
+git shortlog --no-merges $O..maint
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.
* 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.
---
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
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.
"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:
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
'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
-----------
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
--------
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>.
Usually the command automatically creates a commit with
a commit log message stating which commit was
cherry-picked. This flag applies the change necessary
- to cherry-pick the named commit to your working tree,
- but does not make the commit. In addition, when this
- option is used, your working tree does not have to match
+ to cherry-pick the named commit to your working tree
+ and the index, but does not make the commit. In addition,
+ when this option is used, your index does not have to match
the HEAD commit. The cherry-pick is done against the
- beginning state of your working tree.
+ beginning state of your index.
+
This is useful when cherry-picking more than one commits'
-effect to your working tree in a row.
+effect to your index in a row.
-s::
--signoff::
SYNOPSIS
--------
-'git mailinfo' [-k] [-u | --encoding=<encoding>] <msg> <patch>
+'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] <msg> <patch>
DESCRIPTION
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.
(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.
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
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":
-n::
--no-commit::
Usually the command automatically creates a commit with
- a commit log message stating which commit was reverted.
- This flag applies the change necessary to revert the
- named commit to your working tree, but does not make the
- commit. In addition, when this option is used, your
- working tree does not have to match the HEAD commit.
- The revert is done against the beginning state of your
- working tree.
+ a commit log message stating which commit was
+ reverted. This flag applies the change necessary
+ to revert the named commit to your working tree
+ and the index, but does not make the commit. In addition,
+ when this option is used, your index does not have to match
+ the HEAD commit. The revert is done against the
+ beginning state of your index.
+
This is useful when reverting more than one commits'
-effect to your working tree in a row.
+effect to your index in a row.
-s::
--signoff::
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
-----------
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>]::
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.
... 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],
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>...]
'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...]
+DESCRIPTION
+-----------
+Submodules are a special kind of tree entries which refer to a particular tree
+state in another repository. The tree entry describes
+the existence of a submodule with the given name and the exact revision that
+should be used, while an entry in `.gitmodules` file gives the location of
+the repository.
+
+When checked out, submodules will maintain their own independent repositories
+within their directories; the only link between the submodule and the "parent
+project" is the tree entry within the parent project mentioned above.
+
+This command will manage the tree entries and contents of the gitmodules file
+for you, as well as inspecting the status of your submodules and updating them.
+When adding a new submodule to the tree, the 'add' subcommand is to be used.
+However, when pulling a tree containing submodules, these will not be checked
+out by default; the 'init' and 'update' subcommands will maintain submodules
+checked out and at appropriate revision in your working tree. You can inspect
+the current status of your submodules using the 'submodule' subcommand and get
+an overview of changes 'update' would perform using the 'summary' subcommand.
+
+
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
<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
-----
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].
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
------------------------------------------------
(edit files)
-$ git commit -a
+bob$ git commit -a
(repeat as necessary)
------------------------------------------------
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
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
`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
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,
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;
used for pulls:
-------------------------------------
-$ git config --get remote.origin.url
+bob$ git config --get remote.origin.url
/home/alice/project
-------------------------------------
name "origin/master":
-------------------------------------
-$ git branch -r
+bob$ git branch -r
origin/master
-------------------------------------
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;
- '%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
- '%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
Print the parents of the commit.
+--children::
+
+ Print the children of the commit.
+
ifdef::git-rev-list[]
--timestamp::
Print the raw commit timestamp.
-------------------------------------------------
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:
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
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
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
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
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;
+}
+
static char block[BLOCKSIZE];
static unsigned long offset;
-static time_t archive_time;
static int tar_umask = 002;
-static int verbose;
-static const struct commit *commit;
-static size_t base_len;
/* writes out the whole block, but only if it is full */
static void write_if_needed(void)
return chksum;
}
-static int get_path_prefix(const struct strbuf *path, int maxlen)
+static size_t get_path_prefix(const char *path, size_t pathlen, size_t maxlen)
{
- int i = path->len;
+ size_t i = pathlen;
if (i > maxlen)
i = maxlen;
do {
i--;
- } while (i > 0 && path->buf[i] != '/');
+ } while (i > 0 && path[i] != '/');
return i;
}
-static void write_entry(const unsigned char *sha1, struct strbuf *path,
- unsigned int mode, void *buffer, unsigned long size)
+static int write_tar_entry(struct archiver_args *args,
+ const unsigned char *sha1, const char *path, size_t pathlen,
+ unsigned int mode, void *buffer, unsigned long size)
{
struct ustar_header header;
struct strbuf ext_header;
+ int err = 0;
memset(&header, 0, sizeof(header));
strbuf_init(&ext_header, 0);
mode = 0100666;
sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
} else {
- if (verbose)
- fprintf(stderr, "%.*s\n", (int)path->len, path->buf);
if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
*header.typeflag = TYPEFLAG_DIR;
mode = (mode | 0777) & ~tar_umask;
*header.typeflag = TYPEFLAG_REG;
mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
} else {
- error("unsupported file mode: 0%o (SHA1: %s)",
- mode, sha1_to_hex(sha1));
- return;
+ return error("unsupported file mode: 0%o (SHA1: %s)",
+ mode, sha1_to_hex(sha1));
}
- if (path->len > sizeof(header.name)) {
- int plen = get_path_prefix(path, sizeof(header.prefix));
- int rest = path->len - plen - 1;
+ if (pathlen > sizeof(header.name)) {
+ size_t plen = get_path_prefix(path, pathlen,
+ sizeof(header.prefix));
+ size_t rest = pathlen - plen - 1;
if (plen > 0 && rest <= sizeof(header.name)) {
- memcpy(header.prefix, path->buf, plen);
- memcpy(header.name, path->buf + plen + 1, rest);
+ memcpy(header.prefix, path, plen);
+ memcpy(header.name, path + plen + 1, rest);
} else {
sprintf(header.name, "%s.data",
sha1_to_hex(sha1));
strbuf_append_ext_header(&ext_header, "path",
- path->buf, path->len);
+ path, pathlen);
}
} else
- memcpy(header.name, path->buf, path->len);
+ memcpy(header.name, path, pathlen);
}
if (S_ISLNK(mode) && buffer) {
sprintf(header.mode, "%07o", mode & 07777);
sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
- sprintf(header.mtime, "%011lo", archive_time);
+ sprintf(header.mtime, "%011lo", args->time);
sprintf(header.uid, "%07o", 0);
sprintf(header.gid, "%07o", 0);
sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
if (ext_header.len > 0) {
- write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
+ err = write_tar_entry(args, sha1, NULL, 0, 0, ext_header.buf,
+ ext_header.len);
+ if (err)
+ return err;
}
strbuf_release(&ext_header);
write_blocked(&header, sizeof(header));
if (S_ISREG(mode) && buffer && size > 0)
write_blocked(buffer, size);
+ return err;
}
-static void write_global_extended_header(const unsigned char *sha1)
+static int write_global_extended_header(struct archiver_args *args)
{
+ const unsigned char *sha1 = args->commit_sha1;
struct strbuf ext_header;
+ int err;
strbuf_init(&ext_header, 0);
strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
- write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
+ err = write_tar_entry(args, NULL, NULL, 0, 0, ext_header.buf,
+ ext_header.len);
strbuf_release(&ext_header);
+ return err;
}
static int git_tar_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
-static int write_tar_entry(const unsigned char *sha1,
- const char *base, int baselen,
- const char *filename, unsigned mode, int stage)
-{
- static struct strbuf path = STRBUF_INIT;
- void *buffer;
- enum object_type type;
- unsigned long size;
-
- strbuf_reset(&path);
- strbuf_grow(&path, PATH_MAX);
- strbuf_add(&path, base, baselen);
- strbuf_addstr(&path, filename);
- if (is_archive_path_ignored(path.buf + base_len))
- return 0;
- if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
- strbuf_addch(&path, '/');
- buffer = NULL;
- size = 0;
- } else {
- buffer = sha1_file_to_archive(path.buf + base_len, sha1, mode,
- &type, &size, commit);
- if (!buffer)
- die("cannot read %s", sha1_to_hex(sha1));
- }
-
- write_entry(sha1, &path, mode, buffer, size);
- free(buffer);
-
- return READ_TREE_RECURSIVE;
-}
-
int write_tar_archive(struct archiver_args *args)
{
- int plen = args->base ? strlen(args->base) : 0;
+ int err = 0;
git_config(git_tar_config, NULL);
- archive_time = args->time;
- verbose = args->verbose;
- commit = args->commit;
- base_len = args->base ? strlen(args->base) : 0;
-
if (args->commit_sha1)
- write_global_extended_header(args->commit_sha1);
-
- if (args->base && plen > 0 && args->base[plen - 1] == '/') {
- char *base = xstrdup(args->base);
- int baselen = strlen(base);
-
- while (baselen > 0 && base[baselen - 1] == '/')
- base[--baselen] = '\0';
- write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
- free(base);
- }
- read_tree_recursive(args->tree, args->base, plen, 0,
- args->pathspec, write_tar_entry);
- write_trailer();
-
- return 0;
+ err = write_global_extended_header(args);
+ if (!err)
+ err = write_archive_entries(args, write_tar_entry);
+ if (!err)
+ write_trailer();
+ return err;
}
#include "builtin.h"
#include "archive.h"
-static int verbose;
static int zip_date;
static int zip_time;
-static const struct commit *commit;
-static size_t base_len;
static unsigned char *zip_dir;
static unsigned int zip_dir_size;
return buffer;
}
-static char *construct_path(const char *base, int baselen,
- const char *filename, int isdir, int *pathlen)
-{
- int filenamelen = strlen(filename);
- int len = baselen + filenamelen;
- char *path, *p;
-
- if (isdir)
- len++;
- p = path = xmalloc(len + 1);
-
- memcpy(p, base, baselen);
- p += baselen;
- memcpy(p, filename, filenamelen);
- p += filenamelen;
- if (isdir)
- *p++ = '/';
- *p = '\0';
-
- *pathlen = len;
-
- return path;
-}
-
-static int write_zip_entry(const unsigned char *sha1,
- const char *base, int baselen,
- const char *filename, unsigned mode, int stage)
+static int write_zip_entry(struct archiver_args *args,
+ const unsigned char *sha1, const char *path, size_t pathlen,
+ unsigned int mode, void *buffer, unsigned long size)
{
struct zip_local_header header;
struct zip_dir_header dirent;
unsigned long uncompressed_size;
unsigned long crc;
unsigned long direntsize;
- unsigned long size;
int method;
- int result = -1;
- int pathlen;
unsigned char *out;
- char *path;
- enum object_type type;
- void *buffer = NULL;
void *deflated = NULL;
crc = crc32(0, NULL, 0);
- path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen);
- if (is_archive_path_ignored(path + base_len))
- return 0;
- if (verbose)
- fprintf(stderr, "%s\n", path);
if (pathlen > 0xffff) {
- error("path too long (%d chars, SHA1: %s): %s", pathlen,
- sha1_to_hex(sha1), path);
- goto out;
+ return error("path too long (%d chars, SHA1: %s): %s",
+ (int)pathlen, sha1_to_hex(sha1), path);
}
if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
method = 0;
attr2 = 16;
- result = (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
out = NULL;
uncompressed_size = 0;
compressed_size = 0;
(mode & 0111) ? ((mode) << 16) : 0;
if (S_ISREG(mode) && zlib_compression_level != 0)
method = 8;
- result = 0;
- buffer = sha1_file_to_archive(path + base_len, sha1, mode,
- &type, &size, commit);
- if (!buffer)
- die("cannot read %s", sha1_to_hex(sha1));
crc = crc32(crc, buffer, size);
out = buffer;
uncompressed_size = size;
compressed_size = size;
} else {
- error("unsupported file mode: 0%o (SHA1: %s)", mode,
- sha1_to_hex(sha1));
- goto out;
+ return error("unsupported file mode: 0%o (SHA1: %s)", mode,
+ sha1_to_hex(sha1));
}
if (method == 8) {
zip_offset += compressed_size;
}
-out:
- free(buffer);
free(deflated);
- free(path);
- return result;
+ return 0;
}
static void write_zip_trailer(const unsigned char *sha1)
int write_zip_archive(struct archiver_args *args)
{
- int plen = strlen(args->base);
+ int err;
dos_time(&args->time, &zip_date, &zip_time);
zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);
zip_dir_size = ZIP_DIRECTORY_MIN_SIZE;
- verbose = args->verbose;
- commit = args->commit;
- base_len = args->base ? strlen(args->base) : 0;
-
- if (args->base && plen > 0 && args->base[plen - 1] == '/') {
- char *base = xstrdup(args->base);
- int baselen = strlen(base);
-
- while (baselen > 0 && base[baselen - 1] == '/')
- base[--baselen] = '\0';
- write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
- free(base);
- }
- read_tree_recursive(args->tree, args->base, plen, 0,
- args->pathspec, write_zip_entry);
- write_zip_trailer(args->commit_sha1);
-
- free(zip_dir);
- return 0;
-}
+ err = write_archive_entries(args, write_zip_entry);
+ if (!err)
+ write_zip_trailer(args->commit_sha1);
-void *parse_extra_zip_args(int argc, const char **argv)
-{
- for (; argc > 0; argc--, argv++) {
- const char *arg = argv[0];
+ free(zip_dir);
- if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0')
- zlib_compression_level = arg[1] - '0';
- else
- die("Unknown argument for zip format: %s", arg);
- }
- return NULL;
+ return err;
}
#include "cache.h"
#include "commit.h"
#include "attr.h"
+#include "archive.h"
static void format_subst(const struct commit *commit,
const char *src, size_t len,
free(to_free);
}
-static int convert_to_archive(const char *path,
- const void *src, size_t len,
- struct strbuf *buf,
- const struct commit *commit)
-{
- static struct git_attr *attr_export_subst;
- struct git_attr_check check[1];
-
- if (!commit)
- return 0;
-
- if (!attr_export_subst)
- attr_export_subst = git_attr("export-subst", 12);
-
- check[0].attr = attr_export_subst;
- if (git_checkattr(path, ARRAY_SIZE(check), check))
- return 0;
- if (!ATTR_TRUE(check[0].value))
- return 0;
-
- format_subst(commit, src, len, buf);
- return 1;
-}
-
-void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
- unsigned int mode, enum object_type *type,
- unsigned long *sizep,
- const struct commit *commit)
+static void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
+ unsigned int mode, enum object_type *type,
+ unsigned long *sizep, const struct commit *commit)
{
void *buffer;
strbuf_init(&buf, 0);
strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
convert_to_working_tree(path, buf.buf, buf.len, &buf);
- convert_to_archive(path, buf.buf, buf.len, &buf, commit);
+ if (commit)
+ format_subst(commit, buf.buf, buf.len, &buf);
buffer = strbuf_detach(&buf, &size);
*sizep = size;
}
return buffer;
}
-int is_archive_path_ignored(const char *path)
+static void setup_archive_check(struct git_attr_check *check)
{
static struct git_attr *attr_export_ignore;
- struct git_attr_check check[1];
+ static struct git_attr *attr_export_subst;
- if (!attr_export_ignore)
+ if (!attr_export_ignore) {
attr_export_ignore = git_attr("export-ignore", 13);
-
+ attr_export_subst = git_attr("export-subst", 12);
+ }
check[0].attr = attr_export_ignore;
- if (git_checkattr(path, ARRAY_SIZE(check), check))
- return 0;
- return ATTR_TRUE(check[0].value);
+ check[1].attr = attr_export_subst;
+}
+
+struct archiver_context {
+ struct archiver_args *args;
+ write_archive_entry_fn_t write_entry;
+};
+
+static int write_archive_entry(const unsigned char *sha1, const char *base,
+ int baselen, const char *filename, unsigned mode, int stage,
+ void *context)
+{
+ static struct strbuf path = STRBUF_INIT;
+ struct archiver_context *c = context;
+ struct archiver_args *args = c->args;
+ write_archive_entry_fn_t write_entry = c->write_entry;
+ struct git_attr_check check[2];
+ const char *path_without_prefix;
+ int convert = 0;
+ int err;
+ enum object_type type;
+ unsigned long size;
+ void *buffer;
+
+ strbuf_reset(&path);
+ strbuf_grow(&path, PATH_MAX);
+ strbuf_add(&path, base, baselen);
+ strbuf_addstr(&path, filename);
+ path_without_prefix = path.buf + args->baselen;
+
+ setup_archive_check(check);
+ if (!git_checkattr(path_without_prefix, ARRAY_SIZE(check), check)) {
+ if (ATTR_TRUE(check[0].value))
+ return 0;
+ convert = ATTR_TRUE(check[1].value);
+ }
+
+ if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
+ strbuf_addch(&path, '/');
+ if (args->verbose)
+ fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
+ err = write_entry(args, sha1, path.buf, path.len, mode, NULL, 0);
+ if (err)
+ return err;
+ return READ_TREE_RECURSIVE;
+ }
+
+ buffer = sha1_file_to_archive(path_without_prefix, sha1, mode,
+ &type, &size, convert ? args->commit : NULL);
+ if (!buffer)
+ return error("cannot read %s", sha1_to_hex(sha1));
+ if (args->verbose)
+ fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
+ err = write_entry(args, sha1, path.buf, path.len, mode, buffer, size);
+ free(buffer);
+ return err;
+}
+
+int write_archive_entries(struct archiver_args *args,
+ write_archive_entry_fn_t write_entry)
+{
+ struct archiver_context context;
+ int err;
+
+ if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
+ size_t len = args->baselen;
+
+ while (len > 1 && args->base[len - 2] == '/')
+ len--;
+ if (args->verbose)
+ fprintf(stderr, "%.*s\n", (int)len, args->base);
+ err = write_entry(args, args->tree->object.sha1, args->base,
+ len, 040777, NULL, 0);
+ if (err)
+ return err;
+ }
+
+ context.args = args;
+ context.write_entry = write_entry;
+
+ err = read_tree_recursive(args->tree, args->base, args->baselen, 0,
+ args->pathspec, write_archive_entry, &context);
+ if (err == READ_TREE_RECURSIVE)
+ err = 0;
+ return err;
}
struct archiver_args {
const char *base;
+ size_t baselen;
struct tree *tree;
const unsigned char *commit_sha1;
const struct commit *commit;
time_t time;
const char **pathspec;
unsigned int verbose : 1;
- void *extra;
};
typedef int (*write_archive_fn_t)(struct archiver_args *);
-typedef void *(*parse_extra_args_fn_t)(int argc, const char **argv);
+typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size);
struct archiver {
const char *name;
- struct archiver_args args;
write_archive_fn_t write_archive;
- parse_extra_args_fn_t parse_extra;
+ unsigned int flags;
};
-extern int parse_archive_args(int argc,
- const char **argv,
- struct archiver *ar);
+extern int parse_archive_args(int argc, const char **argv, const struct archiver **ar, struct archiver_args *args);
extern void parse_treeish_arg(const char **treeish,
struct archiver_args *ar_args,
*/
extern int write_tar_archive(struct archiver_args *);
extern int write_zip_archive(struct archiver_args *);
-extern void *parse_extra_zip_args(int argc, const char **argv);
-extern void *sha1_file_to_archive(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size, const struct commit *commit);
-extern int is_archive_path_ignored(const char *path);
+extern int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry);
#endif /* ARCHIVE_H */
{
struct attr_stack *elem, *info;
int len;
- char pathbuf[PATH_MAX];
+ struct strbuf pathbuf;
+
+ strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE));
/*
* At the bottom of the attribute stack is the built-in
len = strlen(attr_stack->origin);
if (dirlen <= len)
break;
- memcpy(pathbuf, path, dirlen);
- memcpy(pathbuf + dirlen, "/", 2);
- cp = strchr(pathbuf + len + 1, '/');
+ strbuf_reset(&pathbuf);
+ strbuf_add(&pathbuf, path, dirlen);
+ strbuf_addch(&pathbuf, '/');
+ cp = strchr(pathbuf.buf + len + 1, '/');
strcpy(cp + 1, GITATTRIBUTES_FILE);
- elem = read_attr(pathbuf, 0);
+ elem = read_attr(pathbuf.buf, 0);
*cp = '\0';
- elem->origin = strdup(pathbuf);
+ elem->origin = strdup(pathbuf.buf);
elem->prev = attr_stack;
attr_stack = elem;
debug_push(elem);
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"));
}
#include "parse-options.h"
static const char * const builtin_add_usage[] = {
- "git-add [options] [--] <filepattern>...",
+ "git add [options] [--] <filepattern>...",
NULL
};
static int patch_interactive = 0, add_interactive = 0;
static int line_termination = '\n';
static unsigned long p_context = ULONG_MAX;
static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|fix|error|error-all>] <patch>...";
+"git apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|fix|error|error-all>] <patch>...";
static enum ws_error_action {
nowarn_ws_error,
#include "attr.h"
static const char archive_usage[] = \
-"git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]";
+"git archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]";
-static struct archiver_desc
-{
- const char *name;
- write_archive_fn_t write_archive;
- parse_extra_args_fn_t parse_extra;
-} archivers[] = {
- { "tar", write_tar_archive, NULL },
- { "zip", write_zip_archive, parse_extra_zip_args },
+#define USES_ZLIB_COMPRESSION 1
+
+const struct archiver archivers[] = {
+ { "tar", write_tar_archive },
+ { "zip", write_zip_archive, USES_ZLIB_COMPRESSION },
};
static int run_remote_archiver(const char *remote, int argc,
return !!rv;
}
-static int init_archiver(const char *name, struct archiver *ar)
+static const struct archiver *lookup_archiver(const char *name)
{
- int rv = -1, i;
+ int i;
for (i = 0; i < ARRAY_SIZE(archivers); i++) {
- if (!strcmp(name, archivers[i].name)) {
- memset(ar, 0, sizeof(*ar));
- ar->name = archivers[i].name;
- ar->write_archive = archivers[i].write_archive;
- ar->parse_extra = archivers[i].parse_extra;
- rv = 0;
- break;
- }
+ if (!strcmp(name, archivers[i].name))
+ return &archivers[i];
}
- return rv;
+ return NULL;
}
void parse_pathspec_arg(const char **pathspec, struct archiver_args *ar_args)
ar_args->time = archive_time;
}
-int parse_archive_args(int argc, const char **argv, struct archiver *ar)
+int parse_archive_args(int argc, const char **argv, const struct archiver **ar,
+ struct archiver_args *args)
{
- const char *extra_argv[MAX_EXTRA_ARGS];
- int extra_argc = 0;
const char *format = "tar";
const char *base = "";
+ int compression_level = -1;
int verbose = 0;
int i;
i++;
break;
}
- if (arg[0] == '-') {
- if (extra_argc > MAX_EXTRA_ARGS - 1)
- die("Too many extra options");
- extra_argv[extra_argc++] = arg;
+ if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') {
+ compression_level = arg[1] - '0';
continue;
}
+ if (arg[0] == '-')
+ die("Unknown argument: %s", arg);
break;
}
/* We need at least one parameter -- tree-ish */
if (argc - 1 < i)
usage(archive_usage);
- if (init_archiver(format, ar) < 0)
+ *ar = lookup_archiver(format);
+ if (!*ar)
die("Unknown archive format '%s'", format);
- if (extra_argc) {
- if (!ar->parse_extra)
- die("'%s' format does not handle %s",
- ar->name, extra_argv[0]);
- ar->args.extra = ar->parse_extra(extra_argc, extra_argv);
+ if (compression_level != -1) {
+ if ((*ar)->flags & USES_ZLIB_COMPRESSION)
+ zlib_compression_level = compression_level;
+ else {
+ die("Argument not supported for format '%s': -%d",
+ format, compression_level);
+ }
}
- ar->args.verbose = verbose;
- ar->args.base = base;
+ args->verbose = verbose;
+ args->base = base;
+ args->baselen = strlen(base);
return i;
}
int cmd_archive(int argc, const char **argv, const char *prefix)
{
- struct archiver ar;
+ const struct archiver *ar = NULL;
+ struct archiver_args args;
int tree_idx;
const char *remote = NULL;
setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
- memset(&ar, 0, sizeof(ar));
- tree_idx = parse_archive_args(argc, argv, &ar);
+ tree_idx = parse_archive_args(argc, argv, &ar, &args);
if (prefix == NULL)
prefix = setup_git_directory();
argv += tree_idx;
- parse_treeish_arg(argv, &ar.args, prefix);
- parse_pathspec_arg(argv + 1, &ar.args);
+ parse_treeish_arg(argv, &args, prefix);
+ parse_pathspec_arg(argv + 1, &args);
- return ar.write_archive(&ar.args);
+ return ar->write_archive(&args);
}
#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;
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;
* 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;
}
else
*file = o->file;
- return file->ptr;
}
/*
*/
char guilty;
+ /* true if the entry has been scanned for copies in the current parent
+ */
+ char scanned;
+
/* the line number of the first line of this group in the
* suspect's file; internally all line numbers are 0 based.
*/
struct scoreboard {
/* the final commit (i.e. where we started digging from) */
struct commit *final;
-
+ struct rev_info *revs;
const char *path;
/*
while (made_progress) {
made_progress = 0;
for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target))
+ if (e->guilty || !same_suspect(e->suspect, target) ||
+ ent_score(sb, e) < blame_move_score)
continue;
find_copy_in_blob(sb, e, parent, split, &file_p);
if (split[1].suspect &&
*/
static struct blame_list *setup_blame_list(struct scoreboard *sb,
struct origin *target,
+ int min_score,
int *num_ents_p)
{
struct blame_entry *e;
struct blame_list *blame_list = NULL;
for (e = sb->ent, num_ents = 0; e; e = e->next)
- if (!e->guilty && same_suspect(e->suspect, target))
+ if (!e->scanned && !e->guilty &&
+ same_suspect(e->suspect, target) &&
+ min_score < ent_score(sb, e))
num_ents++;
if (num_ents) {
blame_list = xcalloc(num_ents, sizeof(struct blame_list));
for (e = sb->ent, i = 0; e; e = e->next)
- if (!e->guilty && same_suspect(e->suspect, target))
+ if (!e->scanned && !e->guilty &&
+ same_suspect(e->suspect, target) &&
+ min_score < ent_score(sb, e))
blame_list[i++].ent = e;
}
*num_ents_p = num_ents;
return blame_list;
}
+/*
+ * Reset the scanned status on all entries.
+ */
+static void reset_scanned_flag(struct scoreboard *sb)
+{
+ struct blame_entry *e;
+ for (e = sb->ent; e; e = e->next)
+ e->scanned = 0;
+}
+
/*
* For lines target is suspected for, see if we can find code movement
* across file boundary from the parent commit. porigin is the path
struct blame_list *blame_list;
int num_ents;
- blame_list = setup_blame_list(sb, target, &num_ents);
+ blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
if (!blame_list)
return 1; /* nothing remains for this target */
split_blame(sb, split, blame_list[j].ent);
made_progress = 1;
}
+ else
+ blame_list[j].ent->scanned = 1;
decref_split(split);
}
free(blame_list);
if (!made_progress)
break;
- blame_list = setup_blame_list(sb, target, &num_ents);
+ blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
if (!blame_list) {
retval = 1;
break;
}
}
+ reset_scanned_flag(sb);
diff_flush(&diff_opts);
diff_tree_release_paths(&diff_opts);
return retval;
}
}
-#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;
-
- memset(parent_origin, 0, sizeof(parent_origin));
+ struct commit_list *sg;
+ struct origin *sg_buf[MAXSG];
+ struct origin *porigin, **sg_origin = sg_buf;
+
+ 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++) {
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;
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))
* 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))
* 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);
}
/*
* 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;
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;
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;
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;
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;
-
- 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);
+ dashdash_pos = 0;
+
+ 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);
- }
- 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;
+
+ if (!strcmp(ctx.argv[0], "--reverse")) {
+ ctx.argv[0] = "--children";
+ reverse = 1;
}
- 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;
*
* 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];
-
- /* 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 */
+ argv[argc - 1] = "--";
- 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) {
/*
if (!incremental)
setup_pager();
- assign_blame(&sb, &revs, opt);
+ assign_blame(&sb, opt);
if (incremental)
return 0;
#include "branch.h"
static const char * const builtin_branch_usage[] = {
- "git-branch [options] [-r | -a] [--merged | --no-merged]",
- "git-branch [options] [-l] [-f] <branchname> [<start-point>]",
- "git-branch [options] [-r] (-d | -D) <branchname>",
- "git-branch [options] (-m | -M) [<oldbranch>] <newbranch>",
+ "git branch [options] [-r | -a] [--merged | --no-merged]",
+ "git branch [options] [-l] [-f] <branchname> [<start-point>]",
+ "git branch [options] [-r] (-d | -D) <branchname>",
+ "git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
NULL
};
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)
{
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;
}
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)
{
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)) {
&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,
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;
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),
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(),
};
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)
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);
}
static const char * const cat_file_usage[] = {
- "git-cat-file [-t|-s|-e|-p|<type>] <sha1>",
- "git-cat-file [--batch|--batch-check] < <list_of_sha1s>",
+ "git cat-file [-t|-s|-e|-p|<type>] <sha1>",
+ "git cat-file [--batch|--batch-check] < <list_of_sha1s>",
NULL
};
#include "quote.h"
static const char check_attr_usage[] =
-"git-check-attr attr... [--] pathname...";
+"git check-attr attr... [--] pathname...";
int cmd_check_attr(int argc, const char **argv, const char *prefix)
{
}
static const char checkout_cache_usage[] =
-"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=<string>] [--temp] [--] <file>...";
+"git checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=<string>] [--temp] [--] <file>...";
static struct lock_file lock_file;
}
static int update_some(const unsigned char *sha1, const char *base, int baselen,
- const char *pathname, unsigned mode, int stage)
+ const char *pathname, unsigned mode, int stage, void *context)
{
int len;
struct cache_entry *ce;
static int read_tree_some(struct tree *tree, const char **pathspec)
{
- read_tree_recursive(tree, "", 0, 0, pathspec, update_some);
+ read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
/* update the index with the given tree's info
* for all args, expanding wildcards, and exit
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)
- 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))
+ if (!format_tracking_info(branch, &sb))
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,
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)
static int force = -1; /* unset */
static const char *const builtin_clean_usage[] = {
- "git-clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...",
+ "git clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...",
NULL
};
*
*/
static const char * const builtin_clone_usage[] = {
- "git-clone [options] [--] <repo> [<dir>]",
+ "git clone [options] [--] <repo> [<dir>]",
NULL
};
"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
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;
}
#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[] = {
- "git-commit [options] [--] <filepattern>...",
+ "git commit [options] [--] <filepattern>...",
NULL
};
static const char * const builtin_status_usage[] = {
- "git-status [options] [--] <filepattern>...",
+ "git status [options] [--] <filepattern>...",
NULL
};
#include "color.h"
static const char git_config_set_usage[] =
-"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int | --bool-or-int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty]";
+"git config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int | --bool-or-int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty]";
static char *key;
static regex_t *key_regexp;
}
static char const * const count_objects_usage[] = {
- "git-count-objects [-v]",
+ "git count-objects [-v]",
NULL
};
#define MAX_TAGS (FLAG_BITS - 1)
static const char * const describe_usage[] = {
- "git-describe [options] <committish>*",
+ "git describe [options] <committish>*",
NULL
};
static int longformat;
static int abbrev = DEFAULT_ABBREV;
static int max_candidates = 10;
-const char *pattern = NULL;
+static const char *pattern;
static int always;
struct commit_name {
#include "builtin.h"
static const char diff_files_usage[] =
-"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
+"git diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
COMMON_DIFF_OPTIONS_HELP;
int cmd_diff_files(int argc, const char **argv, const char *prefix)
#include "builtin.h"
static const char diff_cache_usage[] =
-"git-diff-index [-m] [--cached] "
+"git diff-index [-m] [--cached] "
"[<common diff options>] <tree-ish> [<path>...]"
COMMON_DIFF_OPTIONS_HELP;
}
static const char diff_tree_usage[] =
-"git-diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
" -r diff recursively\n"
" --root include the initial commit as diff against /dev/null\n"
};
static const char builtin_diff_usage[] =
-"git-diff <options> <rev>{0,2} -- <path>*";
+"git diff <options> <rev>{0,2} -- <path>*";
static void stuff_change(struct diff_options *opt,
unsigned old_mode, unsigned new_mode,
#include "parse-options.h"
static const char *fast_export_usage[] = {
- "git-fast-export [rev-list-opts]",
+ "git fast-export [rev-list-opts]",
NULL
};
};
static const char fetch_pack_usage[] =
-"git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
+"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
#define COMPLETE (1U << 0)
#define COMMON (1U << 1)
#include "parse-options.h"
static const char * const builtin_fetch_usage[] = {
- "git-fetch [options] [<repository> <refspec>...]",
+ "git fetch [options] [<repository> <refspec>...]",
NULL
};
#include "tag.h"
static const char *fmt_merge_msg_usage =
- "git-fmt-merge-msg [--log] [--no-log] [--file <file>]";
+ "git fmt-merge-msg [--log] [--no-log] [--file <file>]";
static int merge_summary;
}
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;
}
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);
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;
- git_config(fmt_merge_msg_config, NULL);
-
- while (argc > 1) {
- if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
- merge_summary = 1;
- else if (!strcmp(argv[1], "--no-log")
- || !strcmp(argv[1], "--no-summary"))
- merge_summary = 0;
- else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
- if (argc < 3)
- die ("Which file?");
- if (!strcmp(argv[2], "-"))
- in = stdin;
- else {
- fclose(in);
- in = fopen(argv[2], "r");
- if (!in)
- die("cannot open %s", argv[2]);
- }
- argc--; argv++;
- } else
- break;
- argc--; argv++;
- }
-
- if (argc > 1)
- usage(fmt_merge_msg_usage);
-
/* get current branch */
current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
if (!current_branch)
if (!prefixcmp(current_branch, "refs/heads/"))
current_branch += 11;
- while (fgets(line, sizeof(line), in)) {
+ /* 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++;
- if (line[0] == 0)
- continue;
- if (handle_line(line))
- die ("Error in line %d: %s", i, line);
+ p[len] = 0;
+ if (handle_line(p))
+ die ("Error in line %d: %.*s", i, len, p);
}
- printf("Merge ");
+ strbuf_addstr(out, "Merge ");
for (i = 0; i < srcs.nr; i++) {
struct src_data *src_data = srcs.payload[i];
const char *subsep = "";
- printf(sep);
+ strbuf_addstr(out, sep);
sep = "; ";
if (src_data->head_status == 1) {
- printf(srcs.list[i]);
+ strbuf_addstr(out, srcs.list[i]);
continue;
}
if (src_data->head_status == 3) {
subsep = ", ";
- printf("HEAD");
+ strbuf_addstr(out, "HEAD");
}
if (src_data->branch.nr) {
- printf(subsep);
+ strbuf_addstr(out, subsep);
subsep = ", ";
- print_joined("branch ", "branches ", &src_data->branch);
+ print_joined("branch ", "branches ", &src_data->branch,
+ out);
}
if (src_data->r_branch.nr) {
- printf(subsep);
+ strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("remote branch ", "remote branches ",
- &src_data->r_branch);
+ &src_data->r_branch, out);
}
if (src_data->tag.nr) {
- printf(subsep);
+ strbuf_addstr(out, subsep);
subsep = ", ";
- print_joined("tag ", "tags ", &src_data->tag);
+ print_joined("tag ", "tags ", &src_data->tag, out);
}
if (src_data->generic.nr) {
- printf(subsep);
- print_joined("commit ", "commits ", &src_data->generic);
+ strbuf_addstr(out, subsep);
+ print_joined("commit ", "commits ", &src_data->generic,
+ out);
}
if (strcmp(".", srcs.list[i]))
- printf(" of %s", srcs.list[i]);
+ strbuf_addf(out, " of %s", srcs.list[i]);
}
if (!strcmp("master", current_branch))
- putchar('\n');
+ strbuf_addch(out, '\n');
else
- printf(" into %s\n", current_branch);
+ 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, prefix);
+ 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);
+ 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) {
+ if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
+ merge_summary = 1;
+ else if (!strcmp(argv[1], "--no-log")
+ || !strcmp(argv[1], "--no-summary"))
+ merge_summary = 0;
+ else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
+ if (argc < 3)
+ die ("Which file?");
+ if (!strcmp(argv[2], "-"))
+ in = stdin;
+ else {
+ fclose(in);
+ in = fopen(argv[2], "r");
+ if (!in)
+ die("cannot open %s", argv[2]);
+ }
+ argc--; argv++;
+ } else
+ break;
+ argc--; argv++;
+ }
+
+ if (argc > 1)
+ usage(fmt_merge_msg_usage);
- /* 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;
}
}
static char const * const for_each_ref_usage[] = {
- "git-for-each-ref [options] [<pattern>]",
+ "git for-each-ref [options] [<pattern>]",
NULL
};
}
static char const * const fsck_usage[] = {
- "git-fsck [options] [<object>...]",
+ "git fsck [options] [<object>...]",
NULL
};
#define FAILED_RUN "failed to run %s"
static const char * const builtin_gc_usage[] = {
- "git-gc [options]",
+ "git gc [options]",
NULL
};
struct name_entry entry;
char *down;
int tn_len = strlen(tree_name);
- char *path_buf = xmalloc(PATH_MAX + tn_len + 100);
+ struct strbuf pathbuf;
+
+ strbuf_init(&pathbuf, PATH_MAX + tn_len);
if (tn_len) {
- tn_len = sprintf(path_buf, "%s:", tree_name);
- down = path_buf + tn_len;
- strcat(down, base);
- }
- else {
- down = path_buf;
- strcpy(down, base);
+ strbuf_add(&pathbuf, tree_name, tn_len);
+ strbuf_addch(&pathbuf, ':');
+ tn_len = pathbuf.len;
}
- len = strlen(path_buf);
+ strbuf_addstr(&pathbuf, base);
+ len = pathbuf.len;
while (tree_entry(tree, &entry)) {
- strcpy(path_buf + len, entry.path);
+ int te_len = tree_entry_len(entry.path, entry.sha1);
+ pathbuf.len = len;
+ strbuf_add(&pathbuf, entry.path, te_len);
if (S_ISDIR(entry.mode))
/* Match "abc/" against pathspec to
* decide if we want to descend into "abc"
* directory.
*/
- strcpy(path_buf + len + tree_entry_len(entry.path, entry.sha1), "/");
+ strbuf_addch(&pathbuf, '/');
+ down = pathbuf.buf + tn_len;
if (!pathspec_matches(paths, down))
;
else if (S_ISREG(entry.mode))
- hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len);
+ hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
else if (S_ISDIR(entry.mode)) {
enum object_type type;
struct tree_desc sub;
free(data);
}
}
+ strbuf_release(&pathbuf);
return hit;
}
}
static const char builtin_grep_usage[] =
-"git-grep <option>* <rev>* [-e] <pattern> [<path>...]";
+"git grep <option>* <rev>* [-e] <pattern> [<path>...]";
static const char emsg_invalid_context_len[] =
"%s: invalid context length argument";
if (!template_dir)
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
- if (!template_dir) {
- /*
- * if the hard-coded template is relative, it is
- * interpreted relative to the exec_dir
- */
- template_dir = DEFAULT_GIT_TEMPLATE_DIR;
- if (!is_absolute_path(template_dir)) {
- struct strbuf d = STRBUF_INIT;
- strbuf_addf(&d, "%s/%s", git_exec_path(), template_dir);
- template_dir = strbuf_detach(&d, NULL);
- }
- }
+ if (!template_dir)
+ template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
strcpy(template_path, template_dir);
template_len = strlen(template_path);
if (template_path[template_len-1] != '/') {
}
static const char init_db_usage[] =
-"git-init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]]";
+"git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]]";
/*
* If you want to, you can share the DB area with any number of branches.
static int show_tree_object(const unsigned char *sha1,
const char *base, int baselen,
- const char *pathname, unsigned mode, int stage)
+ const char *pathname, unsigned mode, int stage, void *context)
{
printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
return 0;
name,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
- show_tree_object);
+ show_tree_object, NULL);
break;
case OBJ_COMMIT:
rev.pending.nr = rev.pending.alloc = 0;
}
static const char cherry_usage[] =
-"git-cherry [-v] <upstream> [<head>] [<limit>]";
+"git cherry [-v] <upstream> [<head>] [<limit>]";
int cmd_cherry(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
}
static const char ls_files_usage[] =
- "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
+ "git ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
"[ --exclude-per-directory=<filename> ] [--exclude-standard] "
"[--full-name] [--abbrev] [--] [<file>]*";
#include "remote.h"
static const char ls_remote_usage[] =
-"git-ls-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>";
+"git ls-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>";
/*
* Is there one among the list of patterns that match the tail part
static const char *ls_tree_prefix;
static const char ls_tree_usage[] =
- "git-ls-tree [-d] [-r] [-t] [-l] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]";
+ "git ls-tree [-d] [-r] [-t] [-l] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]";
static int show_recursive(const char *base, int baselen, const char *pathname)
{
}
static int show_tree(const unsigned char *sha1, const char *base, int baselen,
- const char *pathname, unsigned mode, int stage)
+ const char *pathname, unsigned mode, int stage, void *context)
{
int retval = 0;
const char *type = blob_type;
tree = parse_tree_indirect(sha1);
if (!tree)
die("not a tree object");
- read_tree_recursive(tree, "", 0, 0, pathspec, show_tree);
+ read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL);
return 0;
}
#include "cache.h"
#include "builtin.h"
#include "utf8.h"
+#include "strbuf.h"
static FILE *cmitmsg, *patchfile, *fin, *fout;
static int keep_subject;
static const char *metainfo_charset;
-static char line[1000];
-static char name[1000];
-static char email[1000];
+static struct strbuf line = STRBUF_INIT;
+static struct strbuf name = STRBUF_INIT;
+static struct strbuf email = STRBUF_INIT;
static enum {
TE_DONTCARE, TE_QP, TE_BASE64,
TYPE_TEXT, TYPE_OTHER,
} message_type;
-static char charset[256];
+static struct strbuf charset = STRBUF_INIT;
static int patch_lines;
-static char **p_hdr_data, **s_hdr_data;
+static struct strbuf **p_hdr_data, **s_hdr_data;
#define MAX_HDR_PARSED 10
#define MAX_BOUNDARIES 5
-static char *sanity_check(char *name, char *email)
+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
{
- int len = strlen(name);
- if (len < 3 || len > 60)
- return email;
- if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>'))
- return email;
- return name;
+ struct strbuf *src = name;
+ if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
+ strchr(name->buf, '<') || strchr(name->buf, '>'))
+ src = email;
+ else if (name == out)
+ return;
+ strbuf_reset(out);
+ strbuf_addbuf(out, src);
}
-static int bogus_from(char *line)
+static void parse_bogus_from(const struct strbuf *line)
{
/* John Doe <johndoe> */
- char *bra, *ket, *dst, *cp;
+ char *bra, *ket;
/* This is fallback, so do not bother if we already have an
* e-mail address.
*/
- if (*email)
- return 0;
+ if (email.len)
+ return;
- bra = strchr(line, '<');
+ bra = strchr(line->buf, '<');
if (!bra)
- return 0;
+ return;
ket = strchr(bra, '>');
if (!ket)
- return 0;
+ return;
- for (dst = email, cp = bra+1; cp < ket; )
- *dst++ = *cp++;
- *dst = 0;
- for (cp = line; isspace(*cp); cp++)
- ;
- for (bra--; isspace(*bra); bra--)
- *bra = 0;
- cp = sanity_check(cp, email);
- strcpy(name, cp);
- return 1;
+ strbuf_reset(&email);
+ strbuf_add(&email, bra + 1, ket - bra - 1);
+
+ strbuf_reset(&name);
+ strbuf_add(&name, line->buf, bra - line->buf);
+ strbuf_trim(&name);
+ get_sane_name(&name, &name, &email);
}
-static int handle_from(char *in_line)
+static void handle_from(const struct strbuf *from)
{
- char line[1000];
char *at;
- char *dst;
+ size_t el;
+ struct strbuf f;
- strcpy(line, in_line);
- at = strchr(line, '@');
- if (!at)
- return bogus_from(line);
+ strbuf_init(&f, from->len);
+ strbuf_addbuf(&f, from);
+
+ at = strchr(f.buf, '@');
+ if (!at) {
+ parse_bogus_from(from);
+ return;
+ }
/*
* If we already have one email, don't take any confusing lines
*/
- if (*email && strchr(at+1, '@'))
- return 0;
+ if (email.len && strchr(at + 1, '@')) {
+ strbuf_release(&f);
+ return;
+ }
/* Pick up the string around '@', possibly delimited with <>
- * pair; that is the email part. White them out while copying.
+ * pair; that is the email part.
*/
- while (at > line) {
+ while (at > f.buf) {
char c = at[-1];
if (isspace(c))
break;
}
at--;
}
- dst = email;
- for (;;) {
- unsigned char c = *at;
- if (!c || c == '>' || isspace(c)) {
- if (c == '>')
- *at = ' ';
- break;
- }
- *at++ = ' ';
- *dst++ = c;
- }
- *dst++ = 0;
+ el = strcspn(at, " \n\t\r\v\f>");
+ strbuf_reset(&email);
+ strbuf_add(&email, at, el);
+ strbuf_remove(&f, at - f.buf, el + 1);
/* The remainder is name. It could be "John Doe <john.doe@xz>"
- * or "john.doe@xz (John Doe)", but we have whited out the
+ * or "john.doe@xz (John Doe)", but we have removed the
* email part, so trim from both ends, possibly removing
* the () pair at the end.
*/
- at = line + strlen(line);
- while (at > line) {
- unsigned char c = *--at;
- if (!isspace(c)) {
- at[(c == ')') ? 0 : 1] = 0;
- break;
- }
- }
+ strbuf_trim(&f);
+ if (f.buf[0] == '(')
+ strbuf_remove(&name, 0, 1);
+ if (f.len && f.buf[f.len - 1] == ')')
+ strbuf_setlen(&f, f.len - 1);
- at = line;
- for (;;) {
- unsigned char c = *at;
- if (!c || !isspace(c)) {
- if (c == '(')
- at++;
- break;
- }
- at++;
- }
- at = sanity_check(at, email);
- strcpy(name, at);
- return 1;
+ get_sane_name(&name, &f, &email);
+ strbuf_release(&f);
}
-static int handle_header(char *line, char *data, int ofs)
+static void handle_header(struct strbuf **out, const struct strbuf *line)
{
- if (!line || !data)
- return 1;
+ if (!*out) {
+ *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(*out, line->len);
+ } else
+ strbuf_reset(*out);
- strcpy(data, line+ofs);
-
- return 0;
+ strbuf_addbuf(*out, line);
}
/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
* case insensitively.
*/
-static int slurp_attr(const char *line, const char *name, char *attr)
+static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
{
const char *ends, *ap = strcasestr(line, name);
size_t sz;
if (!ap) {
- *attr = 0;
+ strbuf_setlen(attr, 0);
return 0;
}
ap += strlen(name);
else
ends = "; \t";
sz = strcspn(ap, ends);
- memcpy(attr, ap, sz);
- attr[sz] = 0;
+ strbuf_add(attr, ap, sz);
return 1;
}
-struct content_type {
- char *boundary;
- int boundary_len;
-};
-
-static struct content_type content[MAX_BOUNDARIES];
+static struct strbuf *content[MAX_BOUNDARIES];
-static struct content_type *content_top = content;
+static struct strbuf **content_top = content;
-static int handle_content_type(char *line)
+static void handle_content_type(struct strbuf *line)
{
- char boundary[256];
+ struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+ strbuf_init(boundary, line->len);
- if (strcasestr(line, "text/") == NULL)
+ if (!strcasestr(line->buf, "text/"))
message_type = TYPE_OTHER;
- if (slurp_attr(line, "boundary=", boundary + 2)) {
- memcpy(boundary, "--", 2);
+ if (slurp_attr(line->buf, "boundary=", boundary)) {
+ strbuf_insert(boundary, 0, "--", 2);
if (content_top++ >= &content[MAX_BOUNDARIES]) {
fprintf(stderr, "Too many boundaries to handle\n");
exit(1);
}
- content_top->boundary_len = strlen(boundary);
- content_top->boundary = xmalloc(content_top->boundary_len+1);
- strcpy(content_top->boundary, boundary);
+ *content_top = boundary;
+ boundary = NULL;
}
- if (slurp_attr(line, "charset=", charset)) {
- int i, c;
- for (i = 0; (c = charset[i]) != 0; i++)
- charset[i] = tolower(c);
+ if (slurp_attr(line->buf, "charset=", &charset))
+ strbuf_tolower(&charset);
+
+ if (boundary) {
+ strbuf_release(boundary);
+ free(boundary);
}
- return 0;
}
-static int handle_content_transfer_encoding(char *line)
+static void handle_content_transfer_encoding(const struct strbuf *line)
{
- if (strcasestr(line, "base64"))
+ if (strcasestr(line->buf, "base64"))
transfer_encoding = TE_BASE64;
- else if (strcasestr(line, "quoted-printable"))
+ else if (strcasestr(line->buf, "quoted-printable"))
transfer_encoding = TE_QP;
else
transfer_encoding = TE_DONTCARE;
- return 0;
}
-static int is_multipart_boundary(const char *line)
+static int is_multipart_boundary(const struct strbuf *line)
{
- return (!memcmp(line, content_top->boundary, content_top->boundary_len));
+ return !strbuf_cmp(line, *content_top);
}
-static int eatspace(char *line)
+static void cleanup_subject(struct strbuf *subject)
{
- int len = strlen(line);
- while (len > 0 && isspace(line[len-1]))
- line[--len] = 0;
- return len;
-}
-
-static char *cleanup_subject(char *subject)
-{
- for (;;) {
- char *p;
- int len, remove;
- switch (*subject) {
+ char *pos;
+ size_t remove;
+ while (subject->len) {
+ switch (*subject->buf) {
case 'r': case 'R':
- if (!memcmp("e:", subject+1, 2)) {
- subject += 3;
+ if (subject->len <= 3)
+ break;
+ if (!memcmp(subject->buf + 1, "e:", 2)) {
+ strbuf_remove(subject, 0, 3);
continue;
}
break;
case ' ': case '\t': case ':':
- subject++;
+ strbuf_remove(subject, 0, 1);
continue;
-
case '[':
- p = strchr(subject, ']');
- if (!p) {
- subject++;
- continue;
- }
- len = strlen(p);
- remove = p - subject;
- if (remove <= len *2) {
- subject = p+1;
- continue;
- }
+ if ((pos = strchr(subject->buf, ']'))) {
+ remove = pos - subject->buf + 1;
+ /* Don't remove too much. */
+ if (remove <= (subject->len - remove + 1) * 2) {
+ strbuf_remove(subject, 0, remove);
+ continue;
+ }
+ } else
+ strbuf_remove(subject, 0, 1);
break;
}
- eatspace(subject);
- return subject;
+ strbuf_trim(subject);
+ return;
}
}
-static void cleanup_space(char *buf)
+static void cleanup_space(struct strbuf *sb)
{
- unsigned char c;
- while ((c = *buf) != 0) {
- buf++;
- if (isspace(c)) {
- buf[-1] = ' ';
- c = *buf;
- while (isspace(c)) {
- int len = strlen(buf);
- memmove(buf, buf+1, len);
- c = *buf;
- }
+ size_t pos, cnt;
+ for (pos = 0; pos < sb->len; pos++) {
+ if (isspace(sb->buf[pos])) {
+ sb->buf[pos] = ' ';
+ for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
+ strbuf_remove(sb, pos + 1, cnt);
}
}
}
-static void decode_header(char *it, unsigned itsize);
+static void decode_header(struct strbuf *line);
static const char *header[MAX_HDR_PARSED] = {
"From","Subject","Date",
};
-static int check_header(char *line, unsigned linesize, char **hdr_data, int overwrite)
+static inline int cmp_header(const struct strbuf *line, const char *hdr)
{
- int i;
+ int len = strlen(hdr);
+ return !strncasecmp(line->buf, hdr, len) && line->len > len &&
+ line->buf[len] == ':' && isspace(line->buf[len + 1]);
+}
+static int check_header(const struct strbuf *line,
+ struct strbuf *hdr_data[], int overwrite)
+{
+ int i, ret = 0, len;
+ struct strbuf sb = STRBUF_INIT;
/* search for the interesting parts */
for (i = 0; header[i]; i++) {
int len = strlen(header[i]);
- if ((!hdr_data[i] || overwrite) &&
- !strncasecmp(line, header[i], len) &&
- line[len] == ':' && isspace(line[len + 1])) {
+ if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
/* Unwrap inline B and Q encoding, and optionally
* normalize the meta information to utf8.
*/
- decode_header(line + len + 2, linesize - len - 2);
- hdr_data[i] = xmalloc(1000 * sizeof(char));
- if (! handle_header(line, hdr_data[i], len + 2)) {
- return 1;
- }
+ strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
+ decode_header(&sb);
+ handle_header(&hdr_data[i], &sb);
+ ret = 1;
+ goto check_header_out;
}
}
/* Content stuff */
- if (!strncasecmp(line, "Content-Type", 12) &&
- line[12] == ':' && isspace(line[12 + 1])) {
- decode_header(line + 12 + 2, linesize - 12 - 2);
- if (! handle_content_type(line)) {
- return 1;
- }
- }
- if (!strncasecmp(line, "Content-Transfer-Encoding", 25) &&
- line[25] == ':' && isspace(line[25 + 1])) {
- decode_header(line + 25 + 2, linesize - 25 - 2);
- if (! handle_content_transfer_encoding(line)) {
- return 1;
- }
+ if (cmp_header(line, "Content-Type")) {
+ len = strlen("Content-Type: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(&sb);
+ strbuf_insert(&sb, 0, "Content-Type: ", len);
+ handle_content_type(&sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Content-Transfer-Encoding")) {
+ len = strlen("Content-Transfer-Encoding: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(&sb);
+ handle_content_transfer_encoding(&sb);
+ ret = 1;
+ goto check_header_out;
}
/* for inbody stuff */
- if (!memcmp(">From", line, 5) && isspace(line[5]))
- return 1;
- if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
+ if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) {
+ ret = 1; /* Should this return 0? */
+ goto check_header_out;
+ }
+ if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) {
for (i = 0; header[i]; i++) {
- if (!memcmp("Subject: ", header[i], 9)) {
- if (! handle_header(line, hdr_data[i], 0)) {
- return 1;
- }
+ if (!memcmp("Subject", header[i], 7)) {
+ handle_header(&hdr_data[i], line);
+ ret = 1;
+ goto check_header_out;
}
}
}
- /* no match */
- return 0;
+check_header_out:
+ strbuf_release(&sb);
+ return ret;
}
-static int is_rfc2822_header(char *line)
+static int is_rfc2822_header(const struct strbuf *line)
{
/*
* The section that defines the loosest possible
* ftext = %d33-57 / %59-126
*/
int ch;
- char *cp = line;
+ char *cp = line->buf;
/* Count mbox From headers as headers */
- if (!memcmp(line, "From ", 5) || !memcmp(line, ">From ", 6))
+ if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From "))
return 1;
while ((ch = *cp++)) {
if (ch == ':')
- return cp != line;
+ return 1;
if ((33 <= ch && ch <= 57) ||
(59 <= ch && ch <= 126))
continue;
return 0;
}
-/*
- * sz is size of 'line' buffer in bytes. Must be reasonably
- * long enough to hold one physical real-world e-mail line.
- */
-static int read_one_header_line(char *line, int sz, FILE *in)
+static int read_one_header_line(struct strbuf *line, FILE *in)
{
- int len;
-
- /*
- * We will read at most (sz-1) bytes and then potentially
- * re-add NUL after it. Accessing line[sz] after this is safe
- * and we can allow len to grow up to and including sz.
- */
- sz--;
-
/* Get the first part of the line. */
- if (!fgets(line, sz, in))
+ if (strbuf_getline(line, in, '\n'))
return 0;
/*
* Is it an empty line or not a valid rfc2822 header?
* If so, stop here, and return false ("not a header")
*/
- len = eatspace(line);
- if (!len || !is_rfc2822_header(line)) {
+ strbuf_rtrim(line);
+ if (!line->len || !is_rfc2822_header(line)) {
/* Re-add the newline */
- line[len] = '\n';
- line[len + 1] = '\0';
+ strbuf_addch(line, '\n');
return 0;
}
* Yuck, 2822 header "folding"
*/
for (;;) {
- int peek, addlen;
- static char continuation[1000];
+ int peek;
+ struct strbuf continuation = STRBUF_INIT;
peek = fgetc(in); ungetc(peek, in);
if (peek != ' ' && peek != '\t')
break;
- if (!fgets(continuation, sizeof(continuation), in))
+ if (strbuf_getline(&continuation, in, '\n'))
break;
- addlen = eatspace(continuation);
- if (len < sz - 1) {
- if (addlen >= sz - len)
- addlen = sz - len - 1;
- memcpy(line + len, continuation, addlen);
- line[len] = '\n';
- len += addlen;
- }
+ continuation.buf[0] = '\n';
+ strbuf_rtrim(&continuation);
+ strbuf_addbuf(line, &continuation);
}
- line[len] = 0;
return 1;
}
-static int decode_q_segment(char *in, char *ot, unsigned otsize, char *ep, int rfc2047)
+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
{
- char *otbegin = ot;
- char *otend = ot + otsize;
+ const char *in = q_seg->buf;
int c;
- while ((c = *in++) != 0 && (in <= ep)) {
- if (ot == otend) {
- *--ot = '\0';
- return -1;
- }
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, q_seg->len);
+
+ while ((c = *in++) != 0) {
if (c == '=') {
int d = *in++;
if (d == '\n' || !d)
break; /* drop trailing newline */
- *ot++ = ((hexval(d) << 4) | hexval(*in++));
+ strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
continue;
}
if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
c = 0x20;
- *ot++ = c;
+ strbuf_addch(out, c);
}
- *ot = 0;
- return (ot - otbegin);
+ return out;
}
-static int decode_b_segment(char *in, char *ot, unsigned otsize, char *ep)
+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
{
/* Decode in..ep, possibly in-place to ot */
int c, pos = 0, acc = 0;
- char *otbegin = ot;
- char *otend = ot + otsize;
+ const char *in = b_seg->buf;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, b_seg->len);
- while ((c = *in++) != 0 && (in <= ep)) {
- if (ot == otend) {
- *--ot = '\0';
- return -1;
- }
+ while ((c = *in++) != 0) {
if (c == '+')
c = 62;
else if (c == '/')
acc = (c << 2);
break;
case 1:
- *ot++ = (acc | (c >> 4));
+ strbuf_addch(out, (acc | (c >> 4)));
acc = (c & 15) << 4;
break;
case 2:
- *ot++ = (acc | (c >> 2));
+ strbuf_addch(out, (acc | (c >> 2)));
acc = (c & 3) << 6;
break;
case 3:
- *ot++ = (acc | c);
+ strbuf_addch(out, (acc | c));
acc = pos = 0;
break;
}
}
- *ot = 0;
- return (ot - otbegin);
+ return out;
}
/*
* Otherwise, we default to assuming it is Latin1 for historical
* reasons.
*/
-static const char *guess_charset(const char *line, const char *target_charset)
+static const char *guess_charset(const struct strbuf *line, const char *target_charset)
{
if (is_encoding_utf8(target_charset)) {
- if (is_utf8(line))
+ if (is_utf8(line->buf))
return NULL;
}
return "latin1";
}
-static void convert_to_utf8(char *line, unsigned linesize, const char *charset)
+static void convert_to_utf8(struct strbuf *line, const char *charset)
{
char *out;
if (!strcmp(metainfo_charset, charset))
return;
- out = reencode_string(line, metainfo_charset, charset);
+ out = reencode_string(line->buf, metainfo_charset, charset);
if (!out)
die("cannot convert from %s to %s\n",
charset, metainfo_charset);
- strlcpy(line, out, linesize);
- free(out);
+ strbuf_attach(line, out, strlen(out), strlen(out));
}
-static int decode_header_bq(char *it, unsigned itsize)
+static int decode_header_bq(struct strbuf *it)
{
- char *in, *out, *ep, *cp, *sp;
- char outbuf[1000];
+ char *in, *ep, *cp;
+ struct strbuf outbuf = STRBUF_INIT, *dec;
+ struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
int rfc2047 = 0;
- in = it;
- out = outbuf;
- while ((ep = strstr(in, "=?")) != NULL) {
- int sz, encoding;
- char charset_q[256], piecebuf[256];
+ in = it->buf;
+ while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
+ int encoding;
+ strbuf_reset(&charset_q);
+ strbuf_reset(&piecebuf);
rfc2047 = 1;
if (in != ep) {
- sz = ep - in;
- memcpy(out, in, sz);
- out += sz;
- in += sz;
+ strbuf_add(&outbuf, in, ep - in);
+ in = ep;
}
/* E.g.
* ep : "=?iso-2022-jp?B?GyR...?= foo"
* ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
*/
ep += 2;
- cp = strchr(ep, '?');
- if (!cp)
- return rfc2047; /* no munging */
- for (sp = ep; sp < cp; sp++)
- charset_q[sp - ep] = tolower(*sp);
- charset_q[cp - ep] = 0;
+
+ if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
+ goto decode_header_bq_out;
+
+ if (cp + 3 - it->buf > it->len)
+ goto decode_header_bq_out;
+ strbuf_add(&charset_q, ep, cp - ep);
+ strbuf_tolower(&charset_q);
+
encoding = cp[1];
if (!encoding || cp[2] != '?')
- return rfc2047; /* no munging */
+ goto decode_header_bq_out;
ep = strstr(cp + 3, "?=");
if (!ep)
- return rfc2047; /* no munging */
+ goto decode_header_bq_out;
+ strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
switch (tolower(encoding)) {
default:
- return rfc2047; /* no munging */
+ goto decode_header_bq_out;
case 'b':
- sz = decode_b_segment(cp + 3, piecebuf, sizeof(piecebuf), ep);
+ dec = decode_b_segment(&piecebuf);
break;
case 'q':
- sz = decode_q_segment(cp + 3, piecebuf, sizeof(piecebuf), ep, 1);
+ dec = decode_q_segment(&piecebuf, 1);
break;
}
- if (sz < 0)
- return rfc2047;
if (metainfo_charset)
- convert_to_utf8(piecebuf, sizeof(piecebuf), charset_q);
+ convert_to_utf8(dec, charset_q.buf);
- sz = strlen(piecebuf);
- if (outbuf + sizeof(outbuf) <= out + sz)
- return rfc2047; /* no munging */
- strcpy(out, piecebuf);
- out += sz;
+ strbuf_addbuf(&outbuf, dec);
+ strbuf_release(dec);
+ free(dec);
in = ep + 2;
}
- strcpy(out, in);
- strlcpy(it, outbuf, itsize);
+ strbuf_addstr(&outbuf, in);
+ strbuf_reset(it);
+ strbuf_addbuf(it, &outbuf);
+decode_header_bq_out:
+ strbuf_release(&outbuf);
+ strbuf_release(&charset_q);
+ strbuf_release(&piecebuf);
return rfc2047;
}
-static void decode_header(char *it, unsigned itsize)
+static void decode_header(struct strbuf *it)
{
-
- if (decode_header_bq(it, itsize))
+ if (decode_header_bq(it))
return;
/* otherwise "it" is a straight copy of the input.
* This can be binary guck but there is no charset specified.
*/
if (metainfo_charset)
- convert_to_utf8(it, itsize, "");
+ convert_to_utf8(it, "");
}
-static int decode_transfer_encoding(char *line, unsigned linesize, int inputlen)
+static void decode_transfer_encoding(struct strbuf *line)
{
- char *ep;
+ struct strbuf *ret;
switch (transfer_encoding) {
case TE_QP:
- ep = line + inputlen;
- return decode_q_segment(line, line, linesize, ep, 0);
+ ret = decode_q_segment(line, 0);
+ break;
case TE_BASE64:
- ep = line + inputlen;
- return decode_b_segment(line, line, linesize, ep);
+ ret = decode_b_segment(line);
+ break;
case TE_DONTCARE:
default:
- return inputlen;
+ return;
}
+ strbuf_reset(line);
+ strbuf_addbuf(line, ret);
+ strbuf_release(ret);
+ free(ret);
}
-static int handle_filter(char *line, unsigned linesize, int linelen);
+static void handle_filter(struct strbuf *line);
static int find_boundary(void)
{
- while(fgets(line, sizeof(line), fin) != NULL) {
- if (is_multipart_boundary(line))
+ while (!strbuf_getline(&line, fin, '\n')) {
+ if (is_multipart_boundary(&line))
return 1;
}
return 0;
static int handle_boundary(void)
{
- char newline[]="\n";
+ struct strbuf newline = STRBUF_INIT;
+
+ strbuf_addch(&newline, '\n');
again:
- if (!memcmp(line+content_top->boundary_len, "--", 2)) {
+ if (line.len >= (*content_top)->len + 2 &&
+ !memcmp(line.buf + (*content_top)->len, "--", 2)) {
/* we hit an end boundary */
/* pop the current boundary off the stack */
- free(content_top->boundary);
+ strbuf_release(*content_top);
+ free(*content_top);
+ *content_top = NULL;
/* technically won't happen as is_multipart_boundary()
will fail first. But just in case..
"can't recover\n");
exit(1);
}
- handle_filter(newline, sizeof(newline), strlen(newline));
+ handle_filter(&newline);
+ strbuf_release(&newline);
/* skip to the next boundary */
if (!find_boundary())
/* set some defaults */
transfer_encoding = TE_DONTCARE;
- charset[0] = 0;
+ strbuf_reset(&charset);
message_type = TYPE_TEXT;
/* slurp in this section's info */
- while (read_one_header_line(line, sizeof(line), fin))
- check_header(line, sizeof(line), p_hdr_data, 0);
+ while (read_one_header_line(&line, fin))
+ check_header(&line, p_hdr_data, 0);
+ strbuf_release(&newline);
/* eat the blank line after section info */
- return (fgets(line, sizeof(line), fin) != NULL);
+ return (strbuf_getline(&line, fin, '\n') == 0);
}
-static inline int patchbreak(const char *line)
+static inline int patchbreak(const struct strbuf *line)
{
+ size_t i;
+
/* Beginning of a "diff -" header? */
- if (!memcmp("diff -", line, 6))
+ if (!prefixcmp(line->buf, "diff -"))
return 1;
/* CVS "Index: " line? */
- if (!memcmp("Index: ", line, 7))
+ if (!prefixcmp(line->buf, "Index: "))
return 1;
/*
* "--- <filename>" starts patches without headers
* "---<sp>*" is a manual separator
*/
- if (!memcmp("---", line, 3)) {
- line += 3;
+ if (line->len < 4)
+ return 0;
+
+ if (!prefixcmp(line->buf, "---")) {
/* space followed by a filename? */
- if (line[0] == ' ' && !isspace(line[1]))
+ if (line->buf[3] == ' ' && !isspace(line->buf[4]))
return 1;
/* Just whitespace? */
- for (;;) {
- unsigned char c = *line++;
+ for (i = 3; i < line->len; i++) {
+ unsigned char c = line->buf[i];
if (c == '\n')
return 1;
if (!isspace(c))
return 0;
}
-
-static int handle_commit_msg(char *line, unsigned linesize)
+static int handle_commit_msg(struct strbuf *line)
{
static int still_looking = 1;
- char *endline = line + linesize;
if (!cmitmsg)
return 0;
if (still_looking) {
- char *cp = line;
- if (isspace(*line)) {
- for (cp = line + 1; *cp; cp++) {
- if (!isspace(*cp))
- break;
- }
- if (!*cp)
- return 0;
- }
- if ((still_looking = check_header(cp, endline - cp, s_hdr_data, 0)) != 0)
+ strbuf_ltrim(line);
+ if (!line->len)
+ return 0;
+ if ((still_looking = check_header(line, s_hdr_data, 0)) != 0)
return 0;
}
/* normalize the log message to UTF-8. */
if (metainfo_charset)
- convert_to_utf8(line, endline - line, charset);
+ convert_to_utf8(line, charset.buf);
if (patchbreak(line)) {
fclose(cmitmsg);
return 1;
}
- fputs(line, cmitmsg);
+ fputs(line->buf, cmitmsg);
return 0;
}
-static int handle_patch(char *line, int len)
+static void handle_patch(const struct strbuf *line)
{
- fwrite(line, 1, len, patchfile);
+ fwrite(line->buf, 1, line->len, patchfile);
patch_lines++;
- return 0;
}
-static int handle_filter(char *line, unsigned linesize, int linelen)
+static void handle_filter(struct strbuf *line)
{
static int filter = 0;
- /* filter tells us which part we left off on
- * a non-zero return indicates we hit a filter point
- */
+ /* filter tells us which part we left off on */
switch (filter) {
case 0:
- if (!handle_commit_msg(line, linesize))
+ if (!handle_commit_msg(line))
break;
filter++;
case 1:
- if (!handle_patch(line, linelen))
- break;
- filter++;
- default:
- return 1;
+ handle_patch(line);
+ break;
}
-
- return 0;
}
static void handle_body(void)
{
- int rc = 0;
- static char newline[2000];
- static char *np = newline;
- int len = strlen(line);
+ int len = 0;
+ struct strbuf prev = STRBUF_INIT;
/* Skip up to the first boundary */
- if (content_top->boundary) {
+ if (*content_top) {
if (!find_boundary())
- return;
+ goto handle_body_out;
}
do {
+ strbuf_setlen(&line, line.len + len);
+
/* process any boundary lines */
- if (content_top->boundary && is_multipart_boundary(line)) {
+ if (*content_top && is_multipart_boundary(&line)) {
/* flush any leftover */
- if (np != newline)
- handle_filter(newline, sizeof(newline),
- np - newline);
+ if (line.len)
+ handle_filter(&line);
+
if (!handle_boundary())
- return;
- len = strlen(line);
+ goto handle_body_out;
}
/* Unwrap transfer encoding */
- len = decode_transfer_encoding(line, sizeof(line), len);
- if (len < 0) {
- error("Malformed input line");
- return;
- }
+ decode_transfer_encoding(&line);
switch (transfer_encoding) {
case TE_BASE64:
case TE_QP:
{
- char *op = line;
+ struct strbuf **lines, **it, *sb;
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(&line, 0, prev.buf, prev.len);
+ strbuf_reset(&prev);
/* binary data most likely doesn't have newlines */
if (message_type != TYPE_TEXT) {
- rc = handle_filter(line, sizeof(line), len);
+ handle_filter(&line);
break;
}
-
/*
* This is a decoded line that may contain
* multiple new lines. Pass only one chunk
* at a time to handle_filter()
*/
- do {
- while (op < line + len && *op != '\n')
- *np++ = *op++;
- *np = *op;
- if (*np != 0) {
- /* should be sitting on a new line */
- *(++np) = 0;
- op++;
- rc = handle_filter(newline, sizeof(newline), np - newline);
- np = newline;
- }
- } while (op < line + len);
+ lines = strbuf_split(&line, '\n');
+ for (it = lines; (sb = *it); it++) {
+ if (*(it + 1) == NULL) /* The last line */
+ if (sb->buf[sb->len - 1] != '\n') {
+ /* Partial line, save it for later. */
+ strbuf_addbuf(&prev, sb);
+ break;
+ }
+ handle_filter(sb);
+ }
/*
- * The partial chunk is saved in newline and will be
+ * The partial chunk is saved in "prev" and will be
* appended by the next iteration of read_line_with_nul().
*/
+ strbuf_list_free(lines);
break;
}
default:
- rc = handle_filter(line, sizeof(line), len);
+ handle_filter(&line);
}
- if (rc)
- /* nothing left to filter */
- break;
- } while ((len = read_line_with_nul(line, sizeof(line), fin)));
- return;
+ strbuf_reset(&line);
+ if (strbuf_avail(&line) < 100)
+ strbuf_grow(&line, 100);
+ } while ((len = read_line_with_nul(line.buf, strbuf_avail(&line), fin)));
+
+handle_body_out:
+ strbuf_release(&prev);
}
-static void output_header_lines(FILE *fout, const char *hdr, char *data)
+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
{
+ const char *sp = data->buf;
while (1) {
- char *ep = strchr(data, '\n');
+ char *ep = strchr(sp, '\n');
int len;
if (!ep)
- len = strlen(data);
+ len = strlen(sp);
else
- len = ep - data;
- fprintf(fout, "%s: %.*s\n", hdr, len, data);
+ len = ep - sp;
+ fprintf(fout, "%s: %.*s\n", hdr, len, sp);
if (!ep)
break;
- data = ep + 1;
+ sp = ep + 1;
}
}
static void handle_info(void)
{
- char *sub;
- char *hdr;
+ struct strbuf *hdr;
int i;
for (i = 0; header[i]; i++) {
-
/* only print inbody headers if we output a patch file */
if (patch_lines && s_hdr_data[i])
hdr = s_hdr_data[i];
continue;
if (!memcmp(header[i], "Subject", 7)) {
- if (keep_subject)
- sub = hdr;
- else {
- sub = cleanup_subject(hdr);
- cleanup_space(sub);
+ if (!keep_subject) {
+ cleanup_subject(hdr);
+ cleanup_space(hdr);
}
- output_header_lines(fout, "Subject", sub);
+ output_header_lines(fout, "Subject", hdr);
} else if (!memcmp(header[i], "From", 4)) {
handle_from(hdr);
- fprintf(fout, "Author: %s\n", name);
- fprintf(fout, "Email: %s\n", email);
+ fprintf(fout, "Author: %s\n", name.buf);
+ fprintf(fout, "Email: %s\n", email.buf);
} else {
cleanup_space(hdr);
- fprintf(fout, "%s: %s\n", header[i], hdr);
+ fprintf(fout, "%s: %s\n", header[i], hdr->buf);
}
}
fprintf(fout, "\n");
return -1;
}
- p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *));
- s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *));
+ p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data));
+ s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data));
do {
peek = fgetc(in);
ungetc(peek, in);
/* process the email header */
- while (read_one_header_line(line, sizeof(line), fin))
- check_header(line, sizeof(line), p_hdr_data, 1);
+ while (read_one_header_line(&line, fin))
+ check_header(&line, p_hdr_data, 1);
handle_body();
handle_info();
}
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)
{
#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)
{
}
static const char merge_base_usage[] =
-"git-merge-base [--all] <commit-id> <commit-id>";
+"git merge-base [--all] <commit-id> <commit-id>";
int cmd_merge_base(int argc, const char **argv, const char *prefix)
{
* - *(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));
static int save_files_dirs(const unsigned char *sha1,
const char *base, int baselen, const char *path,
- unsigned int mode, int stage)
+ unsigned int mode, int stage, void *context)
{
int len = strlen(path);
char *newpath = xmalloc(baselen + len + 1);
static int get_files_dirs(struct tree *tree)
{
int n;
- if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0)
+ if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, NULL))
return 0;
n = current_file_set.nr + current_directory_set.nr;
return n;
--- /dev/null
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+#include "rerere.h"
+
+#define DEFAULT_TWOHEAD (1<<0)
+#define DEFAULT_OCTOPUS (1<<1)
+#define NO_FAST_FORWARD (1<<2)
+#define NO_TRIVIAL (1<<3)
+
+struct strategy {
+ const char *name;
+ unsigned attr;
+};
+
+static const char * const builtin_merge_usage[] = {
+ "git-merge [options] <remote>...",
+ "git-merge [options] <msg> HEAD <remote>",
+ NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct strategy **use_strategies;
+static size_t use_strategies_nr, use_strategies_alloc;
+static const char *branch;
+
+static struct strategy all_strategy[] = {
+ { "recur", NO_TRIVIAL },
+ { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
+ { "octopus", DEFAULT_OCTOPUS },
+ { "resolve", 0 },
+ { "stupid", 0 },
+ { "ours", NO_FAST_FORWARD | NO_TRIVIAL },
+ { "subtree", NO_FAST_FORWARD | NO_TRIVIAL },
+};
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+
+ if (unset)
+ strbuf_setlen(buf, 0);
+ else {
+ strbuf_addf(buf, "%s\n\n", arg);
+ have_message = 1;
+ }
+ return 0;
+}
+
+static struct strategy *get_strategy(const char *name)
+{
+ int i;
+
+ if (!name)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+ if (!strcmp(name, all_strategy[i].name))
+ return &all_strategy[i];
+ return NULL;
+}
+
+static void append_strategy(struct strategy *s)
+{
+ ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
+ use_strategies[use_strategies_nr++] = s;
+}
+
+static int option_parse_strategy(const struct option *opt,
+ const char *name, int unset)
+{
+ int i;
+ struct strategy *s;
+
+ if (unset)
+ return 0;
+
+ s = get_strategy(name);
+
+ if (s)
+ append_strategy(s);
+ else {
+ struct strbuf err;
+ strbuf_init(&err, 0);
+ for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+ strbuf_addf(&err, " %s", all_strategy[i].name);
+ fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
+ fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+ exit(1);
+ }
+ return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+ const char *arg, int unset)
+{
+ show_diffstat = unset;
+ return 0;
+}
+
+static struct option builtin_merge_options[] = {
+ { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+ "do not show a diffstat at the end of the merge",
+ PARSE_OPT_NOARG, option_parse_n },
+ OPT_BOOLEAN(0, "stat", &show_diffstat,
+ "show a diffstat at the end of the merge"),
+ OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+ OPT_BOOLEAN(0, "log", &option_log,
+ "add list of one-line log to merge commit message"),
+ OPT_BOOLEAN(0, "squash", &squash,
+ "create a single commit instead of doing a merge"),
+ OPT_BOOLEAN(0, "commit", &option_commit,
+ "perform a commit if the merge succeeds (default)"),
+ OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+ "allow fast forward (default)"),
+ OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+ "merge strategy to use", option_parse_strategy),
+ OPT_CALLBACK('m', "message", &merge_msg, "message",
+ "message to be used for the merge commit (if any)",
+ option_parse_message),
+ OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+ int len;
+ struct child_process cp;
+ struct strbuf buffer = STRBUF_INIT;
+ const char *argv[] = {"stash", "create", NULL};
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.out = -1;
+ cp.git_cmd = 1;
+
+ if (start_command(&cp))
+ die("could not run stash.");
+ len = strbuf_read(&buffer, cp.out, 1024);
+ close(cp.out);
+
+ if (finish_command(&cp) || len < 0)
+ die("stash failed");
+ else if (!len)
+ return;
+ strbuf_setlen(&buffer, buffer.len-1);
+ if (get_sha1(buffer.buf, stash))
+ die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+ int i = 0;
+ const char *args[6];
+
+ args[i++] = "read-tree";
+ if (verbose)
+ args[i++] = "-v";
+ args[i++] = "--reset";
+ args[i++] = "-u";
+ args[i++] = sha1_to_hex(sha1);
+ args[i] = NULL;
+
+ if (run_command_v_opt(args, RUN_GIT_CMD))
+ die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+ struct strbuf sb;
+ const char *args[] = { "stash", "apply", NULL, NULL };
+
+ if (is_null_sha1(stash))
+ return;
+
+ reset_hard(head, 1);
+
+ strbuf_init(&sb, 0);
+ args[2] = sha1_to_hex(stash);
+
+ /*
+ * It is OK to ignore error here, for example when there was
+ * nothing to restore.
+ */
+ run_command_v_opt(args, RUN_GIT_CMD);
+
+ strbuf_release(&sb);
+ refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+ printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+ drop_save();
+}
+
+static void squash_message(void)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ struct strbuf out;
+ struct commit_list *j;
+ int fd;
+
+ printf("Squash commit -- not updating HEAD\n");
+ fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could not write to %s", git_path("SQUASH_MSG"));
+
+ init_revisions(&rev, NULL);
+ rev.ignore_merges = 1;
+ rev.commit_format = CMIT_FMT_MEDIUM;
+
+ commit = lookup_commit(head);
+ commit->object.flags |= UNINTERESTING;
+ add_pending_object(&rev, &commit->object, NULL);
+
+ for (j = remoteheads; j; j = j->next)
+ add_pending_object(&rev, &j->item->object, NULL);
+
+ setup_revisions(0, NULL, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ strbuf_init(&out, 0);
+ strbuf_addstr(&out, "Squashed commit of the following:\n");
+ while ((commit = get_revision(&rev)) != NULL) {
+ strbuf_addch(&out, '\n');
+ strbuf_addf(&out, "commit %s\n",
+ sha1_to_hex(commit->object.sha1));
+ pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+ NULL, NULL, rev.date_mode, 0);
+ }
+ write(fd, out.buf, out.len);
+ close(fd);
+ strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+ struct child_process hook;
+ const char *argv[3], *env[2];
+ char index[PATH_MAX];
+
+ argv[0] = git_path("hooks/%s", name);
+ if (access(argv[0], X_OK) < 0)
+ return 0;
+
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+ env[0] = index;
+ env[1] = NULL;
+
+ if (squash)
+ argv[1] = "1";
+ else
+ argv[1] = "0";
+ argv[2] = NULL;
+
+ memset(&hook, 0, sizeof(hook));
+ hook.argv = argv;
+ hook.no_stdin = 1;
+ hook.stdout_to_stderr = 1;
+ hook.env = env;
+
+ return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+ struct strbuf reflog_message;
+
+ strbuf_init(&reflog_message, 0);
+ if (!msg)
+ strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+ else {
+ printf("%s\n", msg);
+ strbuf_addf(&reflog_message, "%s: %s",
+ getenv("GIT_REFLOG_ACTION"), msg);
+ }
+ if (squash) {
+ squash_message();
+ } else {
+ if (!merge_msg.len)
+ printf("No merge message -- not updating HEAD\n");
+ else {
+ const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+ update_ref(reflog_message.buf, "HEAD",
+ new_head, head, 0,
+ DIE_ON_ERR);
+ /*
+ * We ignore errors in 'gc --auto', since the
+ * user should see them.
+ */
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+ }
+ if (new_head && show_diffstat) {
+ struct diff_options opts;
+ diff_setup(&opts);
+ opts.output_format |=
+ DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+ opts.detect_rename = DIFF_DETECT_RENAME;
+ if (diff_use_color_default > 0)
+ DIFF_OPT_SET(&opts, COLOR_DIFF);
+ if (diff_setup_done(&opts) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(head, new_head, "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+ }
+
+ /* Run a post-merge hook */
+ run_hook("post-merge");
+
+ strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+ struct object *remote_head;
+ unsigned char branch_head[20], buf_sha[20];
+ struct strbuf buf;
+ const char *ptr;
+ int len, early;
+
+ memset(branch_head, 0, sizeof(branch_head));
+ remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("'%s' does not point to a commit", remote);
+
+ strbuf_init(&buf, 0);
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, remote);
+ resolve_ref(buf.buf, branch_head, 0, 0);
+
+ if (!hashcmp(remote_head->sha1, branch_head)) {
+ strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+ sha1_to_hex(branch_head), remote);
+ return;
+ }
+
+ /* See if remote matches <name>^^^.. or <name>~<number> */
+ for (len = 0, ptr = remote + strlen(remote);
+ remote < ptr && ptr[-1] == '^';
+ ptr--)
+ len++;
+ if (len)
+ early = 1;
+ else {
+ early = 0;
+ ptr = strrchr(remote, '~');
+ if (ptr) {
+ int seen_nonzero = 0;
+
+ len++; /* count ~ */
+ while (*++ptr && isdigit(*ptr)) {
+ seen_nonzero |= (*ptr != '0');
+ len++;
+ }
+ if (*ptr)
+ len = 0; /* not ...~<number> */
+ else if (seen_nonzero)
+ early = 1;
+ else if (len == 1)
+ early = 1; /* "name~" is "name~1"! */
+ }
+ }
+ if (len) {
+ struct strbuf truname = STRBUF_INIT;
+ strbuf_addstr(&truname, "refs/heads/");
+ strbuf_addstr(&truname, remote);
+ strbuf_setlen(&truname, len+11);
+ if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+ strbuf_addf(msg,
+ "%s\t\tbranch '%s'%s of .\n",
+ sha1_to_hex(remote_head->sha1),
+ truname.buf,
+ (early ? " (early part)" : ""));
+ return;
+ }
+ }
+
+ if (!strcmp(remote, "FETCH_HEAD") &&
+ !access(git_path("FETCH_HEAD"), R_OK)) {
+ FILE *fp;
+ struct strbuf line;
+ char *ptr;
+
+ strbuf_init(&line, 0);
+ fp = fopen(git_path("FETCH_HEAD"), "r");
+ if (!fp)
+ die("could not open %s for reading: %s",
+ git_path("FETCH_HEAD"), strerror(errno));
+ strbuf_getline(&line, fp, '\n');
+ fclose(fp);
+ ptr = strstr(line.buf, "\tnot-for-merge\t");
+ if (ptr)
+ strbuf_remove(&line, ptr-line.buf+1, 13);
+ strbuf_addbuf(msg, &line);
+ strbuf_release(&line);
+ return;
+ }
+ strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+ sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+ if (branch && !prefixcmp(k, "branch.") &&
+ !prefixcmp(k + 7, branch) &&
+ !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+ const char **argv;
+ int argc;
+ char *buf;
+
+ buf = xstrdup(v);
+ argc = split_cmdline(buf, &argv);
+ argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+ argc++;
+ parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+ free(buf);
+ }
+
+ if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+ show_diffstat = git_config_bool(k, v);
+ else if (!strcmp(k, "pull.twohead"))
+ return git_config_string(&pull_twohead, k, v);
+ else if (!strcmp(k, "pull.octopus"))
+ return git_config_string(&pull_octopus, k, v);
+ else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
+ option_log = git_config_bool(k, v);
+ return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+ unsigned char *one)
+{
+ int i, nr_trees = 0;
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct tree_desc t[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 2;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.trivial_merges_only = 1;
+ opts.merge = 1;
+ trees[nr_trees] = parse_tree_indirect(common);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(one);
+ if (!trees[nr_trees++])
+ return -1;
+ opts.fn = threeway_merge;
+ cache_tree_free(&active_cache_tree);
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+ if (write_cache_as_tree(sha1, 0, NULL))
+ die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(const char *strategy, struct commit_list *common,
+ const char *head_arg)
+{
+ const char **args;
+ int i = 0, ret;
+ struct commit_list *j;
+ struct strbuf buf;
+
+ args = xmalloc((4 + commit_list_count(common) +
+ commit_list_count(remoteheads)) * sizeof(char *));
+ strbuf_init(&buf, 0);
+ strbuf_addf(&buf, "merge-%s", strategy);
+ args[i++] = buf.buf;
+ for (j = common; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = "--";
+ args[i++] = head_arg;
+ for (j = remoteheads; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i] = NULL;
+ ret = run_command_v_opt(args, RUN_GIT_CMD);
+ strbuf_release(&buf);
+ i = 1;
+ for (j = common; j; j = j->next)
+ free((void *)args[i++]);
+ i += 2;
+ for (j = remoteheads; j; j = j->next)
+ free((void *)args[i++]);
+ free(args);
+ return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+ struct diff_options *opt, void *data)
+{
+ int *count = data;
+
+ (*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+ const struct index_state *state = &the_index;
+ int i, ret = 0;
+
+ for (i = 0; i < state->cache_nr; i++)
+ if (ce_stage(state->cache[i]))
+ ret++;
+
+ return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+ struct tree_desc t[MAX_UNPACK_TREES];
+ int i, fd, nr_trees = 0;
+ struct dir_struct dir;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ if (read_cache_unmerged())
+ die("you need to resolve your current index first");
+
+ fd = hold_locked_index(lock_file, 1);
+
+ memset(&trees, 0, sizeof(trees));
+ memset(&opts, 0, sizeof(opts));
+ memset(&t, 0, sizeof(t));
+ dir.show_ignored = 1;
+ dir.exclude_per_dir = ".gitignore";
+ opts.dir = &dir;
+
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.merge = 1;
+ opts.fn = twoway_merge;
+
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(remote);
+ if (!trees[nr_trees++])
+ return -1;
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+ return 0;
+}
+
+static void split_merge_strategies(const char *string, struct strategy **list,
+ int *nr, int *alloc)
+{
+ char *p, *q, *buf;
+
+ if (!string)
+ return;
+
+ buf = xstrdup(string);
+ q = buf;
+ for (;;) {
+ p = strchr(q, ' ');
+ if (!p) {
+ ALLOC_GROW(*list, *nr + 1, *alloc);
+ (*list)[(*nr)++].name = xstrdup(q);
+ free(buf);
+ return;
+ } else {
+ *p = '\0';
+ ALLOC_GROW(*list, *nr + 1, *alloc);
+ (*list)[(*nr)++].name = xstrdup(q);
+ q = ++p;
+ }
+ }
+}
+
+static void add_strategies(const char *string, unsigned attr)
+{
+ struct strategy *list = NULL;
+ int list_alloc = 0, list_nr = 0, i;
+
+ memset(&list, 0, sizeof(list));
+ split_merge_strategies(string, &list, &list_nr, &list_alloc);
+ if (list != NULL) {
+ for (i = 0; i < list_nr; i++) {
+ struct strategy *s;
+
+ s = get_strategy(list[i].name);
+ if (s)
+ append_strategy(s);
+ }
+ return;
+ }
+ for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+ if (all_strategy[i].attr & attr)
+ append_strategy(&all_strategy[i]);
+
+}
+
+static int merge_trivial(void)
+{
+ unsigned char result_tree[20], result_commit[20];
+ struct commit_list parent;
+
+ write_tree_trivial(result_tree);
+ printf("Wonderful.\n");
+ parent.item = remoteheads->item;
+ parent.next = NULL;
+ commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+ finish(result_commit, "In-index merge");
+ drop_save();
+ return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+ unsigned char *result_tree,
+ const char *wt_strategy)
+{
+ struct commit_list *parents = NULL, *j;
+ struct strbuf buf = STRBUF_INIT;
+ unsigned char result_commit[20];
+
+ free_commit_list(common);
+ if (allow_fast_forward) {
+ parents = remoteheads;
+ commit_list_insert(lookup_commit(head), &parents);
+ parents = reduce_heads(parents);
+ } else {
+ struct commit_list **pptr = &parents;
+
+ pptr = &commit_list_insert(lookup_commit(head),
+ pptr)->next;
+ for (j = remoteheads; j; j = j->next)
+ pptr = &commit_list_insert(j->item, pptr)->next;
+ }
+ free_commit_list(remoteheads);
+ strbuf_addch(&merge_msg, '\n');
+ commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+ strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
+ finish(result_commit, buf.buf);
+ strbuf_release(&buf);
+ drop_save();
+ return 0;
+}
+
+static int suggest_conflicts(void)
+{
+ FILE *fp;
+ int pos;
+
+ fp = fopen(git_path("MERGE_MSG"), "a");
+ if (!fp)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ fprintf(fp, "\nConflicts:\n");
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+
+ if (ce_stage(ce)) {
+ fprintf(fp, "\t%s\n", ce->name);
+ while (pos + 1 < active_nr &&
+ !strcmp(ce->name,
+ active_cache[pos + 1]->name))
+ pos++;
+ }
+ }
+ fclose(fp);
+ rerere();
+ printf("Automatic merge failed; "
+ "fix conflicts and then commit the result.\n");
+ return 1;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+ struct commit *second_token = NULL;
+ if (argc > 1) {
+ unsigned char second_sha1[20];
+
+ if (get_sha1(argv[1], second_sha1))
+ return NULL;
+ second_token = lookup_commit_reference_gently(second_sha1, 0);
+ if (!second_token)
+ die("'%s' is not a commit", argv[1]);
+ if (hashcmp(second_token->object.sha1, head))
+ return NULL;
+ }
+ return second_token;
+}
+
+static int evaluate_result(void)
+{
+ int cnt = 0;
+ struct rev_info rev;
+
+ if (read_cache() < 0)
+ die("failed to read the cache");
+
+ /* Check how many files differ. */
+ init_revisions(&rev, "");
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.diffopt.output_format |=
+ DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = count_diff_files;
+ rev.diffopt.format_callback_data = &cnt;
+ run_diff_files(&rev, 0);
+
+ /*
+ * Check how many unmerged entries are
+ * there.
+ */
+ cnt += count_unmerged_entries();
+
+ return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+ unsigned char result_tree[20];
+ struct strbuf buf;
+ const char *head_arg;
+ int flag, head_invalid = 0, i;
+ int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+ struct commit_list *common = NULL;
+ const char *best_strategy = NULL, *wt_strategy = NULL;
+ struct commit_list **remotes = &remoteheads;
+
+ setup_work_tree();
+ if (unmerged_cache())
+ die("You are in the middle of a conflicted merge.");
+
+ /*
+ * Check if we are _not_ on a detached HEAD, i.e. if there is a
+ * current branch.
+ */
+ branch = resolve_ref("HEAD", head, 0, &flag);
+ if (branch && !prefixcmp(branch, "refs/heads/"))
+ branch += 11;
+ if (is_null_sha1(head))
+ head_invalid = 1;
+
+ git_config(git_merge_config, NULL);
+
+ /* for color.ui */
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ argc = parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+
+ if (squash) {
+ if (!allow_fast_forward)
+ die("You cannot combine --squash with --no-ff.");
+ option_commit = 0;
+ }
+
+ if (!argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ /*
+ * This could be traditional "merge <msg> HEAD <commit>..." and
+ * the way we can tell it is to see if the second token is HEAD,
+ * but some people might have misused the interface and used a
+ * committish that is the same as HEAD there instead.
+ * Traditional format never would have "-m" so it is an
+ * additional safety measure to check for it.
+ */
+ strbuf_init(&buf, 0);
+
+ if (!have_message && is_old_style_invocation(argc, argv)) {
+ strbuf_addstr(&merge_msg, argv[0]);
+ head_arg = argv[1];
+ argv += 2;
+ argc -= 2;
+ } else if (head_invalid) {
+ struct object *remote_head;
+ /*
+ * If the merged head is a valid one there is no reason
+ * to forbid "git merge" into a branch yet to be born.
+ * We do the same for "git pull".
+ */
+ if (argc != 1)
+ die("Can merge only exactly one commit into "
+ "empty head");
+ remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("%s - not something we can merge", argv[0]);
+ update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+ DIE_ON_ERR);
+ reset_hard(remote_head->sha1, 0);
+ return 0;
+ } else {
+ struct strbuf msg;
+
+ /* We are invoked directly as the first-class UI. */
+ head_arg = "HEAD";
+
+ /*
+ * All the rest are the commits being merged;
+ * prepare the standard merge summary message to
+ * be appended to the given message. If remote
+ * is invalid we will die later in the common
+ * codepath so we discard the error in this
+ * loop.
+ */
+ strbuf_init(&msg, 0);
+ for (i = 0; i < argc; i++)
+ merge_name(argv[i], &msg);
+ fmt_merge_msg(option_log, &msg, &merge_msg);
+ if (merge_msg.len)
+ strbuf_setlen(&merge_msg, merge_msg.len-1);
+ }
+
+ if (head_invalid || !argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ strbuf_addstr(&buf, "merge");
+ for (i = 0; i < argc; i++)
+ strbuf_addf(&buf, " %s", argv[i]);
+ setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+ strbuf_reset(&buf);
+
+ for (i = 0; i < argc; i++) {
+ struct object *o;
+
+ o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+ if (!o)
+ die("%s - not something we can merge", argv[i]);
+ remotes = &commit_list_insert(lookup_commit(o->sha1),
+ remotes)->next;
+
+ strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+ setenv(buf.buf, argv[i], 1);
+ strbuf_reset(&buf);
+ }
+
+ if (!use_strategies) {
+ if (!remoteheads->next)
+ add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+ else
+ add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+ }
+
+ for (i = 0; i < use_strategies_nr; i++) {
+ if (use_strategies[i]->attr & NO_FAST_FORWARD)
+ allow_fast_forward = 0;
+ if (use_strategies[i]->attr & NO_TRIVIAL)
+ allow_trivial = 0;
+ }
+
+ if (!remoteheads->next)
+ common = get_merge_bases(lookup_commit(head),
+ remoteheads->item, 1);
+ else {
+ struct commit_list *list = remoteheads;
+ commit_list_insert(lookup_commit(head), &list);
+ common = get_octopus_merge_bases(list);
+ free(list);
+ }
+
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+ DIE_ON_ERR);
+
+ if (!common)
+ ; /* No common ancestors found. We need a real merge. */
+ else if (!remoteheads->next && !common->next &&
+ common->item == remoteheads->item) {
+ /*
+ * If head can reach all the merge then we are up to date.
+ * but first the most common case of merging one remote.
+ */
+ finish_up_to_date("Already up-to-date.");
+ return 0;
+ } else if (allow_fast_forward && !remoteheads->next &&
+ !common->next &&
+ !hashcmp(common->item->object.sha1, head)) {
+ /* Again the most common case of merging one remote. */
+ struct strbuf msg;
+ struct object *o;
+ char hex[41];
+
+ strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+ printf("Updating %s..%s\n",
+ hex,
+ find_unique_abbrev(remoteheads->item->object.sha1,
+ DEFAULT_ABBREV));
+ refresh_cache(REFRESH_QUIET);
+ strbuf_init(&msg, 0);
+ strbuf_addstr(&msg, "Fast forward");
+ if (have_message)
+ strbuf_addstr(&msg,
+ " (no commit created; -m option ignored)");
+ o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+ 0, NULL, OBJ_COMMIT);
+ if (!o)
+ return 1;
+
+ if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+ return 1;
+
+ finish(o->sha1, msg.buf);
+ drop_save();
+ return 0;
+ } else if (!remoteheads->next && common->next)
+ ;
+ /*
+ * We are not doing octopus and not fast forward. Need
+ * a real merge.
+ */
+ else if (!remoteheads->next && !common->next && option_commit) {
+ /*
+ * We are not doing octopus, not fast forward, and have
+ * only one common.
+ */
+ refresh_cache(REFRESH_QUIET);
+ if (allow_trivial) {
+ /* See if it is really trivial. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ printf("Trying really trivial in-index merge...\n");
+ if (!read_tree_trivial(common->item->object.sha1,
+ head, remoteheads->item->object.sha1))
+ return merge_trivial();
+ printf("Nope.\n");
+ }
+ } else {
+ /*
+ * An octopus. If we can reach all the remote we are up
+ * to date.
+ */
+ int up_to_date = 1;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next) {
+ struct commit_list *common_one;
+
+ /*
+ * Here we *have* to calculate the individual
+ * merge_bases again, otherwise "git merge HEAD^
+ * HEAD^^" would be missed.
+ */
+ common_one = get_merge_bases(lookup_commit(head),
+ j->item, 1);
+ if (hashcmp(common_one->item->object.sha1,
+ j->item->object.sha1)) {
+ up_to_date = 0;
+ break;
+ }
+ }
+ if (up_to_date) {
+ finish_up_to_date("Already up-to-date. Yeeah!");
+ return 0;
+ }
+ }
+
+ /* We are going to make a new commit. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+ /*
+ * At this point, we need a real merge. No matter what strategy
+ * we use, it would operate on the index, possibly affecting the
+ * working tree, and when resolved cleanly, have the desired
+ * tree in the index -- this means that the index must be in
+ * sync with the head commit. The strategies are responsible
+ * to ensure this.
+ */
+ if (use_strategies_nr != 1) {
+ /*
+ * Stash away the local changes so that we can try more
+ * than one.
+ */
+ save_state();
+ } else {
+ memcpy(stash, null_sha1, 20);
+ }
+
+ for (i = 0; i < use_strategies_nr; i++) {
+ int ret;
+ if (i) {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ }
+ if (use_strategies_nr != 1)
+ printf("Trying merge strategy %s...\n",
+ use_strategies[i]->name);
+ /*
+ * Remember which strategy left the state in the working
+ * tree.
+ */
+ wt_strategy = use_strategies[i]->name;
+
+ ret = try_merge_strategy(use_strategies[i]->name,
+ common, head_arg);
+ if (!option_commit && !ret) {
+ merge_was_ok = 1;
+ /*
+ * This is necessary here just to avoid writing
+ * the tree, but later we will *not* exit with
+ * status code 1 because merge_was_ok is set.
+ */
+ ret = 1;
+ }
+
+ if (ret) {
+ /*
+ * The backend exits with 1 when conflicts are
+ * left to be resolved, with 2 when it does not
+ * handle the given merge at all.
+ */
+ if (ret == 1) {
+ int cnt = evaluate_result();
+
+ if (best_cnt <= 0 || cnt <= best_cnt) {
+ best_strategy = use_strategies[i]->name;
+ best_cnt = cnt;
+ }
+ }
+ if (merge_was_ok)
+ break;
+ else
+ continue;
+ }
+
+ /* Automerge succeeded. */
+ write_tree_trivial(result_tree);
+ automerge_was_ok = 1;
+ break;
+ }
+
+ /*
+ * If we have a resulting tree, that means the strategy module
+ * auto resolved the merge cleanly.
+ */
+ if (automerge_was_ok)
+ return finish_automerge(common, result_tree, wt_strategy);
+
+ /*
+ * Pick the result from the best strategy and have the user fix
+ * it up.
+ */
+ if (!best_strategy) {
+ restore_state();
+ if (use_strategies_nr > 1)
+ fprintf(stderr,
+ "No merge strategy handled the merge.\n");
+ else
+ fprintf(stderr, "Merge with strategy %s failed.\n",
+ use_strategies[0]->name);
+ return 2;
+ } else if (best_strategy == wt_strategy)
+ ; /* We already have its result in the working tree. */
+ else {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ printf("Using the %s to prepare resolving by hand.\n",
+ best_strategy);
+ try_merge_strategy(best_strategy, common, head_arg);
+ }
+
+ if (squash)
+ finish(NULL, NULL);
+ else {
+ int fd;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next)
+ strbuf_addf(&buf, "%s\n",
+ sha1_to_hex(j->item->object.sha1));
+ fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing",
+ git_path("MERGE_HEAD"));
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die("Could not write to %s", git_path("MERGE_HEAD"));
+ close(fd);
+ strbuf_addch(&merge_msg, '\n');
+ fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+ merge_msg.len)
+ die("Could not write to %s", git_path("MERGE_MSG"));
+ close(fd);
+ }
+
+ if (merge_was_ok) {
+ fprintf(stderr, "Automatic merge went well; "
+ "stopped before committing as requested\n");
+ return 0;
+ } else
+ return suggest_conflicts();
+}
#include "parse-options.h"
static const char * const builtin_mv_usage[] = {
- "git-mv [options] <source>... <destination>",
+ "git mv [options] <source>... <destination>",
NULL
};
}
static char const * const name_rev_usage[] = {
- "git-name-rev [options] ( --all | --stdin | <commit>... )",
+ "git name-rev [options] ( --all | --stdin | <commit>... )",
NULL
};
#include "pack-refs.h"
static char const * const pack_refs_usage[] = {
- "git-pack-refs [options]",
+ "git pack-refs [options]",
NULL
};
#include "progress.h"
static const char prune_packed_usage[] =
-"git-prune-packed [-n] [-q]";
+"git prune-packed [-n] [-q]";
#define DRY_RUN 01
#define VERBOSE 02
#include "parse-options.h"
static const char * const prune_usage[] = {
- "git-prune [-n] [--expire <time>] [--] [<head>...]",
+ "git prune [-n] [--expire <time>] [--] [<head>...]",
NULL
};
static int show_only;
#include "parse-options.h"
static const char * const push_usage[] = {
- "git-push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]",
+ "git push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]",
NULL,
};
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;
*/
static const char reflog_expire_usage[] =
-"git-reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+"git reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
static const char reflog_delete_usage[] =
-"git-reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
+"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
static unsigned long default_reflog_expire;
static unsigned long default_reflog_expire_unreachable;
*/
static const char reflog_usage[] =
-"git-reflog (expire | ...)";
+"git reflog (expire | ...)";
int cmd_reflog(int argc, const char **argv, const char *prefix)
{
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;
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);
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,
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;
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);
"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");
}
#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]";
+"git rerere [clear | status | diff | gc]";
/* these values are days */
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);
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"));
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 };
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;
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"))
#include "parse-options.h"
static const char * const git_reset_usage[] = {
- "git-reset [--mixed | --soft | --hard] [-q] [<commit>]",
- "git-reset [--mixed] <commit> [--] <paths>...",
+ "git reset [--mixed | --soft | --hard] [-q] [<commit>]",
+ "git reset [--mixed] <commit> [--] <paths>...",
NULL
};
#define COUNTED (1u<<16)
static const char rev_list_usage[] =
-"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
+"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
" limiting output:\n"
" --max-count=nr\n"
" --max-age=epoch\n"
" --reverse\n"
" formatting output:\n"
" --parents\n"
+" --children\n"
" --objects | --objects-edge\n"
" --unpacked\n"
" --header | --pretty\n"
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(' ');
{
static int keep_dashdash = 0;
static char const * const parseopt_usage[] = {
- "git-rev-parse --parseopt [options] -- [<args>...]",
+ "git rev-parse --parseopt [options] -- [<args>...]",
NULL
};
static struct option parseopt_opts[] = {
*/
static const char * const revert_usage[] = {
- "git-revert [options] <commit-ish>",
+ "git revert [options] <commit-ish>",
NULL
};
static const char * const cherry_pick_usage[] = {
- "git-cherry-pick [options] <commit-ish>",
+ "git cherry-pick [options] <commit-ish>",
NULL
};
{
char buffer[256];
const char *argv[6];
+ int i = 0;
sprintf(buffer, "GITHEAD_%s", head_sha1);
setenv(buffer, head_name, 1);
* 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);
}
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;
}
}
- 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)) {
#include "parse-options.h"
static const char * const builtin_rm_usage[] = {
- "git-rm [options] [--] <file>...",
+ "git rm [options] [--] <file>...",
NULL
};
#include "send-pack.h"
static const char send_pack_usage[] =
-"git-send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
" --all and explicit <ref> specification are mutually exclusive.";
static struct send_pack_args args = {
#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)
{
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);
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;
}
#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 */
-
- *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);
-
- 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);
+ struct shortlog *log = opt->value;
+
+ log->wrap_lines = !unset;
+ if (unset)
+ return 0;
+ if (!arg) {
+ log->wrap = DEFAULT_WRAPLEN;
+ log->in1 = DEFAULT_INDENT1;
+ log->in2 = DEFAULT_INDENT2;
+ return 0;
+ }
+
+ 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)
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);
+ init_revisions(&rev, prefix);
+ 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;
}
- else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
- usage(shortlog_usage);
- else
- break;
- argv++;
- argc--;
+ parse_revision_opt(&rev, &ctx, options, shortlog_usage);
}
- init_revisions(&rev, prefix);
- argc = setup_revisions(argc, argv, &rev, NULL);
- if (argc > 1)
- die ("unrecognized argument: %s", argv[1]);
+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))
#include "builtin.h"
static const char show_branch_usage[] =
-"git-show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n[,b]] <branch>";
+"git show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n[,b]] <branch>";
static const char show_branch_usage_reflog[] =
"--reflog is incompatible with --all, --remotes, --independent or --merge-base";
#include "parse-options.h"
static const char * const git_symbolic_ref_usage[] = {
- "git-symbolic-ref [options] name [ref]",
+ "git symbolic-ref [options] name [ref]",
NULL
};
#include "parse-options.h"
static const char * const git_tag_usage[] = {
- "git-tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
- "git-tag -d <tagname>...",
- "git-tag -l [-n[<num>]] [<pattern>]",
- "git-tag -v <tagname>...",
+ "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
+ "git tag -d <tagname>...",
+ "git tag -l [-n[<num>]] [<pattern>]",
+ "git tag -v <tagname>...",
NULL
};
const char *args[4];
char *bracket;
int len;
+ int i, j;
if (!*signingkey) {
if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
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;
}
#include "quote.h"
static const char tar_tree_usage[] =
-"git-tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
+"git tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
"*** Note that this command is now deprecated; use git-archive instead.";
int cmd_tar_tree(int argc, const char **argv, const char *prefix)
}
static const char update_index_usage[] =
-"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
static unsigned char head_sha1[20];
static unsigned char merge_head_sha1[20];
#include "parse-options.h"
static const char * const git_update_ref_usage[] = {
- "git-update-ref [options] -d <refname> [<oldval>]",
- "git-update-ref [options] <refname> <newval> [<oldval>]",
+ "git update-ref [options] -d <refname> [<oldval>]",
+ "git update-ref [options] <refname> <newval> [<oldval>]",
NULL
};
#include "sideband.h"
static const char upload_archive_usage[] =
- "git-upload-archive <repo>";
+ "git upload-archive <repo>";
static const char deadchild[] =
-"git-upload-archive: archiver died with error";
+"git upload-archive: archiver died with error";
static const char lostchild[] =
-"git-upload-archive: archiver process was lost";
+"git upload-archive: archiver process was lost";
static int run_upload_archive(int argc, const char **argv, const char *prefix)
{
- struct archiver ar;
+ const struct archiver *ar;
+ struct archiver_args args;
const char *sent_argv[MAX_ARGS];
const char *arg_cmd = "argument ";
char *p, buf[4096];
sent_argv[sent_argc] = NULL;
/* parse all options sent by the client */
- treeish_idx = parse_archive_args(sent_argc, sent_argv, &ar);
+ treeish_idx = parse_archive_args(sent_argc, sent_argv, &ar, &args);
- parse_treeish_arg(sent_argv + treeish_idx, &ar.args, prefix);
- parse_pathspec_arg(sent_argv + treeish_idx + 1, &ar.args);
+ parse_treeish_arg(sent_argv + treeish_idx, &args, prefix);
+ parse_pathspec_arg(sent_argv + treeish_idx + 1, &args);
- return ar.write_archive(&ar.args);
+ return ar->write_archive(&args);
}
static void error_clnt(const char *fmt, ...)
#include <signal.h>
static const char builtin_verify_tag_usage[] =
- "git-verify-tag [-v|--verbose] <tag>...";
+ "git verify-tag [-v|--verbose] <tag>...";
#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
#include "cache-tree.h"
static const char write_tree_usage[] =
-"git-write-tree [--missing-ok] [--prefix=<prefix>/]";
+"git write-tree [--missing-ok] [--prefix=<prefix>/]";
int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
{
#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[];
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);
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);
int i, ref_count = 0;
char buffer[1024];
struct rev_info revs;
+ int read_from_stdin = 0;
struct child_process rls;
FILE *rls_fout;
/* 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;
return read_one(&buffer, &size);
}
-struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
+static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
{
while (*path) {
const char *slash;
int cache_tree_fully_valid(struct cache_tree *);
int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
-struct cache_tree *cache_tree_find(struct cache_tree *, const char *);
-
#define WRITE_TREE_UNREADABLE_INDEX (-1)
#define WRITE_TREE_UNMERGED_INDEX (-2)
#define WRITE_TREE_PREFIX_ERROR (-3)
#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)
/* 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 *);
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);
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 */
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) {
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;
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;
}
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) {
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;
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;
+}
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);
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);
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 */
timer_fn = handler;
return old;
}
+
+static const char *make_backslash_path(const char *path)
+{
+ static char buf[PATH_MAX + 1];
+ char *c;
+
+ if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
+ die("Too long path: %.*s", 60, path);
+
+ for (c = buf; *c; c++) {
+ if (*c == '/')
+ *c = '\\';
+ }
+ return buf;
+}
+
+void mingw_open_html(const char *unixpath)
+{
+ const char *htmlpath = make_backslash_path(unixpath);
+ printf("Launching default browser to display HTML ...\n");
+ ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
+}
#define PATH_SEP ';'
#define PRIuMAX "I64u"
+void mingw_open_html(const char *path);
+#define open_html mingw_open_html
+
/*
* helpers
*/
const char *git_etc_gitconfig(void)
{
static const char *system_wide;
- if (!system_wide) {
- system_wide = ETC_GITCONFIG;
- if (!is_absolute_path(system_wide)) {
- /* interpret path relative to exec-dir */
- struct strbuf d = STRBUF_INIT;
- strbuf_addf(&d, "%s/%s", git_exec_path(), system_wide);
- system_wide = strbuf_detach(&d, NULL);
- }
- }
+ if (!system_wide)
+ system_wide = system_path(ETC_GITCONFIG);
return system_wide;
}
# git@vger.kernel.org
#
+case "$COMP_WORDBREAKS" in
+*:*) : great ;;
+*) COMP_WORDBREAKS="$COMP_WORDBREAKS:"
+esac
+
__gitdir ()
{
if [ -z "$1" ]; then
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"
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"
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 ()
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,$,/,
cur="${cur#*..}"
__gitcomp "$(__git_refs)" "$pfx" "$cur"
;;
- *.)
- __gitcomp "$cur."
- ;;
*)
__gitcomp "$(__git_refs)"
;;
_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
*)
case "$cur" in
*:*)
- __gitcomp "$(__git_refs)" "" "${cur#*:}"
+ local pfx=""
+ case "$COMP_WORDBREAKS" in
+ *:*) : great ;;
+ *) pfx="${cur%%:*}:" ;;
+ esac
+ __gitcomp "$(__git_refs)" "$pfx" "${cur#*:}"
;;
*)
local remote
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#+}"
_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
__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]}"
_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
}
rebase) _git_rebase ;;
remote) _git_remote ;;
reset) _git_reset ;;
+ send-email) _git_send_email ;;
shortlog) _git_shortlog ;;
show) _git_show ;;
show-branch) _git_log ;;
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
"\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
(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))
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git merge [options] <remote>...
+git merge [options] <msg> HEAD <remote>
+--
+stat show a diffstat at the end of the merge
+n don't show a diffstat at the end of the merge
+summary (synonym to --stat)
+log add list of one-line log to merge commit message
+squash create a single commit instead of doing a merge
+commit perform a commit if the merge succeeds (default)
+ff allow fast forward (default)
+s,strategy= merge strategy to use
+m,message= message to be used for the merge commit (if any)
+"
+
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+test -z "$(git ls-files -u)" ||
+ die "You are in the middle of a conflicted merge."
+
+LF='
+'
+
+all_strategies='recur recursive octopus resolve stupid ours subtree'
+default_twohead_strategies='recursive'
+default_octopus_strategies='octopus'
+no_fast_forward_strategies='subtree ours'
+no_trivial_strategies='recursive recur subtree ours'
+use_strategies=
+
+allow_fast_forward=t
+allow_trivial_merge=t
+squash= no_commit= log_arg=
+
+dropsave() {
+ rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
+ "$GIT_DIR/MERGE_STASH" || exit 1
+}
+
+savestate() {
+ # Stash away any local modifications.
+ git stash create >"$GIT_DIR/MERGE_STASH"
+}
+
+restorestate() {
+ if test -f "$GIT_DIR/MERGE_STASH"
+ then
+ git reset --hard $head >/dev/null
+ git stash apply $(cat "$GIT_DIR/MERGE_STASH")
+ git update-index --refresh >/dev/null
+ fi
+}
+
+finish_up_to_date () {
+ case "$squash" in
+ t)
+ echo "$1 (nothing to squash)" ;;
+ '')
+ echo "$1" ;;
+ esac
+ dropsave
+}
+
+squash_message () {
+ echo Squashed commit of the following:
+ echo
+ git log --no-merges --pretty=medium ^"$head" $remoteheads
+}
+
+finish () {
+ if test '' = "$2"
+ then
+ rlogm="$GIT_REFLOG_ACTION"
+ else
+ echo "$2"
+ rlogm="$GIT_REFLOG_ACTION: $2"
+ fi
+ case "$squash" in
+ t)
+ echo "Squash commit -- not updating HEAD"
+ squash_message >"$GIT_DIR/SQUASH_MSG"
+ ;;
+ '')
+ case "$merge_msg" in
+ '')
+ echo "No merge message -- not updating HEAD"
+ ;;
+ *)
+ git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
+ git gc --auto
+ ;;
+ esac
+ ;;
+ esac
+ case "$1" in
+ '')
+ ;;
+ ?*)
+ if test "$show_diffstat" = t
+ then
+ # We want color (if set), but no pager
+ GIT_PAGER='' git diff --stat --summary -M "$head" "$1"
+ fi
+ ;;
+ esac
+
+ # Run a post-merge hook
+ if test -x "$GIT_DIR"/hooks/post-merge
+ then
+ case "$squash" in
+ t)
+ "$GIT_DIR"/hooks/post-merge 1
+ ;;
+ '')
+ "$GIT_DIR"/hooks/post-merge 0
+ ;;
+ esac
+ fi
+}
+
+merge_name () {
+ remote="$1"
+ rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
+ bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
+ if test "$rh" = "$bh"
+ then
+ echo "$rh branch '$remote' of ."
+ elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
+ git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
+ then
+ echo "$rh branch '$truname' (early part) of ."
+ elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
+ then
+ sed -e 's/ not-for-merge / /' -e 1q \
+ "$GIT_DIR/FETCH_HEAD"
+ else
+ echo "$rh commit '$remote'"
+ fi
+}
+
+parse_config () {
+ while test $# != 0; do
+ case "$1" in
+ -n|--no-stat|--no-summary)
+ show_diffstat=false ;;
+ --stat|--summary)
+ show_diffstat=t ;;
+ --log|--no-log)
+ log_arg=$1 ;;
+ --squash)
+ test "$allow_fast_forward" = t ||
+ die "You cannot combine --squash with --no-ff."
+ squash=t no_commit=t ;;
+ --no-squash)
+ squash= no_commit= ;;
+ --commit)
+ no_commit= ;;
+ --no-commit)
+ no_commit=t ;;
+ --ff)
+ allow_fast_forward=t ;;
+ --no-ff)
+ test "$squash" != t ||
+ die "You cannot combine --squash with --no-ff."
+ allow_fast_forward=f ;;
+ -s|--strategy)
+ shift
+ case " $all_strategies " in
+ *" $1 "*)
+ use_strategies="$use_strategies$1 " ;;
+ *)
+ die "available strategies are: $all_strategies" ;;
+ esac
+ ;;
+ -m|--message)
+ shift
+ merge_msg="$1"
+ have_message=t
+ ;;
+ --)
+ shift
+ break ;;
+ *) usage ;;
+ esac
+ shift
+ done
+ args_left=$#
+}
+
+test $# != 0 || usage
+
+have_message=
+
+if branch=$(git-symbolic-ref -q HEAD)
+then
+ mergeopts=$(git config "branch.${branch#refs/heads/}.mergeoptions")
+ if test -n "$mergeopts"
+ then
+ parse_config $mergeopts --
+ fi
+fi
+
+parse_config "$@"
+while test $args_left -lt $#; do shift; done
+
+if test -z "$show_diffstat"; then
+ test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
+ test "$(git config --bool merge.stat)" = false && show_diffstat=false
+ test -z "$show_diffstat" && show_diffstat=t
+fi
+
+# This could be traditional "merge <msg> HEAD <commit>..." and the
+# way we can tell it is to see if the second token is HEAD, but some
+# people might have misused the interface and used a committish that
+# is the same as HEAD there instead. Traditional format never would
+# have "-m" so it is an additional safety measure to check for it.
+
+if test -z "$have_message" &&
+ second_token=$(git rev-parse --verify "$2^0" 2>/dev/null) &&
+ head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null) &&
+ test "$second_token" = "$head_commit"
+then
+ merge_msg="$1"
+ shift
+ head_arg="$1"
+ shift
+elif ! git rev-parse --verify HEAD >/dev/null 2>&1
+then
+ # If the merged head is a valid one there is no reason to
+ # forbid "git merge" into a branch yet to be born. We do
+ # the same for "git pull".
+ if test 1 -ne $#
+ then
+ echo >&2 "Can merge only exactly one commit into empty head"
+ exit 1
+ fi
+
+ rh=$(git rev-parse --verify "$1^0") ||
+ die "$1 - not something we can merge"
+
+ git update-ref -m "initial pull" HEAD "$rh" "" &&
+ git read-tree --reset -u HEAD
+ exit
+
+else
+ # We are invoked directly as the first-class UI.
+ head_arg=HEAD
+
+ # All the rest are the commits being merged; prepare
+ # the standard merge summary message to be appended to
+ # the given message. If remote is invalid we will die
+ # later in the common codepath so we discard the error
+ # in this loop.
+ merge_name=$(for remote
+ do
+ merge_name "$remote"
+ done | git fmt-merge-msg $log_arg
+ )
+ merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
+fi
+head=$(git rev-parse --verify "$head_arg"^0) || usage
+
+# All the rest are remote heads
+test "$#" = 0 && usage ;# we need at least one remote head.
+set_reflog_action "merge $*"
+
+remoteheads=
+for remote
+do
+ remotehead=$(git rev-parse --verify "$remote"^0 2>/dev/null) ||
+ die "$remote - not something we can merge"
+ remoteheads="${remoteheads}$remotehead "
+ eval GITHEAD_$remotehead='"$remote"'
+ export GITHEAD_$remotehead
+done
+set x $remoteheads ; shift
+
+case "$use_strategies" in
+'')
+ case "$#" in
+ 1)
+ var="`git config --get pull.twohead`"
+ if test -n "$var"
+ then
+ use_strategies="$var"
+ else
+ use_strategies="$default_twohead_strategies"
+ fi ;;
+ *)
+ var="`git config --get pull.octopus`"
+ if test -n "$var"
+ then
+ use_strategies="$var"
+ else
+ use_strategies="$default_octopus_strategies"
+ fi ;;
+ esac
+ ;;
+esac
+
+for s in $use_strategies
+do
+ for ss in $no_fast_forward_strategies
+ do
+ case " $s " in
+ *" $ss "*)
+ allow_fast_forward=f
+ break
+ ;;
+ esac
+ done
+ for ss in $no_trivial_strategies
+ do
+ case " $s " in
+ *" $ss "*)
+ allow_trivial_merge=f
+ break
+ ;;
+ esac
+ done
+done
+
+case "$#" in
+1)
+ common=$(git merge-base --all $head "$@")
+ ;;
+*)
+ common=$(git show-branch --merge-base $head "$@")
+ ;;
+esac
+echo "$head" >"$GIT_DIR/ORIG_HEAD"
+
+case "$allow_fast_forward,$#,$common,$no_commit" in
+?,*,'',*)
+ # No common ancestors found. We need a real merge.
+ ;;
+?,1,"$1",*)
+ # If head can reach all the merge then we are up to date.
+ # but first the most common case of merging one remote.
+ finish_up_to_date "Already up-to-date."
+ exit 0
+ ;;
+t,1,"$head",*)
+ # Again the most common case of merging one remote.
+ echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
+ git update-index --refresh 2>/dev/null
+ msg="Fast forward"
+ if test -n "$have_message"
+ then
+ msg="$msg (no commit created; -m option ignored)"
+ fi
+ new_head=$(git rev-parse --verify "$1^0") &&
+ git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
+ finish "$new_head" "$msg" || exit
+ dropsave
+ exit 0
+ ;;
+?,1,?*"$LF"?*,*)
+ # We are not doing octopus and not fast forward. Need a
+ # real merge.
+ ;;
+?,1,*,)
+ # We are not doing octopus, not fast forward, and have only
+ # one common.
+ git update-index --refresh 2>/dev/null
+ case "$allow_trivial_merge" in
+ t)
+ # See if it is really trivial.
+ git var GIT_COMMITTER_IDENT >/dev/null || exit
+ echo "Trying really trivial in-index merge..."
+ if git read-tree --trivial -m -u -v $common $head "$1" &&
+ result_tree=$(git write-tree)
+ then
+ echo "Wonderful."
+ result_commit=$(
+ printf '%s\n' "$merge_msg" |
+ git commit-tree $result_tree -p HEAD -p "$1"
+ ) || exit
+ finish "$result_commit" "In-index merge"
+ dropsave
+ exit 0
+ fi
+ echo "Nope."
+ esac
+ ;;
+*)
+ # An octopus. If we can reach all the remote we are up to date.
+ up_to_date=t
+ for remote
+ do
+ common_one=$(git merge-base --all $head $remote)
+ if test "$common_one" != "$remote"
+ then
+ up_to_date=f
+ break
+ fi
+ done
+ if test "$up_to_date" = t
+ then
+ finish_up_to_date "Already up-to-date. Yeeah!"
+ exit 0
+ fi
+ ;;
+esac
+
+# We are going to make a new commit.
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
+# At this point, we need a real merge. No matter what strategy
+# we use, it would operate on the index, possibly affecting the
+# working tree, and when resolved cleanly, have the desired tree
+# in the index -- this means that the index must be in sync with
+# the $head commit. The strategies are responsible to ensure this.
+
+case "$use_strategies" in
+?*' '?*)
+ # Stash away the local changes so that we can try more than one.
+ savestate
+ single_strategy=no
+ ;;
+*)
+ rm -f "$GIT_DIR/MERGE_STASH"
+ single_strategy=yes
+ ;;
+esac
+
+result_tree= best_cnt=-1 best_strategy= wt_strategy=
+merge_was_ok=
+for strategy in $use_strategies
+do
+ test "$wt_strategy" = '' || {
+ echo "Rewinding the tree to pristine..."
+ restorestate
+ }
+ case "$single_strategy" in
+ no)
+ echo "Trying merge strategy $strategy..."
+ ;;
+ esac
+
+ # Remember which strategy left the state in the working tree
+ wt_strategy=$strategy
+
+ git-merge-$strategy $common -- "$head_arg" "$@"
+ exit=$?
+ if test "$no_commit" = t && test "$exit" = 0
+ then
+ merge_was_ok=t
+ exit=1 ;# pretend it left conflicts.
+ fi
+
+ test "$exit" = 0 || {
+
+ # The backend exits with 1 when conflicts are left to be resolved,
+ # with 2 when it does not handle the given merge at all.
+
+ if test "$exit" -eq 1
+ then
+ cnt=`{
+ git diff-files --name-only
+ git ls-files --unmerged
+ } | wc -l`
+ if test $best_cnt -le 0 -o $cnt -le $best_cnt
+ then
+ best_strategy=$strategy
+ best_cnt=$cnt
+ fi
+ fi
+ continue
+ }
+
+ # Automerge succeeded.
+ result_tree=$(git write-tree) && break
+done
+
+# If we have a resulting tree, that means the strategy module
+# auto resolved the merge cleanly.
+if test '' != "$result_tree"
+then
+ if test "$allow_fast_forward" = "t"
+ then
+ parents=$(git show-branch --independent "$head" "$@")
+ else
+ parents=$(git rev-parse "$head" "$@")
+ fi
+ parents=$(echo "$parents" | sed -e 's/^/-p /')
+ result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
+ finish "$result_commit" "Merge made by $wt_strategy."
+ dropsave
+ exit 0
+fi
+
+# Pick the result from the best strategy and have the user fix it up.
+case "$best_strategy" in
+'')
+ restorestate
+ case "$use_strategies" in
+ ?*' '?*)
+ echo >&2 "No merge strategy handled the merge."
+ ;;
+ *)
+ echo >&2 "Merge with strategy $use_strategies failed."
+ ;;
+ esac
+ exit 2
+ ;;
+"$wt_strategy")
+ # We already have its result in the working tree.
+ ;;
+*)
+ echo "Rewinding the tree to pristine..."
+ restorestate
+ echo "Using the $best_strategy to prepare resolving by hand."
+ git-merge-$best_strategy $common -- "$head_arg" "$@"
+ ;;
+esac
+
+if test "$squash" = t
+then
+ finish
+else
+ for remote
+ do
+ echo $remote
+ done >"$GIT_DIR/MERGE_HEAD"
+ printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG"
+fi
+
+if test "$merge_was_ok" = t
+then
+ echo >&2 \
+ "Automatic merge went well; stopped before committing as requested"
+ exit 0
+else
+ {
+ echo '
+Conflicts:
+'
+ git ls-files --unmerged |
+ sed -e 's/^[^ ]* / /' |
+ uniq
+ } >>"$GIT_DIR/MERGE_MSG"
+ git rerere
+ die "Automatic merge failed; fix conflicts and then commit the result."
+fi
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--;
}
/*
static int reuseaddr;
static const char daemon_usage[] =
-"git-daemon [--verbose] [--syslog] [--export-all]\n"
+"git daemon [--verbose] [--syslog] [--export-all]\n"
" [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
" [--base-path=path] [--base-path-relaxed]\n"
" [--user-path | --user-path=path]\n"
if (silent_on_removed)
continue;
diff_addremove(&revs->diffopt, '-', ce->ce_mode,
- ce->sha1, ce->name, NULL);
+ ce->sha1, ce->name);
continue;
}
changed = ce_match_stat(ce, &st, ce_option);
newmode = ce_mode_from_stat(ce, st.st_mode);
diff_change(&revs->diffopt, oldmode, newmode,
ce->sha1, (changed ? null_sha1 : ce->sha1),
- ce->name, NULL);
+ ce->name);
}
diffcore_std(&revs->diffopt);
const unsigned char *sha1, unsigned int mode)
{
diff_addremove(&revs->diffopt, prefix[0], mode,
- sha1, ce->name, NULL);
+ sha1, ce->name);
}
static int get_stat_data(struct cache_entry *ce,
return 0;
diff_change(&revs->diffopt, oldmode, mode,
- old->sha1, sha1, old->name, NULL);
+ old->sha1, sha1, old->name);
return 0;
}
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *base, const char *path)
+ const char *concatpath)
{
- char concatpath[PATH_MAX];
struct diff_filespec *one, *two;
if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
addremove = (addremove == '+' ? '-' :
addremove == '-' ? '+' : addremove);
- if (!path) path = "";
- sprintf(concatpath, "%s%s", base, path);
-
if (options->prefix &&
strncmp(concatpath, options->prefix, options->prefix_length))
return;
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *base, const char *path)
+ const char *concatpath)
{
- char concatpath[PATH_MAX];
struct diff_filespec *one, *two;
if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
}
- if (!path) path = "";
- sprintf(concatpath, "%s%s", base, path);
if (options->prefix &&
strncmp(concatpath, options->prefix, options->prefix_length))
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *base, const char *path);
+ const char *fullpath);
typedef void (*add_remove_fn_t)(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *base, const char *path);
+ const char *fullpath);
typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
struct diff_options *options, void *data);
int addremove,
unsigned mode,
const unsigned char *sha1,
- const char *base,
- const char *path);
+ const char *fullpath);
extern void diff_change(struct diff_options *,
unsigned mode1, unsigned mode2,
const unsigned char *sha1,
const unsigned char *sha2,
- const char *base, const char *path);
+ const char *fullpath);
extern void diff_unmerge(struct diff_options *,
const char *path,
#endif
}
+const char *system_path(const char *path)
+{
+ if (!is_absolute_path(path)) {
+ struct strbuf d = STRBUF_INIT;
+ strbuf_addf(&d, "%s/%s", git_exec_path(), path);
+ path = strbuf_detach(&d, NULL);
+ }
+ return path;
+}
+
void git_set_argv_exec_path(const char *exec_path)
{
argv_exec_path = exec_path;
extern void setup_path(const char *);
extern int execv_git_cmd(const char **argv); /* NULL terminated */
extern int execl_git_cmd(const char *cmd, ...);
-
+extern const char *system_path(const char *path);
#endif /* GIT_EXEC_CMD_H */
}
static const char fast_import_usage[] =
-"git-fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
+"git fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
int main(int argc, const char **argv)
{
$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");
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 ? "\"$_\"": $_ } @_;
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;
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]);
-
- 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;
- }
+sub edit_hunk_manually {
+ my ($oldtext) = @_;
- last if ($o1_ofs <= $ofs);
+ 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;
- $o_cnt++;
- $ofs++;
- if ($line =~ /^ /) {
- $n_cnt++;
- }
- push @line, $line;
+ my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor")
+ || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+ system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
+
+ 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];
+ }
+ return \@newtext;
+}
+
+sub diff_applies {
+ my $fh;
+ open $fh, '| git apply --recount --cached --check';
+ for my $h (@_) {
+ print $fh @{$h->{TEXT}};
}
- my $head = ("@@ -$o0_ofs" .
- (($o_cnt != 1) ? ",$o_cnt" : '') .
- " +$n0_ofs" .
- (($n_cnt != 1) ? ",$n_cnt" : '') .
- " @@\n");
- @{$prev->{TEXT}} = ($head, @line);
+ return close $fh;
}
-sub coalesce_overlapping_hunks {
- my (@in) = @_;
- my @out = ();
+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;
+ }
+}
- my ($last_o_ctx);
+sub edit_hunk_loop {
+ my ($head, $hunk, $ix) = @_;
+ my $text = $hunk->[$ix]->{TEXT};
- 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], $_);
+ 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 {
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
}
if (hunk_splittable($hunk[$ix]{TEXT})) {
$other .= '/s';
}
+ $other .= '/e';
for (@{$hunk[$ix]{DISPLAY}}) {
print;
}
$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;
}
}
- @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 $_;
}
SUBDIRECTORY_OK=Yes
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
-git-am [options] <mbox>|<Maildir>...
-git-am [options] --resolved
-git-am [options] --skip
+git am [options] [<mbox>|<Maildir>...]
+git am [options] --resolved
+git am [options] --skip
--
d,dotest= (removed -- do not use)
i,interactive run interactively
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"
}
prec=4
-dotest=".dotest"
+dotest="$GIT_DIR/rebase"
sign= utf8=t keep= skip= interactive= resolved= binary= rebasing=
resolvemsg= resume=
git_apply_opt=
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
: >"$dotest/rebasing"
else
: >"$dotest/applying"
+ git update-ref ORIG_HEAD HEAD
fi
fi
<"$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
}
=head1 Invocation
- git-archimport [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ]
+ git archimport [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ]
[ -D depth] [ -t tempdir ] <archive>/<branch> [ <archive>/<branch> ]
Imports a project from one or more Arch repositories. It will follow branches
sub usage() {
print STDERR <<END;
-Usage: ${\basename $0} # fetch/update GIT from Arch
+Usage: git archimport # fetch/update GIT from Arch
[ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ] [ -D depth ] [ -t tempdir ]
repository/arch-branch [ repository/arch-branch] ...
END
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
sub usage {
print STDERR <<END;
-Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-u] [-w cvsworkdir] [-m msgprefix] [ parent ] commit
+Usage: GIT_DIR=/path/to/.git git cvsexportcommit [-h] [-p] [-v] [-c] [-f] [-u] [-w cvsworkdir] [-m msgprefix] [ parent ] commit
END
exit(1);
}
my $msg = shift;
print(STDERR "Error: $msg\n") if $msg;
print STDERR <<END;
-Usage: ${\basename $0} # fetch/update GIT from CVS
+Usage: git cvsimport # fetch/update GIT from CVS
[-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
[-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
[-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
$log->info("--------------- STARTING -----------------");
my $usage =
- "Usage: git-cvsserver [options] [pserver|server] [<directory> ...]\n".
+ "Usage: git cvsserver [options] [pserver|server] [<directory> ...]\n".
" --base-path <path> : Prepend to requested CVSROOT\n".
" --strict-paths : Don't allow recursing into subdirectories\n".
" --export-all : Don't check for gitcvs.enabled in config\n".
}
# done; get out of the tempdir
- cleanupWorkDir();
+ cleanupWorkTree();
print "ok\n";
}
}
-.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
#
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]}
PERL='@@PERL@@'
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
-git-instaweb [options] (--start | --stop | --restart)
+git instaweb [options] (--start | --stop | --restart)
--
l,local only bind on 127.0.0.1
p,port= the port to bind to
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-OPTIONS_KEEPDASHDASH=
-OPTIONS_SPEC="\
-git-merge [options] <remote>...
-git-merge [options] <msg> HEAD <remote>
---
-stat show a diffstat at the end of the merge
-n don't show a diffstat at the end of the merge
-summary (synonym to --stat)
-log add list of one-line log to merge commit message
-squash create a single commit instead of doing a merge
-commit perform a commit if the merge succeeds (default)
-ff allow fast forward (default)
-s,strategy= merge strategy to use
-m,message= message to be used for the merge commit (if any)
-"
-
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-require_work_tree
-cd_to_toplevel
-
-test -z "$(git ls-files -u)" ||
- die "You are in the middle of a conflicted merge."
-
-LF='
-'
-
-all_strategies='recur recursive octopus resolve stupid ours subtree'
-default_twohead_strategies='recursive'
-default_octopus_strategies='octopus'
-no_fast_forward_strategies='subtree ours'
-no_trivial_strategies='recursive recur subtree ours'
-use_strategies=
-
-allow_fast_forward=t
-allow_trivial_merge=t
-squash= no_commit= log_arg=
-
-dropsave() {
- rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
- "$GIT_DIR/MERGE_STASH" || exit 1
-}
-
-savestate() {
- # Stash away any local modifications.
- git stash create >"$GIT_DIR/MERGE_STASH"
-}
-
-restorestate() {
- if test -f "$GIT_DIR/MERGE_STASH"
- then
- git reset --hard $head >/dev/null
- git stash apply $(cat "$GIT_DIR/MERGE_STASH")
- git update-index --refresh >/dev/null
- fi
-}
-
-finish_up_to_date () {
- case "$squash" in
- t)
- echo "$1 (nothing to squash)" ;;
- '')
- echo "$1" ;;
- esac
- dropsave
-}
-
-squash_message () {
- echo Squashed commit of the following:
- echo
- git log --no-merges --pretty=medium ^"$head" $remoteheads
-}
-
-finish () {
- if test '' = "$2"
- then
- rlogm="$GIT_REFLOG_ACTION"
- else
- echo "$2"
- rlogm="$GIT_REFLOG_ACTION: $2"
- fi
- case "$squash" in
- t)
- echo "Squash commit -- not updating HEAD"
- squash_message >"$GIT_DIR/SQUASH_MSG"
- ;;
- '')
- case "$merge_msg" in
- '')
- echo "No merge message -- not updating HEAD"
- ;;
- *)
- git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
- git gc --auto
- ;;
- esac
- ;;
- esac
- case "$1" in
- '')
- ;;
- ?*)
- if test "$show_diffstat" = t
- then
- # We want color (if set), but no pager
- GIT_PAGER='' git diff --stat --summary -M "$head" "$1"
- fi
- ;;
- esac
-
- # Run a post-merge hook
- if test -x "$GIT_DIR"/hooks/post-merge
- then
- case "$squash" in
- t)
- "$GIT_DIR"/hooks/post-merge 1
- ;;
- '')
- "$GIT_DIR"/hooks/post-merge 0
- ;;
- esac
- fi
-}
-
-merge_name () {
- remote="$1"
- rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
- bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
- if test "$rh" = "$bh"
- then
- echo "$rh branch '$remote' of ."
- elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
- git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
- then
- echo "$rh branch '$truname' (early part) of ."
- elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
- then
- sed -e 's/ not-for-merge / /' -e 1q \
- "$GIT_DIR/FETCH_HEAD"
- else
- echo "$rh commit '$remote'"
- fi
-}
-
-parse_config () {
- while test $# != 0; do
- case "$1" in
- -n|--no-stat|--no-summary)
- show_diffstat=false ;;
- --stat|--summary)
- show_diffstat=t ;;
- --log|--no-log)
- log_arg=$1 ;;
- --squash)
- test "$allow_fast_forward" = t ||
- die "You cannot combine --squash with --no-ff."
- squash=t no_commit=t ;;
- --no-squash)
- squash= no_commit= ;;
- --commit)
- no_commit= ;;
- --no-commit)
- no_commit=t ;;
- --ff)
- allow_fast_forward=t ;;
- --no-ff)
- test "$squash" != t ||
- die "You cannot combine --squash with --no-ff."
- allow_fast_forward=f ;;
- -s|--strategy)
- shift
- case " $all_strategies " in
- *" $1 "*)
- use_strategies="$use_strategies$1 " ;;
- *)
- die "available strategies are: $all_strategies" ;;
- esac
- ;;
- -m|--message)
- shift
- merge_msg="$1"
- have_message=t
- ;;
- --)
- shift
- break ;;
- *) usage ;;
- esac
- shift
- done
- args_left=$#
-}
-
-test $# != 0 || usage
-
-have_message=
-
-if branch=$(git-symbolic-ref -q HEAD)
-then
- mergeopts=$(git config "branch.${branch#refs/heads/}.mergeoptions")
- if test -n "$mergeopts"
- then
- parse_config $mergeopts --
- fi
-fi
-
-parse_config "$@"
-while test $args_left -lt $#; do shift; done
-
-if test -z "$show_diffstat"; then
- test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
- test "$(git config --bool merge.stat)" = false && show_diffstat=false
- test -z "$show_diffstat" && show_diffstat=t
-fi
-
-# This could be traditional "merge <msg> HEAD <commit>..." and the
-# way we can tell it is to see if the second token is HEAD, but some
-# people might have misused the interface and used a committish that
-# is the same as HEAD there instead. Traditional format never would
-# have "-m" so it is an additional safety measure to check for it.
-
-if test -z "$have_message" &&
- second_token=$(git rev-parse --verify "$2^0" 2>/dev/null) &&
- head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null) &&
- test "$second_token" = "$head_commit"
-then
- merge_msg="$1"
- shift
- head_arg="$1"
- shift
-elif ! git rev-parse --verify HEAD >/dev/null 2>&1
-then
- # If the merged head is a valid one there is no reason to
- # forbid "git merge" into a branch yet to be born. We do
- # the same for "git pull".
- if test 1 -ne $#
- then
- echo >&2 "Can merge only exactly one commit into empty head"
- exit 1
- fi
-
- rh=$(git rev-parse --verify "$1^0") ||
- die "$1 - not something we can merge"
-
- git update-ref -m "initial pull" HEAD "$rh" "" &&
- git read-tree --reset -u HEAD
- exit
-
-else
- # We are invoked directly as the first-class UI.
- head_arg=HEAD
-
- # All the rest are the commits being merged; prepare
- # the standard merge summary message to be appended to
- # the given message. If remote is invalid we will die
- # later in the common codepath so we discard the error
- # in this loop.
- merge_name=$(for remote
- do
- merge_name "$remote"
- done | git fmt-merge-msg $log_arg
- )
- merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
-fi
-head=$(git rev-parse --verify "$head_arg"^0) || usage
-
-# All the rest are remote heads
-test "$#" = 0 && usage ;# we need at least one remote head.
-set_reflog_action "merge $*"
-
-remoteheads=
-for remote
-do
- remotehead=$(git rev-parse --verify "$remote"^0 2>/dev/null) ||
- die "$remote - not something we can merge"
- remoteheads="${remoteheads}$remotehead "
- eval GITHEAD_$remotehead='"$remote"'
- export GITHEAD_$remotehead
-done
-set x $remoteheads ; shift
-
-case "$use_strategies" in
-'')
- case "$#" in
- 1)
- var="`git config --get pull.twohead`"
- if test -n "$var"
- then
- use_strategies="$var"
- else
- use_strategies="$default_twohead_strategies"
- fi ;;
- *)
- var="`git config --get pull.octopus`"
- if test -n "$var"
- then
- use_strategies="$var"
- else
- use_strategies="$default_octopus_strategies"
- fi ;;
- esac
- ;;
-esac
-
-for s in $use_strategies
-do
- for ss in $no_fast_forward_strategies
- do
- case " $s " in
- *" $ss "*)
- allow_fast_forward=f
- break
- ;;
- esac
- done
- for ss in $no_trivial_strategies
- do
- case " $s " in
- *" $ss "*)
- allow_trivial_merge=f
- break
- ;;
- esac
- done
-done
-
-case "$#" in
-1)
- common=$(git merge-base --all $head "$@")
- ;;
-*)
- common=$(git show-branch --merge-base $head "$@")
- ;;
-esac
-echo "$head" >"$GIT_DIR/ORIG_HEAD"
-
-case "$allow_fast_forward,$#,$common,$no_commit" in
-?,*,'',*)
- # No common ancestors found. We need a real merge.
- ;;
-?,1,"$1",*)
- # If head can reach all the merge then we are up to date.
- # but first the most common case of merging one remote.
- finish_up_to_date "Already up-to-date."
- exit 0
- ;;
-t,1,"$head",*)
- # Again the most common case of merging one remote.
- echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
- git update-index --refresh 2>/dev/null
- msg="Fast forward"
- if test -n "$have_message"
- then
- msg="$msg (no commit created; -m option ignored)"
- fi
- new_head=$(git rev-parse --verify "$1^0") &&
- git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
- finish "$new_head" "$msg" || exit
- dropsave
- exit 0
- ;;
-?,1,?*"$LF"?*,*)
- # We are not doing octopus and not fast forward. Need a
- # real merge.
- ;;
-?,1,*,)
- # We are not doing octopus, not fast forward, and have only
- # one common.
- git update-index --refresh 2>/dev/null
- case "$allow_trivial_merge" in
- t)
- # See if it is really trivial.
- git var GIT_COMMITTER_IDENT >/dev/null || exit
- echo "Trying really trivial in-index merge..."
- if git read-tree --trivial -m -u -v $common $head "$1" &&
- result_tree=$(git write-tree)
- then
- echo "Wonderful."
- result_commit=$(
- printf '%s\n' "$merge_msg" |
- git commit-tree $result_tree -p HEAD -p "$1"
- ) || exit
- finish "$result_commit" "In-index merge"
- dropsave
- exit 0
- fi
- echo "Nope."
- esac
- ;;
-*)
- # An octopus. If we can reach all the remote we are up to date.
- up_to_date=t
- for remote
- do
- common_one=$(git merge-base --all $head $remote)
- if test "$common_one" != "$remote"
- then
- up_to_date=f
- break
- fi
- done
- if test "$up_to_date" = t
- then
- finish_up_to_date "Already up-to-date. Yeeah!"
- exit 0
- fi
- ;;
-esac
-
-# We are going to make a new commit.
-git var GIT_COMMITTER_IDENT >/dev/null || exit
-
-# At this point, we need a real merge. No matter what strategy
-# we use, it would operate on the index, possibly affecting the
-# working tree, and when resolved cleanly, have the desired tree
-# in the index -- this means that the index must be in sync with
-# the $head commit. The strategies are responsible to ensure this.
-
-case "$use_strategies" in
-?*' '?*)
- # Stash away the local changes so that we can try more than one.
- savestate
- single_strategy=no
- ;;
-*)
- rm -f "$GIT_DIR/MERGE_STASH"
- single_strategy=yes
- ;;
-esac
-
-result_tree= best_cnt=-1 best_strategy= wt_strategy=
-merge_was_ok=
-for strategy in $use_strategies
-do
- test "$wt_strategy" = '' || {
- echo "Rewinding the tree to pristine..."
- restorestate
- }
- case "$single_strategy" in
- no)
- echo "Trying merge strategy $strategy..."
- ;;
- esac
-
- # Remember which strategy left the state in the working tree
- wt_strategy=$strategy
-
- git-merge-$strategy $common -- "$head_arg" "$@"
- exit=$?
- if test "$no_commit" = t && test "$exit" = 0
- then
- merge_was_ok=t
- exit=1 ;# pretend it left conflicts.
- fi
-
- test "$exit" = 0 || {
-
- # The backend exits with 1 when conflicts are left to be resolved,
- # with 2 when it does not handle the given merge at all.
-
- if test "$exit" -eq 1
- then
- cnt=`{
- git diff-files --name-only
- git ls-files --unmerged
- } | wc -l`
- if test $best_cnt -le 0 -o $cnt -le $best_cnt
- then
- best_strategy=$strategy
- best_cnt=$cnt
- fi
- fi
- continue
- }
-
- # Automerge succeeded.
- result_tree=$(git write-tree) && break
-done
-
-# If we have a resulting tree, that means the strategy module
-# auto resolved the merge cleanly.
-if test '' != "$result_tree"
-then
- if test "$allow_fast_forward" = "t"
- then
- parents=$(git show-branch --independent "$head" "$@")
- else
- parents=$(git rev-parse "$head" "$@")
- fi
- parents=$(echo "$parents" | sed -e 's/^/-p /')
- result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
- finish "$result_commit" "Merge made by $wt_strategy."
- dropsave
- exit 0
-fi
-
-# Pick the result from the best strategy and have the user fix it up.
-case "$best_strategy" in
-'')
- restorestate
- case "$use_strategies" in
- ?*' '?*)
- echo >&2 "No merge strategy handled the merge."
- ;;
- *)
- echo >&2 "Merge with strategy $use_strategies failed."
- ;;
- esac
- exit 2
- ;;
-"$wt_strategy")
- # We already have its result in the working tree.
- ;;
-*)
- echo "Rewinding the tree to pristine..."
- restorestate
- echo "Using the $best_strategy to prepare resolving by hand."
- git-merge-$best_strategy $common -- "$head_arg" "$@"
- ;;
-esac
-
-if test "$squash" = t
-then
- finish
-else
- for remote
- do
- echo $remote
- done >"$GIT_DIR/MERGE_HEAD"
- printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG"
-fi
-
-if test "$merge_was_ok" = t
-then
- echo >&2 \
- "Automatic merge went well; stopped before committing as requested"
- exit 0
-else
- {
- echo '
-Conflicts:
-'
- git ls-files --unmerged |
- sed -e 's/^[^ ]* / /' |
- uniq
- } >>"$GIT_DIR/MERGE_MSG"
- git rerere
- die "Automatic merge failed; fix conflicts and then commit the result."
-fi
"refs/remotes/$origin/$reflist" 2>/dev/null)"
}
orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
-git-fetch --update-head-ok "$@" || exit 1
+git fetch --update-head-ok "$@" || exit 1
curr_head=$(git rev-parse --verify HEAD 2>/dev/null)
if test "$curr_head" != "$orig_head"
#!/bin/sh
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
-git-quiltimport [options]
+git quiltimport [options]
--
n,dry-run dry run
author= author name and email address for patches without any
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"
# 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
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
new_parents="$new_parents $new_p"
;;
esac
+ else
+ new_parents="$new_parents $p"
fi
done
case $fast_forward in
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"
do_rest
;;
--abort)
+ is_standalone "$@" || usage
+ get_saved_options
comment_for_reflog abort
git rerere clear
exit
;;
--skip)
+ is_standalone "$@" || usage
+ get_saved_options
comment_for_reflog skip
git rerere clear
output git reset --hard && do_rest
;;
- -s|--strategy)
+ -s)
case "$#,$1" in
*,*=*)
STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
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"
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"
has_action "$TODO" ||
die_abort "Nothing to do"
+ git update-ref ORIG_HEAD $HEAD
output git checkout $ONTO && do_rest
;;
esac
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
unset newbase
strategy=recursive
do_merge=
-dotest=$GIT_DIR/.dotest-merge
+dotest="$GIT_DIR"/rebase-merge
prec=4
verbose=
git_am_opt=
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 || {
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 $?
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
then
move_to_original_branch
else
- dotest=.dotest
+ dotest="$GIT_DIR"/rebase
move_to_original_branch
fi
git reset --hard $(cat "$dotest/orig-head")
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
# Detach HEAD and reset the tree
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 checkout -q "$onto^0" || die "could not detach HEAD"
+git update-ref ORIG_HEAD $branch
# If the $onto is a proper descendant of the tip of the branch, then
# we just fast forwarded.
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
sub usage() {
- print("Usage: $0 [--safe] <dir> [<dir> ...] <master_dir> \n");
+ print("Usage: git relink [--safe] <dir> [<dir> ...] <master_dir> \n");
print("All directories should contain a .git/objects/ subdirectory.\n");
print("Options\n");
print("\t--safe\t" .
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
-git-repack [options]
+git repack [options]
--
a pack everything in a single pack
A same as -a, and turn unreachable objects loose
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
sub usage {
print <<EOT;
-git-send-email [options] <file | directory>...
+git send-email [options] <file | directory>...
Options:
--from Specify the "From:" line of the email to be sent.
echo exit $?
)"
else
+ dashless=$(basename "$0" | sed -e 's/-/ /')
usage() {
- die "Usage: $0 $USAGE"
+ die "Usage: $dashless $USAGE"
}
if [ -z "$LONG_USAGE" ]
then
- LONG_USAGE="Usage: $0 $USAGE"
+ LONG_USAGE="Usage: $dashless $USAGE"
else
- LONG_USAGE="Usage: $0 $USAGE
+ LONG_USAGE="Usage: $dashless $USAGE
$LONG_USAGE"
fi
}
save_stash () {
+ keep_index=
+ case "$1" in
+ --keep-index)
+ keep_index=t
+ shift
+ esac
+
stash_msg="$1"
if no_changes
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 () {
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 &&
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)
;;
save)
shift
- save_stash "$*" && git-reset --hard
+ save_stash "$*"
;;
apply)
shift
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
# 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
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 ()
{
#
# Add a new submodule to the working tree, .gitmodules and the index
#
-# $@ = repo [path]
+# $@ = repo path
#
# optional branch is stored in global branch variable
#
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"
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"}) ||
my $fd = $exit ? \*STDERR : \*STDOUT;
print $fd <<"";
git-svn - bidirectional operations between a single Subversion tree and git
-Usage: $0 <command> [options] [arguments]\n
+Usage: git svn <command> [options] [arguments]\n
print $fd "Available commands:\n" unless $cmd;
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;
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);
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)=
}
}
}
+
+ 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;
}
}
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;
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 = $_;
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;
{ "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 },
}
static const char hash_object_usage[] =
-"git-hash-object [ [-t <type>] [-w] [--stdin] <file>... | --stdin-paths < <list-of-paths> ]";
+"git hash-object [ [-t <type>] [-w] [--stdin] <file>... | --stdin-paths < <list-of-paths> ]";
int main(int argc, char **argv)
{
};
static const char * const builtin_help_usage[] = {
- "git-help [--all] [--man|--web|--info] [command]",
+ "git help [--all] [--man|--web|--info] [command]",
NULL
};
static void get_html_page_path(struct strbuf *page_path, const char *page)
{
struct stat st;
+ const char *html_path = system_path(GIT_HTML_PATH);
/* Check that we have a git documentation directory. */
- if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
- die("'%s': not a documentation directory.", GIT_HTML_PATH);
+ if (stat(mkpath("%s/git.html", html_path), &st)
+ || !S_ISREG(st.st_mode))
+ die("'%s': not a documentation directory.", html_path);
strbuf_init(page_path, 0);
- strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
+ strbuf_addf(page_path, "%s/%s.html", html_path, page);
}
+/*
+ * If open_html is not defined in a platform-specific way (see for
+ * example compat/mingw.h), we use the script web--browse to display
+ * HTML.
+ */
+#ifndef open_html
+void open_html(const char *path)
+{
+ execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
+}
+#endif
+
static void show_html_page(const char *git_cmd)
{
const char *page = cmd_to_page(git_cmd);
get_html_page_path(&page_path, page);
- execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
+ open_html(page_path.buf);
}
void help_unknown_cmd(const char *cmd)
#include <expat.h>
static const char http_push_usage[] =
-"git-http-push [--all] [--dry-run] [--force] [--verbose] <remote> [<head>...]\n";
+"git http-push [--all] [--dry-run] [--force] [--verbose] <remote> [<head>...]\n";
#ifndef XML_STATUS_OK
enum XML_Status {
#include "fsck.h"
static const char index_pack_usage[] =
-"git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
+"git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
struct object_entry
{
off_t offset;
};
+struct base_data {
+ struct base_data *base;
+ struct base_data *child;
+ struct object_entry *obj;
+ void *data;
+ unsigned long size;
+};
+
/*
* Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
* to memcmp() only the first 20 bytes.
static struct object_entry *objects;
static struct delta_entry *deltas;
+static struct base_data *base_cache;
+static size_t base_cache_used;
static int nr_objects;
static int nr_deltas;
static int nr_resolved_deltas;
die("pack has bad object at offset %lu: %s", offset, buf);
}
+static void prune_base_data(struct base_data *retain)
+{
+ struct base_data *b = base_cache;
+ for (b = base_cache;
+ base_cache_used > delta_base_cache_limit && b;
+ b = b->child) {
+ if (b->data && b != retain) {
+ free(b->data);
+ b->data = NULL;
+ base_cache_used -= b->size;
+ }
+ }
+}
+
+static void link_base_data(struct base_data *base, struct base_data *c)
+{
+ if (base)
+ base->child = c;
+ else
+ base_cache = c;
+
+ c->base = base;
+ c->child = NULL;
+ base_cache_used += c->size;
+ prune_base_data(c);
+}
+
+static void unlink_base_data(struct base_data *c)
+{
+ struct base_data *base = c->base;
+ if (base)
+ base->child = NULL;
+ else
+ base_cache = NULL;
+ if (c->data) {
+ free(c->data);
+ base_cache_used -= c->size;
+ }
+}
+
static void *unpack_entry_data(unsigned long offset, unsigned long size)
{
z_stream stream;
}
}
-static void resolve_delta(struct object_entry *delta_obj, void *base_data,
- unsigned long base_size, enum object_type type)
+static void *get_base_data(struct base_data *c)
+{
+ if (!c->data) {
+ struct object_entry *obj = c->obj;
+
+ if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+ void *base = get_base_data(c->base);
+ void *raw = get_data_from_pack(obj);
+ c->data = patch_delta(
+ base, c->base->size,
+ raw, obj->size,
+ &c->size);
+ free(raw);
+ if (!c->data)
+ bad_object(obj->idx.offset, "failed to apply delta");
+ } else
+ c->data = get_data_from_pack(obj);
+
+ base_cache_used += c->size;
+ prune_base_data(c);
+ }
+ return c->data;
+}
+
+static void resolve_delta(struct object_entry *delta_obj,
+ struct base_data *base_obj, enum object_type type)
{
void *delta_data;
unsigned long delta_size;
- void *result;
- unsigned long result_size;
union delta_base delta_base;
int j, first, last;
+ struct base_data result;
delta_obj->real_type = type;
delta_data = get_data_from_pack(delta_obj);
delta_size = delta_obj->size;
- result = patch_delta(base_data, base_size, delta_data, delta_size,
- &result_size);
+ result.data = patch_delta(get_base_data(base_obj), base_obj->size,
+ delta_data, delta_size,
+ &result.size);
free(delta_data);
- if (!result)
+ if (!result.data)
bad_object(delta_obj->idx.offset, "failed to apply delta");
- sha1_object(result, result_size, type, delta_obj->idx.sha1);
+ sha1_object(result.data, result.size, type, delta_obj->idx.sha1);
nr_resolved_deltas++;
+ result.obj = delta_obj;
+ link_base_data(base_obj, &result);
+
hashcpy(delta_base.sha1, delta_obj->idx.sha1);
if (!find_delta_children(&delta_base, &first, &last)) {
for (j = first; j <= last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_REF_DELTA)
- resolve_delta(child, result, result_size, type);
+ resolve_delta(child, &result, type);
}
}
for (j = first; j <= last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_OFS_DELTA)
- resolve_delta(child, result, result_size, type);
+ resolve_delta(child, &result, type);
}
}
- free(result);
+ unlink_base_data(&result);
}
static int compare_delta_entry(const void *a, const void *b)
{
int i;
struct delta_entry *delta = deltas;
- void *data;
struct stat st;
/*
nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- data = unpack_raw_entry(obj, &delta->base);
+ void *data = unpack_raw_entry(obj, &delta->base);
obj->real_type = obj->type;
if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
nr_deltas++;
struct object_entry *obj = &objects[i];
union delta_base base;
int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last;
+ struct base_data base_obj;
if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
continue;
ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
if (!ref && !ofs)
continue;
- data = get_data_from_pack(obj);
+ base_obj.data = get_data_from_pack(obj);
+ base_obj.size = obj->size;
+ base_obj.obj = obj;
+ link_base_data(NULL, &base_obj);
+
if (ref)
for (j = ref_first; j <= ref_last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_REF_DELTA)
- resolve_delta(child, data,
- obj->size, obj->type);
+ resolve_delta(child, &base_obj, obj->type);
}
if (ofs)
for (j = ofs_first; j <= ofs_last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_OFS_DELTA)
- resolve_delta(child, data,
- obj->size, obj->type);
+ resolve_delta(child, &base_obj, obj->type);
}
- free(data);
+ unlink_base_data(&base_obj);
display_progress(progress, nr_resolved_deltas);
}
}
return size;
}
-static void append_obj_to_pack(const unsigned char *sha1, void *buf,
+static struct object_entry *append_obj_to_pack(
+ const unsigned char *sha1, void *buf,
unsigned long size, enum object_type type)
{
struct object_entry *obj = &objects[nr_objects++];
obj[1].idx.offset = obj[0].idx.offset + n;
obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32);
hashcpy(obj->idx.sha1, sha1);
+ return obj;
}
static int delta_pos_compare(const void *_a, const void *_b)
for (i = 0; i < n; i++) {
struct delta_entry *d = sorted_by_pos[i];
- void *data;
- unsigned long size;
enum object_type type;
int j, first, last;
+ struct base_data base_obj;
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- data = read_sha1_file(d->base.sha1, &type, &size);
- if (!data)
+ base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
+ if (!base_obj.data)
continue;
+ if (check_sha1_signature(d->base.sha1, base_obj.data,
+ base_obj.size, typename(type)))
+ die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+ base_obj.obj = append_obj_to_pack(d->base.sha1, base_obj.data,
+ base_obj.size, type);
+ link_base_data(NULL, &base_obj);
+
find_delta_children(&d->base, &first, &last);
for (j = first; j <= last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_REF_DELTA)
- resolve_delta(child, data, size, type);
+ resolve_delta(child, &base_obj, type);
}
- if (check_sha1_signature(d->base.sha1, data, size, typename(type)))
- die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
- append_obj_to_pack(d->base.sha1, data, size, type);
- free(data);
+ unlink_base_data(&base_obj);
display_progress(progress, nr_resolved_deltas);
}
free(sorted_by_pos);
#define BLKSIZE 512
static const char pack_redundant_usage[] =
-"git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
+"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
static int load_all_packs, verbose, alt_odb;
#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)
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;
}
}
- arg = p->opt ? p->opt : (p->argc > 1 ? p->argv[1] : NULL);
switch (opt->type) {
case OPTION_BIT:
if (unset)
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) {
*(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;
}
}
-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) {
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, '=');
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)
+static void check_typos(const char *arg, const struct option *options)
{
if (strlen(arg) < 3)
return;
}
}
-static NORETURN void usage_with_options_internal(const char * const *,
- const struct option *, int);
+void parse_options_start(struct parse_opt_ctx_t *ctx,
+ int argc, const char **argv, int flags)
+{
+ 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;
+}
-int parse_options(int argc, const char **argv, const struct option *options,
- const char * const usagestr[], int flags)
+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[])
{
- struct optparse_t args = { argv + 1, argv, argc - 1, 0, NULL };
+ /* we must reset ->opt, unknown short option leave it dangling */
+ ctx->opt = NULL;
- for (; args.argc; args.argc--, args.argv++) {
- const char *arg = args.argv[0];
+ 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;
+}
- memmove(args.out + args.cpidx, args.argv, args.argc * sizeof(*args.out));
- args.out[args.cpidx + args.argc] = NULL;
- return args.cpidx + args.argc;
+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);
+ }
+
+ 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)
}
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"
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 {
PARSE_OPT_NOARG = 2,
PARSE_OPT_NONEG = 4,
PARSE_OPT_HIDDEN = 8,
+ PARSE_OPT_LASTARG_DEFAULT = 16,
};
struct option;
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);
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)
#include "utf8.h"
#include "diff.h"
#include "revision.h"
+#include "path-list.h"
+#include "mailmap.h"
static char *user_format;
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)
{
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 */
}
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;
+}
#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,
}
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;
+}
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
--- /dev/null
+#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);
+}
--- /dev/null
+#ifndef RERERE_H
+#define RERERE_H
+
+#include "path-list.h"
+
+extern int setup_rerere(struct path_list *);
+extern int rerere(void);
+
+#endif
#include "grep.h"
#include "reflog-walk.h"
#include "patch-ids.h"
+#include "decorate.h"
volatile show_early_output_fn_t show_early_output;
static void file_add_remove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *base, const char *path)
+ const char *fullpath)
{
int diff = REV_TREE_DIFFERENT;
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *base, const char *path)
+ const char *fullpath)
{
tree_difference = REV_TREE_DIFFERENT;
DIFF_OPT_SET(options, HAS_CHANGES);
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;
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;
}
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;
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);
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.
*/
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;
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;
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;
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;
}
}
}
- 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! */
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
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;
return -1;
if (revs->topo_order)
sort_in_topological_order(&revs->commits, revs->lifo);
+ if (revs->children.name)
+ set_children(revs);
return 0;
}
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;
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)
/* 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;
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;
}
#ifndef REVISION_H
#define REVISION_H
+#include "parse-options.h"
+
#define SEEN (1u<<0)
#define UNINTERESTING (1u<<1)
#define TREESAME (1u<<2)
#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;
/* Basic information */
const char *prefix;
+ const char *def;
void *prune_data;
unsigned int early_output;
/* Format info */
unsigned int shown_one:1,
+ show_merge:1,
abbrev_commit:1,
use_terminator:1,
missing_newline:1;
struct diff_options pruning;
struct reflog_walk_info *reflog_info;
+ struct decoration children;
};
#define REV_TREE_SAME 0
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);
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];
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;
}
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;
}
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;
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,
int wrap;
int in1;
int in2;
+ int user_format;
char *common_repo_prefix;
int email;
ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
}
+void strbuf_trim(struct strbuf *sb)
+{
+ char *b = sb->buf;
+ while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
+ sb->len--;
+ while (sb->len > 0 && isspace(*b)) {
+ b++;
+ sb->len--;
+ }
+ memmove(sb->buf, b, sb->len);
+ sb->buf[sb->len] = '\0';
+}
void strbuf_rtrim(struct strbuf *sb)
{
while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
sb->buf[sb->len] = '\0';
}
-int strbuf_cmp(struct strbuf *a, struct strbuf *b)
+void strbuf_ltrim(struct strbuf *sb)
+{
+ char *b = sb->buf;
+ while (sb->len > 0 && isspace(*b)) {
+ b++;
+ sb->len--;
+ }
+ memmove(sb->buf, b, sb->len);
+ sb->buf[sb->len] = '\0';
+}
+
+void strbuf_tolower(struct strbuf *sb)
+{
+ int i;
+ for (i = 0; i < sb->len; i++)
+ sb->buf[i] = tolower(sb->buf[i]);
+}
+
+struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
+{
+ int alloc = 2, pos = 0;
+ char *n, *p;
+ struct strbuf **ret;
+ struct strbuf *t;
+
+ ret = xcalloc(alloc, sizeof(struct strbuf *));
+ p = n = sb->buf;
+ while (n < sb->buf + sb->len) {
+ int len;
+ n = memchr(n, delim, sb->len - (n - sb->buf));
+ if (pos + 1 >= alloc) {
+ alloc = alloc * 2;
+ ret = xrealloc(ret, sizeof(struct strbuf *) * alloc);
+ }
+ if (!n)
+ n = sb->buf + sb->len - 1;
+ len = n - p + 1;
+ t = xmalloc(sizeof(struct strbuf));
+ strbuf_init(t, len);
+ strbuf_add(t, p, len);
+ ret[pos] = t;
+ ret[++pos] = NULL;
+ p = ++n;
+ }
+ return ret;
+}
+
+void strbuf_list_free(struct strbuf **sbs)
+{
+ struct strbuf **s = sbs;
+
+ while (*s) {
+ strbuf_release(*s);
+ free(*s++);
+ }
+ free(sbs);
+}
+
+int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)
{
int cmp;
if (a->len < b->len) {
}
/*----- strbuf size related -----*/
-static inline size_t strbuf_avail(struct strbuf *sb) {
+static inline size_t strbuf_avail(const struct strbuf *sb) {
return sb->alloc ? sb->alloc - sb->len - 1 : 0;
}
#define strbuf_reset(sb) strbuf_setlen(sb, 0)
/*----- content related -----*/
+extern void strbuf_trim(struct strbuf *);
extern void strbuf_rtrim(struct strbuf *);
-extern int strbuf_cmp(struct strbuf *, struct strbuf *);
+extern void strbuf_ltrim(struct strbuf *);
+extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
+extern void strbuf_tolower(struct strbuf *);
+
+extern struct strbuf **strbuf_split(const struct strbuf *, int delim);
+extern void strbuf_list_free(struct strbuf **);
/*----- add data in your buffer -----*/
static inline void strbuf_addch(struct strbuf *sb, int c) {
static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
strbuf_add(sb, s, strlen(s));
}
-static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) {
+static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
strbuf_add(sb, sb2->buf, sb2->len);
}
extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
$(RM) -r 'trash directory' test-results
aggregate-results:
- ./aggregate-results.sh test-results/t*-*
+ '$(SHELL_PATH_SQ)' ./aggregate-results.sh test-results/t*-*
# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
full-svn-test:
do
while read type value
do
- case $type in
- '')
- continue ;;
+ case $type in
+ '')
+ continue ;;
fixed)
fixed=$(($fixed + $value)) ;;
success)
failed)
failed=$(($failed + $value)) ;;
broken)
- broken=$(( $broken + $value)) ;;
+ broken=$(($broken + $value)) ;;
total)
- total=$(( $total + $value)) ;;
+ total=$(($total + $value)) ;;
esac
done <"$file"
done
LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so
<Location /$repo_base_path>
DAV svn
- SVNPath $rawsvnrepo
+ SVNPath "$rawsvnrepo"
</Location>
EOF
"$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
# updating a new file without --add should fail.
test_expect_success 'git update-index without --add should fail adding.' '
- ! git update-index should-be-empty
+ test_must_fail git update-index should-be-empty
'
# and with --add it should succeed, even if it is empty (it used to fail).
# Removing paths.
rm -f should-be-empty full-of-directories
test_expect_success 'git update-index without --remove should fail removing.' '
- ! git update-index should-be-empty
+ test_must_fail git update-index should-be-empty
'
test_expect_success \
'git update-index --index-info < badobjects'
test_expect_success 'writing this tree without --missing-ok.' '
- ! git write-tree
+ test_must_fail git write-tree
'
test_expect_success \
>file &&
git add file &&
+ test_tick &&
git commit -m initial &&
echo >file &&
git add file
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)
'
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)
'
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)
'
(
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)
'
git config core.safecrlf true &&
for w in I am all CRLF; do echo $w; done | append_cr >allcrlf &&
- ! git add allcrlf
+ test_must_fail git add allcrlf
'
test_expect_success 'safecrlf: autocrlf=input, mixed LF/CRLF' '
git config core.safecrlf true &&
for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed &&
- ! git add mixed
+ test_must_fail git add mixed
'
test_expect_success 'safecrlf: autocrlf=true, all LF' '
git config core.safecrlf true &&
for w in I am all LF; do echo $w; done >alllf &&
- ! git add alllf
+ test_must_fail git add alllf
'
test_expect_success 'safecrlf: autocrlf=true mixed LF/CRLF' '
git config core.safecrlf true &&
for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed &&
- ! git add mixed
+ test_must_fail git add mixed
'
test_expect_success 'safecrlf: print warning only once' '
else
test_expect_success "detection of case insensitive filesystem during repo init" '
- ! git config --bool core.ignorecase >/dev/null ||
+ test_must_fail git config --bool core.ignorecase >/dev/null ||
test $(git config --bool core.ignorecase) = false
'
fi
git commit -m 'Some fun.' -i hello example
test_expect_success 'git resolve now fails' '
- ! git merge -m "Merge work in mybranch" mybranch
+ test_must_fail git merge -m "Merge work in mybranch" mybranch
'
cat > hello << EOF
'test wow = $(git config --get nextsection.nonewline !for)'
test_expect_success 'ambiguous get' '
- ! git config --get nextsection.nonewline
+ test_must_fail git config --get nextsection.nonewline
'
test_expect_success 'get multivar' \
test_expect_success 'multivar replace' 'cmp .git/config expect'
test_expect_success 'ambiguous value' '
- ! git config nextsection.nonewline
+ test_must_fail git config nextsection.nonewline
'
test_expect_success 'ambiguous unset' '
- ! git config --unset nextsection.nonewline
+ test_must_fail git config --unset nextsection.nonewline
'
test_expect_success 'invalid unset' '
- ! git config --unset somesection.nonewline
+ test_must_fail git config --unset somesection.nonewline
'
git config --unset nextsection.nonewline "wow3$"
test_expect_success 'multivar unset' 'cmp .git/config expect'
-test_expect_success 'invalid key' '! git config inval.2key blabla'
+test_expect_success 'invalid key' 'test_must_fail git config inval.2key blabla'
test_expect_success 'correct key' 'git config 123456.a123 987'
test_expect_success "rename succeeded" "test_cmp expect .git/config"
test_expect_success "rename non-existing section" '
- ! git config --rename-section branch."world domination" branch.drei
+ test_must_fail git config --rename-section \
+ branch."world domination" branch.drei
'
test_expect_success "rename succeeded" "test_cmp expect .git/config"
test_expect_success 'invalid bool (--get)' '
git config bool.nobool foobar &&
- ! git config --bool --get bool.nobool'
+ test_must_fail git config --bool --get bool.nobool'
test_expect_success 'invalid bool (set)' '
- ! git config --bool bool.nobool foobar'
+ test_must_fail git config --bool bool.nobool foobar'
rm .git/config
test_expect_success 'quoting' 'cmp .git/config expect'
test_expect_success 'key with newline' '
- ! git config "key.with
+ test_must_fail git config "key.with
newline" 123'
test_expect_success 'value with newline' 'git config key.sub value.with\\\
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 &&
cd test && git apply --check --index ../test.patch)'
test_expect_success 'gitdir required mode on unsupported repo' '
- (cd test2 && ! git apply --check --index ../test.patch)
+ (cd test2 && test_must_fail git apply --check --index ../test.patch)
'
test_done
rm -f .git/$m
test_expect_success '(not) create HEAD with old sha1' "
- ! git update-ref HEAD $A $B
+ test_must_fail git update-ref HEAD $A $B
"
test_expect_success "(not) prior created .git/$m" "
! test -f .git/$m
"create HEAD" \
"git update-ref HEAD $A"
test_expect_success '(not) change HEAD with wrong SHA1' "
- ! git update-ref HEAD $B $Z
+ test_must_fail git update-ref HEAD $B $Z
"
test_expect_success "(not) changed .git/$m" "
! test $B"' = $(cat .git/'"$m"')
test_expect_success \
'git checkout-index without -f should fail on conflicting work tree.' \
- '! git checkout-index -a'
+ 'test_must_fail git checkout-index -a'
test_expect_success \
'git checkout-index with -f should succeed.' \
do
test_expect_success \
"git update-index to add conflicting path $p should fail." \
- "! git update-index --add -- $p"
+ "test_must_fail git update-index --add -- $p"
done
test_done
test_expect_success \
'git ls-files --error-unmatch should fail with unmatched path.' \
- '! git ls-files --error-unmatch foo bar-does-not-match'
+ 'test_must_fail git ls-files --error-unmatch foo bar-does-not-match'
test_expect_success \
'git ls-files --error-unmatch should succeed eith matched paths.' \
test_expect_success 'git branch -m o/o o should fail when o/p exists' '
git branch o/o &&
git branch o/p &&
- ! git branch -m o/o o
+ test_must_fail git branch -m o/o o
'
test_expect_success 'git branch -m q r/q should fail when r exists' '
git branch q &&
git branch r &&
- ! git branch -m q r/q
+ test_must_fail git branch -m q r/q
'
mv .git/config .git/config-saved
test_expect_success 'config information was renamed, too' \
"test $(git config branch.s.dummy) = Hello &&
- ! git config branch.s/s/dummy"
+ test_must_fail git config branch.s/s/dummy"
test_expect_success \
'git branch -m u v should fail when the reflog for u is a symlink' '
git branch -l u &&
mv .git/logs/refs/heads/u real-u &&
ln -s real-u .git/logs/refs/heads/u &&
- ! git branch -m u v
+ test_must_fail git branch -m u v
'
test_expect_success 'test tracking setup via --track' \
git branch c &&
git pack-refs --all &&
rm -f .git/refs/heads/c &&
- ! git branch c/d
+ test_must_fail git branch c/d
'
test_expect_success \
test_expect_success 'git branch i/j/k should barf if branch i exists' '
git branch i &&
git pack-refs --all --prune &&
- ! git branch i/j/k
+ test_must_fail git branch i/j/k
'
test_expect_success \
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
'
test_expect_success 'rebase with git am -3 (default)' '
- ! git rebase master
+ test_must_fail git rebase master
'
test_expect_success 'rebase --skip with am -3' '
test refs/heads/skip-reference = $(git symbolic-ref HEAD) &&
git branch post-rebase &&
git reset --hard pre-rebase &&
- ! git rebase master &&
+ test_must_fail git rebase master &&
echo "hello" > hello &&
git add hello &&
git rebase --continue &&
test_expect_success 'checkout skip-merge' 'git checkout -f skip-merge'
-test_expect_success 'rebase with --merge' '! git rebase --merge master'
+test_expect_success 'rebase with --merge' '
+ test_must_fail git rebase --merge master
+'
test_expect_success 'rebase --skip with --merge' '
git rebase --skip
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' '
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 &&
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' '
'
}
-testrebase "" .dotest
-testrebase " --merge" .git/.dotest-merge
+testrebase "" .git/rebase
+testrebase " --merge" .git/rebase-merge
test_done
git reset --hard &&
git checkout a^0 &&
- ! git cherry-pick -m 1 b &&
+ test_must_fail git cherry-pick -m 1 b &&
git diff --exit-code a --
'
git reset --hard &&
git checkout a^0 &&
- ! git cherry-pick c &&
+ test_must_fail git cherry-pick c &&
git diff --exit-code a --
'
git reset --hard &&
git checkout b^0 &&
- ! git cherry-pick -m 3 c
+ test_must_fail git cherry-pick -m 3 c
'
git reset --hard &&
git checkout c^0 &&
- ! git revert -m 1 b &&
+ test_must_fail git revert -m 1 b &&
git diff --exit-code c
'
git reset --hard &&
git checkout c^0 &&
- ! git revert c &&
+ test_must_fail git revert c &&
git diff --exit-code c
'
git reset --hard &&
git checkout c^0 &&
- ! git revert -m 3 c &&
+ test_must_fail git revert -m 3 c &&
git diff --exit-code c
'
--- /dev/null
+#!/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
echo "other content" > foo
git add foo
echo "yet another content" > foo
- ! git rm --cached foo
+ test_must_fail git rm --cached foo
'
test_expect_success \
test_expect_success \
'Post-check that foo exists but is not in index after git rm foo' \
- '[ -f foo ] && ! git ls-files --error-unmatch foo'
+ '[ -f foo ] && test_must_fail git ls-files --error-unmatch foo'
test_expect_success \
'Pre-check that bar exists and is in index before "git rm bar"' \
test_expect_success \
'Post-check that bar does not exist and is not in index after "git rm -f bar"' \
- '! [ -f bar ] && ! git ls-files --error-unmatch bar'
+ '! [ -f bar ] && test_must_fail git ls-files --error-unmatch bar'
test_expect_success \
'Test that "git rm -- -q" succeeds (remove a file that looks like an option)' \
chmod a-w .
test_expect_success \
'Test that "git rm -f" fails if its rm fails' \
- '! git rm -f baz'
+ 'test_must_fail git rm -f baz'
chmod 775 .
else
test_expect_success 'skipping removal failure (perhaps running as root?)' :
test_expect_success 'Modify foo -- rm should refuse' '
echo >>foo &&
- ! git rm foo baz &&
+ test_must_fail git rm foo baz &&
test -f foo &&
test -f baz &&
git ls-files --error-unmatch foo baz
git rm -f foo baz &&
test ! -f foo &&
test ! -f baz &&
- ! git ls-files --error-unmatch foo &&
- ! git ls-files --error-unmatch bar
+ test_must_fail git ls-files --error-unmatch foo &&
+ test_must_fail git ls-files --error-unmatch bar
'
test_expect_success 'Re-add foo and baz for HEAD tests' '
'
test_expect_success 'foo is different in index from HEAD -- rm should refuse' '
- ! git rm foo baz &&
+ test_must_fail git rm foo baz &&
test -f foo &&
test -f baz &&
git ls-files --error-unmatch foo baz
git rm -f foo baz &&
test ! -f foo &&
test ! -f baz &&
- ! git ls-files --error-unmatch foo
- ! git ls-files --error-unmatch baz
+ test_must_fail git ls-files --error-unmatch foo
+ test_must_fail git ls-files --error-unmatch baz
'
test_expect_success 'Recursive test setup' '
'
test_expect_success 'Recursive without -r fails' '
- ! git rm frotz &&
+ test_must_fail git rm frotz &&
test -d frotz &&
test -f frotz/nitfol
'
test_expect_success 'Recursive with -r but dirty' '
echo qfwfq >>frotz/nitfol
- ! git rm -r frotz &&
+ test_must_fail git rm -r frotz &&
test -d frotz &&
test -f frotz/nitfol
'
'
test_expect_success 'Remove nonexistent file returns nonzero exit status' '
- ! git rm nonexistent
+ test_must_fail git rm nonexistent
'
test_done
'
test_expect_success 'error out when attempting to add ignored ones without -f' '
- ! git add a.?? &&
+ test_must_fail git add a.?? &&
! (git ls-files | grep "\\.ig")
'
test_expect_success 'error out when attempting to add ignored ones without -f' '
- ! git add d.?? &&
+ test_must_fail git add d.?? &&
! (git ls-files | grep "\\.ig")
'
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)'
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
test_expect_success 'check with trailing whitespace' '
echo "foo(); " > x &&
- ! git diff --check
+ test_must_fail git diff --check
'
# indent has space followed by hard tab
echo " foo();" > x &&
- ! git diff --check
+ test_must_fail git diff --check
'
echo "foo(); " > x &&
git add x &&
- ! git diff --cached --check
+ test_must_fail git diff --cached --check
'
# indent has space followed by hard tab
echo " foo();" > x &&
git add x &&
- ! git diff --cached --check
+ test_must_fail git diff --cached --check
'
echo "foo(); " > x &&
git add x &&
- ! git diff-index --check HEAD
+ test_must_fail git diff-index --check HEAD
'
# indent has space followed by hard tab
echo " foo();" > x &&
git add x &&
- ! git diff-index --check HEAD
+ test_must_fail git diff-index --check HEAD
'
echo "foo(); " > x &&
git add x &&
- ! git diff-index --cached --check HEAD
+ test_must_fail git diff-index --cached --check HEAD
'
# indent has space followed by hard tab
echo " foo();" > x &&
git add x &&
- ! git diff-index --cached --check HEAD
+ test_must_fail git diff-index --cached --check HEAD
'
echo "foo(); " > x &&
git commit -m "another commit" x &&
- ! git diff-tree --check HEAD^ HEAD
+ test_must_fail git diff-tree --check HEAD^ HEAD
'
# indent has space followed by hard tab
echo " foo();" > x &&
git commit -m "yet another" x &&
- ! git diff-tree --check HEAD^ HEAD
+ test_must_fail git diff-tree --check HEAD^ HEAD
'
git config core.whitespace "trailing-space" &&
echo "foo (); " > x &&
- ! git diff --check
+ test_must_fail git diff --check
'
# indent contains space followed by HT
git config core.whitespace "space-before-tab" &&
echo " foo (); " > x &&
- ! git diff --check
+ test_must_fail git diff --check
'
git config core.whitespace "indent-with-non-tab" &&
echo " foo ();" > x &&
- ! git diff --check
+ test_must_fail git diff --check
'
git config core.whitespace "indent-with-non-tab" &&
echo " foo ();" > x &&
- ! git diff --check
+ test_must_fail git diff --check
'
test_expect_success 'last regexp must not be negated' '
git config diff.java.funcname "!static" &&
- ! git diff --no-index Beer.java Beer-correct.java
+ test_must_fail git diff --no-index Beer.java Beer-correct.java
'
test_done
test_expect_success 'check binary diff -- should fail.' \
'git-checkout master &&
- ! git apply --check B.diff'
+ test_must_fail git apply --check B.diff'
test_expect_success 'check binary diff (copy) -- should fail.' \
'git-checkout master &&
- ! git apply --check C.diff'
+ test_must_fail git apply --check C.diff'
test_expect_success \
'check incomplete binary diff with replacement -- should fail.' '
git-checkout master &&
- ! git apply --check --allow-binary-replacement B.diff
+ test_must_fail git apply --check --allow-binary-replacement B.diff
'
test_expect_success \
'check incomplete binary diff with replacement (copy) -- should fail.' '
git-checkout master &&
- ! git apply --check --allow-binary-replacement C.diff
+ test_must_fail git apply --check --allow-binary-replacement C.diff
'
test_expect_success 'check binary diff with replacement.' \
test_expect_success 'apply binary diff -- should fail.' \
'do_reset &&
- ! git apply B.diff'
+ test_must_fail git apply B.diff'
test_expect_success 'apply binary diff -- should fail.' \
'do_reset &&
- ! git apply --index B.diff'
+ test_must_fail git apply --index B.diff'
test_expect_success 'apply binary diff (copy) -- should fail.' \
'do_reset &&
- ! git apply C.diff'
+ test_must_fail git apply C.diff'
test_expect_success 'apply binary diff (copy) -- should fail.' \
'do_reset &&
- ! git apply --index C.diff'
+ test_must_fail git apply --index C.diff'
test_expect_success 'apply binary diff without replacement.' \
'do_reset &&
# test
test_expect_success 'apply at the end' \
- '! git apply --index test-patch'
+ 'test_must_fail git apply --index test-patch'
cat >test-patch <<\EOF
diff a/file b/file
git update-index file
test_expect_success 'apply at the beginning' \
- '! git apply --index test-patch'
+ 'test_must_fail git apply --index test-patch'
test_done
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^)"
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^)" &&
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"
'
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_must_fail git am lorem-move.patch &&
+ 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)"
'
test_expect_success 'am --resolved works' '
git checkout lorem2^^ &&
- ! git am lorem-move.patch &&
- test -d .dotest &&
+ test_must_fail git am lorem-move.patch &&
+ 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/
+ test_must_fail git am <failmail &&
+ rm -r .git/rebase/
'
test_expect_success 'am fails on empty patch' '
echo "---" >>failmail &&
- ! git am <failmail &&
+ test_must_fail git am <failmail &&
git am --skip &&
- ! test -d .dotest
+ ! test -d .git/rebase
'
test_expect_success 'am works from stdin in subdirectory' '
. ./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,
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
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
test_expect_success 'nothing recorded without rerere' '
(rm -rf .git/rr-cache; git config rerere.enabled false) &&
- ! git merge first &&
+ test_must_fail git merge first &&
! test -d .git/rr-cache
'
git reset --hard &&
mkdir .git/rr-cache &&
git config --unset rerere.enabled &&
- ! git merge first
+ test_must_fail 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
+ test_must_fail git merge first &&
+ grep ^=======$ $rr/preimage
'
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
'
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;
git checkout -b third master &&
git show second^:a1 | sed "s/To die: t/To die! T/" > a1 &&
git commit -q -a -m third &&
- ! git pull . first
+ test_must_fail git pull . first
'
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'
echo Bello > file2 &&
git add file2 &&
git commit -m version2 &&
- ! git merge fourth &&
- sha1=$(sed -e "s/ .*//" .git/rr-cache/MERGE_RR) &&
+ test_must_fail git merge fourth &&
+ sha1=$(sed -e "s/ .*//" .git/MERGE_RR) &&
rr=.git/rr-cache/$sha1 &&
echo Cello > file2 &&
git add file2 &&
'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
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+"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>
--- /dev/null
+---
+ 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
+
=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
+
test_expect_success \
'make sure index-pack detects the SHA1 collision' \
- '! git-index-pack -o bad.idx test-3.pack'
+ 'test_must_fail git-index-pack -o bad.idx test-3.pack'
test_expect_success \
'honor pack.packSizeLimit' \
test_expect_success \
'[index v1] 4) confirm that the pack is actually corrupted' \
- '! git fsck --full $commit'
+ 'test_must_fail git fsck --full $commit'
test_expect_success \
'[index v1] 5) pack-objects happily reuses corrupted data' \
test_expect_success \
'[index v1] 6) newly created pack is BAD !' \
- '! git verify-pack -v "test-4-${pack1}.pack"'
+ 'test_must_fail git verify-pack -v "test-4-${pack1}.pack"'
test_expect_success \
'[index v2] 1) stream pack to repository' \
test_expect_success \
'[index v2] 4) confirm that the pack is actually corrupted' \
- '! git fsck --full $commit'
+ 'test_must_fail git fsck --full $commit'
test_expect_success \
'[index v2] 5) pack-objects refuses to reuse corrupted data' \
- '! git pack-objects test-5 <obj-list'
+ 'test_must_fail git pack-objects test-5 <obj-list'
test_expect_success \
'[index v2] 6) verify-pack detects CRC mismatch' \
( while read obj
do git cat-file -p $obj >/dev/null || exit 1
done <obj-list ) &&
- err=$(! git verify-pack ".git/objects/pack/pack-${pack1}.pack" 2>&1) &&
+ err=$(test_must_fail git verify-pack \
+ ".git/objects/pack/pack-${pack1}.pack" 2>&1) &&
echo "$err" | grep "CRC mismatch"'
test_done
chmod u+x victim/.git/hooks/post-update
test_expect_success push '
- ! git-send-pack --force ./victim/.git master tofail >send.out 2>send.err
+ test_must_fail git-send-pack --force ./victim/.git \
+ master tofail >send.out 2>send.err
'
test_expect_success 'updated as expected' '
git commit -a -m aa-master
'
-test_expect_success 'mixed-success push returns error' '! git push'
+test_expect_success 'mixed-success push returns error' '
+ test_must_fail git push
+'
test_expect_success 'check tracking branches updated correctly after push' '
test "$(git rev-parse origin/master)" = "$(git rev-parse master)"
git commit -a -m 2
'
-test_expect_success 'push reports error' '! git push 2>stderr'
+test_expect_success 'push reports error' 'test_must_fail git push 2>stderr'
test_expect_success 'individual ref reports error' 'grep rejected stderr'
"test \"count: 18\" = \"$(grep count count.shallow)\""
test_expect_success "pull in shallow repo with missing merge base" \
- "(cd shallow && ! git pull --depth 4 .. A)"
+ "(cd shallow && test_must_fail git pull --depth 4 .. A)"
test_done
git fetch origin &&
git remote prune origin &&
git rev-parse refs/remotes/origin/side2 &&
- ! git rev-parse refs/remotes/origin/side)
+ test_must_fail git rev-parse refs/remotes/origin/side)
'
cat > test/expect << EOF
(cd test &&
git remote prune --dry-run origin > output &&
git rev-parse refs/remotes/origin/side2 &&
- ! git rev-parse refs/remotes/origin/side &&
+ test_must_fail git rev-parse refs/remotes/origin/side &&
(cd ../one &&
git branch -m side side2) &&
test_cmp expect output)
git branch -m side2 side) &&
(cd mirror &&
git rev-parse --verify refs/heads/side2 &&
- ! git rev-parse --verify refs/heads/side &&
+ test_must_fail git rev-parse --verify refs/heads/side &&
git fetch origin &&
git remote prune origin &&
- ! git rev-parse --verify refs/heads/side2 &&
+ test_must_fail git rev-parse --verify refs/heads/side2 &&
git rev-parse --verify refs/heads/side)
'
git branch -m side side2) &&
(cd alttst &&
git rev-parse --verify refs/remotes/origin/side &&
- ! git rev-parse --verify refs/remotes/origin/side2 &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/side2 &&
git fetch alt &&
git remote prune alt &&
- ! git rev-parse --verify refs/remotes/origin/side &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/side &&
git rev-parse --verify refs/remotes/origin/side2)
'
test_expect_success 'reject adding remote with an invalid name' '
- ! git remote add some:url desired-name
+ test_must_fail git remote add some:url desired-name
'
cd five &&
git init &&
- ! git fetch .. anno:five
+ test_must_fail git fetch .. anno:five
'
cd six &&
git init &&
- ! git fetch .. six:six
+ test_must_fail git fetch .. six:six
'
test_expect_success 'unbundle 1' '
cd "$D/bundle" &&
git checkout -b some-branch &&
- ! git fetch "$D/bundle1" master:master
+ test_must_fail git fetch "$D/bundle1" master:master
'
test_expect_success 'bundle 1 has only 3 files ' '
# the strange name is: a\!'b
test_expect_success 'quoting of a strangely named repo' '
- ! git fetch "a\\!'\''b" > result 2>&1 &&
+ test_must_fail git fetch "a\\!'\''b" > result 2>&1 &&
cat result &&
grep "fatal: '\''a\\\\!'\''b'\''" result
'
git fetch origin master &&
n=$(git rev-parse --verify refs/remotes/origin/master) &&
test "$o" = "$n" &&
- ! git rev-parse --verify refs/remotes/origin/side
+ test_must_fail git rev-parse --verify refs/remotes/origin/side
)
'
git pull origin master &&
n=$(git rev-parse --verify refs/remotes/origin/master) &&
test "$o" = "$n" &&
- ! git rev-parse --verify refs/remotes/origin/side
+ test_must_fail git rev-parse --verify refs/remotes/origin/side
)
'
mk_test heads/master &&
git push testrepo : &&
git commit --amend -massaged &&
- ! git push testrepo &&
+ test_must_fail git push testrepo &&
check_push_result $the_commit heads/master &&
git reset --hard $the_commit
# Without force rewinding should fail
git reset --hard HEAD^ &&
- ! git push testrepo HEAD &&
+ test_must_fail git push testrepo HEAD &&
check_push_result $the_commit heads/local &&
# With force rewinding should succeed
git clone parent child &&
(cd child &&
echo two >foo && git commit -a -m two &&
- ! git push &&
+ test_must_fail git push &&
test $(git rev-parse master) != \
$(git rev-parse remotes/origin/master))
pwd &&
rm -f testrepo/.git/objects/??/* &&
git push testrepo :refs/heads/master &&
- (cd testrepo && ! git rev-parse --verify refs/heads/master)
+ (cd testrepo && test_must_fail git rev-parse --verify refs/heads/master)
'
'
test_expect_success 'fsck fails' '
- ! git fsck
+ test_must_fail git fsck
'
test_expect_success 'upload-pack fails due to error in pack-objects' '
'
test_expect_success 'fsck fails' '
- ! git fsck
+ test_must_fail git fsck
'
test_expect_success 'upload-pack fails due to error in rev-list' '
test_expect_success 'fetch fails' '
- ! git fetch .. master
+ test_must_fail git fetch .. master
'
git push origin :dev &&
git branch -d -r origin/dev &&
git fetch &&
- ! git show-ref --verify refs/remotes/origin/dev
+ test_must_fail git show-ref --verify refs/remotes/origin/dev
'
stop_httpd
test_expect_success \
'clone of non-existent source should fail' \
- '! git-clone foo bar'
+ 'test_must_fail git-clone foo bar'
test_expect_success \
'failed clone should not leave a directory' \
# current path not to the target dir
test_expect_success \
'clone of non-existent (relative to $PWD) source should fail' \
- '! git-clone ../foo baz'
+ 'test_must_fail git-clone ../foo baz'
test_expect_success \
'clone should work now that source exists' \
: > 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 &&
--- /dev/null
+#!/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
+
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
cp test.txt backup.txt
test_expect_success "merge with conflicts" \
- "! git merge-file test.txt orig.txt new3.txt"
+ "test_must_fail git merge-file test.txt orig.txt new3.txt"
cat > expect.txt << EOF
<<<<<<< test.txt
cp backup.txt test.txt
test_expect_success "merge with conflicts, using -L" \
- "! git merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
+ "test_must_fail git merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
cat > expect.txt << EOF
<<<<<<< 1
sed "s/ tu / TU /" < new1.txt > new5.txt
test_expect_success "conflict in removed tail" \
- "! git merge-file -p orig.txt new1.txt new5.txt > out"
+ "test_must_fail git merge-file -p orig.txt new1.txt new5.txt > out"
cat > expect << EOF
Dominus regit me,
test_expect_success "expected conflict markers" "test_cmp expect out"
test_expect_success 'binary files cannot be merged' '
- ! git merge-file -p orig.txt ../test4012.png new1.txt 2> merge.err &&
+ test_must_fail git merge-file -p \
+ orig.txt ../test4012.png new1.txt 2> merge.err &&
grep "Cannot merge binary files" merge.err
'
test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
- ! git merge-file -p new6.txt new5.txt new7.txt > output &&
+ test_must_fail git merge-file -p new6.txt new5.txt new7.txt > output &&
test 1 = $(grep ======= < output | wc -l)
'
test_expect_success 'ZEALOUS_ALNUM' '
- ! git merge-file -p new8.txt new5.txt new9.txt > merge.out &&
+ test_must_fail git merge-file -p \
+ new8.txt new5.txt new9.txt > merge.out &&
test 1 = $(grep ======= < merge.out | wc -l)
'
GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
'
-test_expect_success "combined merge conflicts" "! git merge -m final G"
+test_expect_success "combined merge conflicts" "
+ test_must_fail git merge -m final G
+"
cat > expect << EOF
<<<<<<< HEAD:a1
printf "\0\0" > binary-file &&
git add binary-file &&
git commit -m binary2 &&
- ! git merge F > merge.out 2> merge.err &&
+ test_must_fail git merge F > merge.out 2> merge.err &&
grep "Cannot merge binary files: HEAD:binary-file vs. F:binary-file" \
merge.err
'
test_expect_success \
'merge master into b-symlink, which has a different symbolic link' '
git-checkout b-symlink &&
-! git-merge master'
+test_must_fail git-merge master'
test_expect_success \
'the merge result must be a file' '
test_expect_success \
'merge master into b-file, which has a file instead of a symbolic link' '
git-reset --hard && git-checkout b-file &&
-! git-merge master'
+test_must_fail git-merge master'
test_expect_success \
'the merge result must be a file' '
'merge b-file, which has a file instead of a symbolic link, into master' '
git-reset --hard &&
git-checkout master &&
-! git-merge b-file'
+test_must_fail git-merge b-file'
test_expect_success \
'the merge result must be a file' '
--- /dev/null
+#!/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
test_expect_success 'final^1^2' "test $(git rev-parse start2) = $(git rev-parse final^1^2)"
test_expect_success 'final^1^2 != final^1^1' "test $(git rev-parse final^1^2) != $(git rev-parse final^1^1)"
test_expect_success 'final^1^3 not valid' "if git rev-parse --verify final^1^3; then false; else :; fi"
-test_expect_success '--verify start2^1' '! git rev-parse --verify start2^1'
+test_expect_success '--verify start2^1' 'test_must_fail git rev-parse --verify start2^1'
test_expect_success '--verify start2^0' 'git rev-parse --verify start2^0'
test_expect_success 'repack for next test' 'git repack -a -d'
test_expect_success \
'do not move directory over existing directory' \
- 'mkdir path0 && mkdir path0/path2 && ! git mv path2 path0'
+ 'mkdir path0 && mkdir path0/path2 && test_must_fail git mv path2 path0'
test_expect_success \
'move into "."' \
>sub/file &&
git add sub/file &&
- ! git mv sub "$out/out" &&
+ test_must_fail git mv sub "$out/out" &&
test -d sub &&
! test -d ../in &&
git ls-files --error-unmatch sub/file
'! (tag_exists mytag)'
test_expect_success 'creating a tag in an empty tree should fail' '
- ! git-tag mynotag &&
+ test_must_fail git-tag mynotag &&
! tag_exists mynotag
'
test_expect_success 'creating a tag for HEAD in an empty tree should fail' '
- ! git-tag mytaghead HEAD &&
+ test_must_fail git-tag mytaghead HEAD &&
! tag_exists mytaghead
'
test_expect_success 'creating a tag for an unknown revision should fail' '
- ! git-tag mytagnorev aaaaaaaaaaa &&
+ test_must_fail git-tag mytagnorev aaaaaaaaaaa &&
! tag_exists mytagnorev
'
test_expect_success \
'trying to create a tag with the name of one existing should fail' \
- '! git tag mytag'
+ 'test_must_fail git tag mytag'
test_expect_success \
'trying to create a tag with a non-valid name should fail' '
test `git-tag -l | wc -l` -eq 1 &&
- ! git tag "" &&
- ! git tag .othertag &&
- ! git tag "other tag" &&
- ! git tag "othertag^" &&
- ! git tag "other~tag" &&
+ test_must_fail git tag "" &&
+ test_must_fail git tag .othertag &&
+ test_must_fail git tag "other tag" &&
+ test_must_fail git tag "othertag^" &&
+ test_must_fail git tag "other~tag" &&
test `git-tag -l | wc -l` -eq 1
'
test_expect_success 'trying to delete an unknown tag should fail' '
! tag_exists unknown-tag &&
- ! git-tag -d unknown-tag
+ test_must_fail git-tag -d unknown-tag
'
cat >expect <<EOF
'trying to delete two tags, existing and not, should fail in the 2nd' '
tag_exists mytag &&
! tag_exists myhead &&
- ! git-tag -d mytag anothertag &&
+ test_must_fail git-tag -d mytag anothertag &&
! tag_exists mytag &&
! tag_exists myhead
'
test_expect_success 'trying to delete an already deleted tag should fail' \
- '! git-tag -d mytag'
+ 'test_must_fail git-tag -d mytag'
# listing various tags with pattern matching:
'
test_expect_success 'trying to verify an unknown tag should fail' \
- '! git-tag -v unknown-tag'
+ 'test_must_fail git-tag -v unknown-tag'
test_expect_success \
'trying to verify a non-annotated and non-signed tag should fail' \
- '! git-tag -v non-annotated-tag'
+ 'test_must_fail git-tag -v non-annotated-tag'
test_expect_success \
'trying to verify many non-annotated or unknown tags, should fail' \
- '! git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
+ 'test_must_fail git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
# creating annotated tags:
'trying to create a tag with a non-existing -F file should fail' '
! test -f nonexistingfile &&
! tag_exists notag &&
- ! git-tag -F nonexistingfile notag &&
+ test_must_fail git-tag -F nonexistingfile notag &&
! tag_exists notag
'
echo "message file 1" >msgfile1 &&
echo "message file 2" >msgfile2 &&
! tag_exists msgtag &&
- ! git-tag -m "message 1" -F msgfile1 msgtag &&
+ test_must_fail git-tag -m "message 1" -F msgfile1 msgtag &&
! tag_exists msgtag &&
- ! git-tag -F msgfile1 -m "message 1" msgtag &&
+ test_must_fail git-tag -F msgfile1 -m "message 1" msgtag &&
! tag_exists msgtag &&
- ! git-tag -m "message 1" -F msgfile1 -m "message 2" msgtag &&
+ test_must_fail git-tag -m "message 1" -F msgfile1 \
+ -m "message 2" msgtag &&
! tag_exists msgtag
'
test_expect_success \
'trying to verify an annotated non-signed tag should fail' '
tag_exists annotated-tag &&
- ! git-tag -v annotated-tag
+ test_must_fail git-tag -v annotated-tag
'
test_expect_success \
'trying to verify a file-annotated non-signed tag should fail' '
tag_exists file-annotated-tag &&
- ! git-tag -v file-annotated-tag
+ test_must_fail git-tag -v file-annotated-tag
'
test_expect_success \
'trying to verify two annotated non-signed tags should fail' '
tag_exists annotated-tag file-annotated-tag &&
- ! git-tag -v annotated-tag file-annotated-tag
+ test_must_fail git-tag -v annotated-tag file-annotated-tag
'
# creating and verifying signed tags:
test_expect_success 'sign with an unknown id (1)' '
- ! git tag -u author@example.com -m "Another message" o-signed-tag
+ test_must_fail git tag -u author@example.com \
+ -m "Another message" o-signed-tag
'
test_expect_success 'sign with an unknown id (2)' '
- ! git tag -u DEADBEEF -m "Another message" o-signed-tag
+ test_must_fail git tag -u DEADBEEF -m "Another message" o-signed-tag
'
'trying to create a signed tag with non-existing -F file should fail' '
! test -f nonexistingfile &&
! tag_exists nosigtag &&
- ! git-tag -s -F nonexistingfile nosigtag &&
+ test_must_fail git-tag -s -F nonexistingfile nosigtag &&
! tag_exists nosigtag
'
test_expect_success \
'verifying many signed and non-signed tags should fail' '
- ! git-tag -v signed-tag annotated-tag &&
- ! git-tag -v file-annotated-tag file-signed-tag &&
- ! git-tag -v annotated-tag file-signed-tag file-annotated-tag &&
- ! git-tag -v signed-tag annotated-tag file-signed-tag
+ test_must_fail git-tag -v signed-tag annotated-tag &&
+ test_must_fail git-tag -v file-annotated-tag file-signed-tag &&
+ test_must_fail git-tag -v annotated-tag \
+ file-signed-tag file-annotated-tag &&
+ test_must_fail git-tag -v signed-tag annotated-tag file-signed-tag
'
test_expect_success 'verifying a forged tag should fail' '
sed -e "s/signed-tag/forged-tag/" |
git mktag) &&
git tag forged-tag $forged &&
- ! git-tag -v forged-tag
+ test_must_fail git-tag -v forged-tag
'
# blank and empty messages for signed tags:
git config user.signingkey BobTheMouse
test_expect_success \
'git-tag -s fails if gpg is misconfigured' \
- '! git tag -s -m tail tag-gpg-failure'
+ 'test_must_fail git tag -s -m tail tag-gpg-failure'
git config --unset user.signingkey
# try to verify without gpg:
rm -rf gpghome
test_expect_success \
'verify signed tag fails when public key is not present' \
- '! git-tag -v signed-tag'
+ 'test_must_fail git-tag -v signed-tag'
test_expect_success \
'git-tag -a fails if tag annotation is empty' '
EOF
test_expect_success 'giving a non existing revision should fail' '
- ! git reset aaaaaa &&
- ! git reset --mixed aaaaaa &&
- ! git reset --soft aaaaaa &&
- ! git reset --hard aaaaaa &&
+ test_must_fail git reset aaaaaa &&
+ test_must_fail git reset --mixed aaaaaa &&
+ test_must_fail git reset --soft aaaaaa &&
+ test_must_fail git reset --hard aaaaaa &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
'
touch .git/MERGE_HEAD &&
echo "100644 44c5b5884550c17758737edcced463447b91d42b 1 un" |
git update-index --index-info &&
- ! git reset --soft HEAD &&
+ test_must_fail git reset --soft HEAD &&
rm .git/MERGE_HEAD &&
git rm --cached -- un
'
test_expect_success \
'giving paths with options different than --mixed should fail' '
- ! git reset --soft -- first &&
- ! git reset --hard -- first &&
- ! git reset --soft HEAD^ -- first &&
- ! git reset --hard HEAD^ -- first &&
+ test_must_fail git reset --soft -- first &&
+ test_must_fail git reset --hard -- first &&
+ test_must_fail git reset --soft HEAD^ -- first &&
+ test_must_fail git reset --hard HEAD^ -- first &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
'
test_expect_success 'giving unrecognized options should fail' '
- ! git reset --other &&
- ! git reset -o &&
- ! git reset --mixed --other &&
- ! git reset --mixed -o &&
- ! git reset --soft --other &&
- ! git reset --soft -o &&
- ! git reset --hard --other &&
- ! git reset --hard -o &&
+ test_must_fail git reset --other &&
+ test_must_fail git reset -o &&
+ test_must_fail git reset --mixed --other &&
+ test_must_fail git reset --mixed -o &&
+ test_must_fail git reset --soft --other &&
+ test_must_fail git reset --soft -o &&
+ test_must_fail git reset --hard --other &&
+ test_must_fail git reset --hard -o &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
'
echo "3rd line in branch2" >>secondfile &&
git commit -a -m "change in branch2" &&
- ! git merge branch1 &&
- ! git reset --soft &&
+ test_must_fail git merge branch1 &&
+ test_must_fail git reset --soft &&
printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile &&
git commit -a -m "the change in branch2" &&
echo "3rd line in branch4" >>secondfile &&
git checkout -m branch3 &&
- ! git reset --soft &&
+ test_must_fail git reset --soft &&
printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile &&
git commit -a -m "the line in branch3" &&
echo "3rd line in branch2" >>secondfile &&
git commit -a -m "change in branch2" &&
- ! git pull . branch1 &&
+ test_must_fail git pull . branch1 &&
git reset --hard &&
check_changes 77abb337073fb4369a7ad69ff6f5ec0e4d6b54bb
'
echo 4 > file4 &&
echo 5 > file1 &&
git add file1 file3 file4 &&
- ! git reset HEAD -- file1 file2 file3 &&
+ test_must_fail git reset HEAD -- file1 file2 file3 &&
git diff > output &&
test_cmp output expect &&
git diff --cached > output &&
>sub/file2 &&
git update-index --add sub/file1 sub/file2 &&
T=$(git write-tree) &&
- ! git reset HEAD sub/file2 &&
+ test_must_fail git reset HEAD sub/file2 &&
U=$(git write-tree) &&
echo "$T" &&
echo "$U" &&
- ! git diff-index --cached --exit-code "$T" &&
+ test_must_fail git diff-index --cached --exit-code "$T" &&
test "$T" != "$U"
'
'
test_expect_success 'hard reset is not allowed' '
- ! git reset --hard HEAD^
+ test_must_fail git reset --hard HEAD^
'
test_expect_success 'soft reset is allowed' '
test_expect_success 'clean.requireForce defaults to true' '
git config --unset clean.requireForce &&
- ! git-clean
+ test_must_fail git clean
'
test_expect_success 'clean.requireForce' '
git config clean.requireForce true &&
- ! git-clean
+ test_must_fail git clean
'
then
echo "[OOPS] init succeeded but submodule url is wrong"
false
- elif ! git config submodule.example.url ./.subrepo
+ elif test_must_fail git config submodule.example.url ./.subrepo
then
echo "[OOPS] init succeeded but update of url failed"
false
test_tick &&
git commit -m rewrite file &&
echo dirty > file &&
- ! git rebase --onto HEAD~2 HEAD^
+ test_must_fail git rebase --onto HEAD~2 HEAD^
'
test_expect_success 'nonexistent template file should return error' '
echo changes >> foo &&
git add foo &&
- ! git commit --template "$PWD"/notexist
+ test_must_fail git commit --template "$PWD"/notexist
'
test_expect_success 'nonexistent template file in config should return error' '
git config commit.template "$PWD"/notexist &&
- ! git commit &&
+ test_must_fail git commit &&
git config --unset commit.template
'
test_expect_success 'unedited template should not commit' '
echo "template line" > "$TEMPLATE" &&
- ! git commit --template "$TEMPLATE"
+ test_must_fail git commit --template "$TEMPLATE"
'
test_expect_success 'unedited template with comments should not commit' '
echo "# comment in template" >> "$TEMPLATE" &&
- ! git commit --template "$TEMPLATE"
+ test_must_fail git commit --template "$TEMPLATE"
'
test_expect_success 'a Signed-off-by line by itself should not commit' '
test_expect_success \
"fail initial amend" \
- "! git-commit --amend"
+ "test_must_fail git-commit --amend"
test_expect_success \
"initial commit" \
test_expect_success \
"invalid options 1" \
- "! git-commit -m foo -m bar -F file"
+ "test_must_fail git-commit -m foo -m bar -F file"
test_expect_success \
"invalid options 2" \
- "! git-commit -C HEAD -m illegal"
+ "test_must_fail git-commit -C HEAD -m illegal"
test_expect_success \
"using paths with -a" \
"echo King of the bongo >file &&
- ! git-commit -m foo -a file"
+ test_must_fail git-commit -m foo -a file"
test_expect_success \
"using paths with --interactive" \
test_expect_success \
"using invalid commit with -C" \
- "! git-commit -C bogus"
+ "test_must_fail git-commit -C bogus"
test_expect_success \
"testing nothing to commit" \
- "! git-commit -m initial"
+ "test_must_fail git-commit -m initial"
test_expect_success \
"next commit" \
test_expect_success \
"commit message from non-existing file" \
"echo 'more bongo: bongo bongo bongo bongo' >file && \
- ! git-commit -F gah -a"
+ test_must_fail git-commit -F gah -a"
# Empty except stray tabs and spaces on a few lines.
sed -e 's/@$//' >msg <<EOF
EOF
test_expect_success \
"empty commit message" \
- "! git-commit -F msg -a"
+ "test_must_fail git-commit -F msg -a"
test_expect_success \
"commit message from file" \
test_expect_success \
"passing -m and -F" \
"echo 'enough with the bongos' >file && \
- ! git-commit -F msg -m amending ."
+ test_must_fail git-commit -F msg -m amending ."
test_expect_success \
"using message from other commit" \
echo "another" >> file &&
git add file &&
- ! git commit -m "another"
+ test_must_fail git commit -m "another"
'
echo "another" >> file &&
git add file &&
- ! git commit -m "another"
+ test_must_fail git commit -m "another"
'
echo "[OOPS] unmerged files"
false
fi &&
- if ! git diff --exit-code
+ if test_must_fail git diff --exit-code
then
echo "[OOPS] working tree != index"
false
test_debug 'gitk --all'
-test_expect_success 'test option parsing' '
- if git merge -$ c1
- then
- echo "[OOPS] -$ accepted"
- false
- fi &&
- if git merge --no-such c1
- then
- echo "[OOPS] --no-such accepted"
- false
- fi &&
- if git merge -s foobar c1
- then
- echo "[OOPS] -s foobar accepted"
- false
- fi &&
- if git merge -s=foobar c1
- then
- echo "[OOPS] -s=foobar accepted"
- false
- fi &&
- if git merge -m
- then
- echo "[OOPS] missing commit msg accepted"
- false
- fi &&
- if git merge
- then
- echo "[OOPS] missing commit references accepted"
- false
- fi
+test_expect_failure 'test option parsing' '
+ test_must_fail git merge -$ c1 &&
+ test_must_fail git merge --no-such c1 &&
+ test_must_fail git merge -s foobar c1 &&
+ test_must_fail git merge -s=foobar c1 &&
+ test_must_fail git merge -m &&
+ test_must_fail git merge
'
test_expect_success 'merge c0 with c1' '
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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" &&
git config mergetool.mytool.trustExitCode true &&
git checkout branch1 &&
- ! git merge master >/dev/null 2>&1 &&
+ test_must_fail git merge master >/dev/null 2>&1 &&
( yes "" | git mergetool file1>/dev/null 2>&1 ) &&
( yes "" | git mergetool file2>/dev/null 2>&1 ) &&
test "$(cat file1)" = "master updated" &&
clean_fake_sendmail &&
cp $patches longline.patch &&
echo $z512$z512 >>longline.patch &&
- ! git send-email \
+ test_must_fail git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
git update-index --remove dir/file &&
git update-index --add dir/file/file &&
git commit -m '$name' &&
- ! git-svn set-tree --find-copies-harder --rmdir \
+ test_must_fail git-svn set-tree --find-copies-harder --rmdir \
remotes/git-svn..mybranch" || true
git update-index --remove -- bar/zzz &&
git update-index --add -- bar &&
git commit -m "$name" &&
- ! git-svn set-tree --find-copies-harder --rmdir \
+ test_must_fail git-svn set-tree --find-copies-harder --rmdir \
remotes/git-svn..mybranch2' || true
echo yyy > bar/zzz/yyy &&
git update-index --add bar/zzz/yyy &&
git commit -m "$name" &&
- ! git-svn set-tree --find-copies-harder --rmdir \
+ test_must_fail git-svn set-tree --find-copies-harder --rmdir \
remotes/git-svn..mybranch3' || true
echo asdf > dir &&
git update-index --add -- dir &&
git commit -m "$name" &&
- ! git-svn set-tree --find-copies-harder --rmdir \
+ test_must_fail git-svn set-tree --find-copies-harder --rmdir \
remotes/git-svn..mybranch4' || true
test_expect_success 'exit if remote refs are ambigious' "
git config --add svn-remote.svn.fetch \
bar:refs/remotes/git-svn &&
- ! git-svn migrate
+ test_must_fail git-svn migrate
"
test_expect_success 'exit if init-ing a would clobber a URL' '
svn mkdir -m "mkdir bar" "${svnrepo}2/bar" &&
git config --unset svn-remote.svn.fetch \
"^bar:refs/remotes/git-svn$" &&
- ! git-svn init "${svnrepo}2/bar"
+ test_must_fail git-svn init "${svnrepo}2/bar"
'
test_expect_success \
test_expect_success 'commit conflicting change from git' '
echo second line from git >> file &&
git commit -a -m "second line from git" &&
- ! git-svn commit-diff -r1 HEAD~1 HEAD "$svnrepo"
+ test_must_fail git-svn commit-diff -r1 HEAD~1 HEAD "$svnrepo"
'
test_expect_success 'commit complementing change from git' '
rm -rf t.svn &&
echo "fourth line from git" >> file &&
git commit -a -m "fourth line from git" &&
- ! git-svn dcommit
+ test_must_fail git-svn dcommit
'
test_expect_success 'dcommit does the svn equivalent of an index merge' "
git commit -a -m 'new file' &&
echo clobber > file &&
git commit -a -m 'clobber' &&
- ! git svn dcommit
+ test_must_fail git svn dcommit
"
-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 &&
test_expect_success 'attempt to dcommit with a dirty index' '
echo foo >>file &&
git add file &&
- ! git svn dcommit
+ test_must_fail git svn dcommit
'
test_done
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
git commit -a -m "generation 2" &&
id=$(git rev-list --max-count=1 HEAD) &&
(cd "$CVSWORK" &&
- ! git cvsexportcommit -c $id
+ test_must_fail git cvsexportcommit -c $id
)'
#test_expect_success \
# git commit -a -m "generation 3" &&
# id=$(git rev-list --max-count=1 HEAD) &&
# (cd "$CVSWORK" &&
-# ! git cvsexportcommit -c $id
+# test_must_fail git cvsexportcommit -c $id
# )'
# We reuse the state from two tests back here
git commit -a -m "Update two" &&
id=$(git rev-list --max-count=1 HEAD) &&
(cd "$CVSWORK" &&
- ! git-cvsexportcommit -c $id
+ test_must_fail git-cvsexportcommit -c $id
)'
case "$(git config --bool core.filemode)" in
INPUT_END
test_expect_success 'B: fail on invalid blob sha1' '
- ! git-fast-import <input
+ test_must_fail git-fast-import <input
'
rm -f .git/objects/pack_* .git/objects/index_*
INPUT_END
test_expect_success 'B: fail on invalid branch name ".badbranchname"' '
- ! git-fast-import <input
+ test_must_fail git-fast-import <input
'
rm -f .git/objects/pack_* .git/objects/index_*
INPUT_END
test_expect_success 'B: fail on invalid branch name "bad[branch]name"' '
- ! git-fast-import <input
+ test_must_fail git-fast-import <input
'
rm -f .git/objects/pack_* .git/objects/index_*
INPUT_END
test_expect_success 'E: rfc2822 date, --date-format=raw' '
- ! git-fast-import --date-format=raw <input
+ test_must_fail git-fast-import --date-format=raw <input
'
test_expect_success \
'E: rfc2822 date, --date-format=rfc2822' \
test $MASTER != $(git rev-parse --verify refs/heads/partial) &&
git diff master..partial &&
git diff master^..partial^ &&
- ! git rev-parse partial~2)
+ test_must_fail git rev-parse partial~2)
'
test_expect_success 'signed-tags=abort' '
- ! git fast-export --signed-tags=abort sign-your-name
+ test_must_fail git fast-export --signed-tags=abort sign-your-name
'
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'
exit
;;
*)
- say 'skipping cvsimport tests, cvsps too old'
+ say 'skipping cvsimport tests, unsupported cvsps version'
test_done
exit
;;
test_must_fail () {
"$@"
- test $? -gt 0 -a $? -le 129
+ test $? -gt 0 -a $? -le 129 -o $? -gt 192
}
# test_cmp is a helper function to compare actual and expected output.
return newbase;
}
+static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen)
+{
+ char *fullname = xmalloc(baselen + pathlen + 1);
+ memcpy(fullname, base, baselen);
+ memcpy(fullname + baselen, path, pathlen);
+ fullname[baselen + pathlen] = 0;
+ return fullname;
+}
+
static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
const char *base, int baselen);
const char *path1, *path2;
const unsigned char *sha1, *sha2;
int cmp, pathlen1, pathlen2;
+ char *fullname;
sha1 = tree_entry_extract(t1, &path1, &mode1);
sha2 = tree_entry_extract(t2, &path2, &mode2);
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
int retval;
char *newbase = malloc_base(base, baselen, path1, pathlen1);
- if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
+ if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
+ newbase[baselen + pathlen1] = 0;
opt->change(opt, mode1, mode2,
- sha1, sha2, base, path1);
+ sha1, sha2, newbase);
+ newbase[baselen + pathlen1] = '/';
+ }
retval = diff_tree_sha1(sha1, sha2, newbase, opt);
free(newbase);
return retval;
}
- opt->change(opt, mode1, mode2, sha1, sha2, base, path1);
+ fullname = malloc_fullname(base, baselen, path1, pathlen1);
+ opt->change(opt, mode1, mode2, sha1, sha2, fullname);
+ free(fullname);
return 0;
}
unsigned mode;
const char *path;
const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
+ int pathlen = tree_entry_len(path, sha1);
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
enum object_type type;
- int pathlen = tree_entry_len(path, sha1);
char *newbase = malloc_base(base, baselen, path, pathlen);
struct tree_desc inner;
void *tree;
free(tree);
free(newbase);
} else {
- opt->add_remove(opt, prefix[0], mode, sha1, base, path);
+ char *fullname = malloc_fullname(base, baselen, path, pathlen);
+ opt->add_remove(opt, prefix[0], mode, sha1, fullname);
+ free(fullname);
}
}
return add_cache_entry(ce, opt);
}
-static int read_one_entry(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
+static int read_one_entry(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage, void *context)
{
return read_one_entry_opt(sha1, base, baselen, pathname, mode, stage,
ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
* This is used when the caller knows there is no existing entries at
* the stage that will conflict with the entry being added.
*/
-static int read_one_entry_quick(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
+static int read_one_entry_quick(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage, void *context)
{
return read_one_entry_opt(sha1, base, baselen, pathname, mode, stage,
ADD_CACHE_JUST_APPEND);
int read_tree_recursive(struct tree *tree,
const char *base, int baselen,
int stage, const char **match,
- read_tree_fn_t fn)
+ read_tree_fn_t fn, void *context)
{
struct tree_desc desc;
struct name_entry entry;
if (!match_tree_entry(base, baselen, entry.path, entry.mode, match))
continue;
- switch (fn(entry.sha1, base, baselen, entry.path, entry.mode, stage)) {
+ switch (fn(entry.sha1, base, baselen, entry.path, entry.mode, stage, context)) {
case 0:
continue;
case READ_TREE_RECURSIVE:
retval = read_tree_recursive(lookup_tree(entry.sha1),
newbase,
baselen + pathlen + 1,
- stage, match, fn);
+ stage, match, fn, context);
free(newbase);
if (retval)
return -1;
if (!fn)
fn = read_one_entry_quick;
- err = read_tree_recursive(tree, "", 0, stage, match, fn);
+ err = read_tree_recursive(tree, "", 0, stage, match, fn, NULL);
if (fn == read_one_entry || err)
return err;
struct tree *parse_tree_indirect(const unsigned char *sha1);
#define READ_TREE_RECURSIVE 1
-typedef int (*read_tree_fn_t)(const unsigned char *, const char *, int, const char *, unsigned int, int);
+typedef int (*read_tree_fn_t)(const unsigned char *, const char *, int, const char *, unsigned int, int, void *);
extern int read_tree_recursive(struct tree *tree,
const char *base, int baselen,
int stage, const char **match,
- read_tree_fn_t fn);
+ read_tree_fn_t fn, void *context);
extern int read_tree(struct tree *tree, int stage, const char **paths);
#include "cache.h"
static const char update_server_info_usage[] =
-"git-update-server-info [--force]";
+"git update-server-info [--force]";
int main(int ac, char **av)
{
#include "diffcore.h"
#include "quote.h"
#include "run-command.h"
+#include "remote.h"
int wt_status_relative_paths = 1;
int wt_status_use_color = -1;
run_diff_index(&rev, 1);
}
+static void wt_status_print_tracking(struct wt_status *s)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char *cp, *ep;
+ struct branch *branch;
+
+ assert(s->branch && !s->is_initial);
+ if (prefixcmp(s->branch, "refs/heads/"))
+ return;
+ branch = branch_get(s->branch + 11);
+ if (!format_tracking_info(branch, &sb))
+ return;
+
+ for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
+ color_fprintf_ln(s->fp, color(WT_STATUS_HEADER),
+ "# %.*s", (int)(ep - cp), cp);
+ color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
+}
+
void wt_status_print(struct wt_status *s)
{
unsigned char sha1[20];
}
color_fprintf(s->fp, color(WT_STATUS_HEADER), "# ");
color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
+ if (!s->is_initial)
+ wt_status_print_tracking(s);
}
if (s->is_initial) {