diff options
85 files changed, 3850 insertions, 351 deletions
diff --git a/.gitignore b/.gitignore index e8b060cbe4..20ee642420 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,7 @@ git-push git-quiltimport git-read-tree git-rebase +git-rebase--interactive git-receive-pack git-reflog git-relink @@ -123,6 +124,7 @@ git-ssh-fetch git-ssh-pull git-ssh-push git-ssh-upload +git-stash git-status git-stripspace git-submodule diff --git a/Documentation/RelNotes-1.5.2.3.txt b/Documentation/RelNotes-1.5.2.3.txt new file mode 100644 index 0000000000..addb22955b --- /dev/null +++ b/Documentation/RelNotes-1.5.2.3.txt @@ -0,0 +1,27 @@ +GIT v1.5.2.3 Release Notes +========================== + +Fixes since v1.5.2.2 +-------------------- + + * Bugfixes + + - Version 2 pack index format was introduced in version 1.5.2 + to support pack files that has offset that cannot be + represented in 32-bit. The runtime code to validate such + an index mishandled such an index for an empty pack. + + - Commit walkers (most notably, fetch over http protocol) + tried to traverse commit objects contained in trees (aka + subproject); they shouldn't. + + - A build option NO_R_TO_GCC_LINKER was not explained in Makefile + comment correctly. + + * Documentation Fixes and Updates + + - git-config --regexp was not documented properly. + + - git-repack -a was not documented properly. + + - git-remote -n was not documented properly. diff --git a/Documentation/RelNotes-1.5.3.txt b/Documentation/RelNotes-1.5.3.txt index d111661a7b..ef2f95b3c5 100644 --- a/Documentation/RelNotes-1.5.3.txt +++ b/Documentation/RelNotes-1.5.3.txt @@ -1,4 +1,4 @@ -GIT v1.5.3 Release Notes (draft) +GIT v1.5.3 Release Notes ======================== Updates since v1.5.2 @@ -10,8 +10,23 @@ Updates since v1.5.2 * Thee are a handful pack-objects changes to help you cope better with repositories with pathologically large blobs in them. +* For people who need to import from Perforce, a front-end for + fast-import is in contrib/fast-import/ now. + +* Comes with git-gui 0.8.0. + +* Comes with updated gitk. + * New commands and options. + - "git log" learned a new option '--follow', to follow + renaming history of a single file. + + - "git-filter-branch" is a reborn cg-admin-rewritehist. + + - "git-cvsserver" learned new options (--base-path, --export-all, + --strict-paths) inspired by git-daemon. + - "git-submodule" command helps you manage the projects from the superproject that contain them. @@ -36,9 +51,45 @@ Updates since v1.5.2 - "git repack" can be told to split resulting packs to avoid exceeding limit specified with "--max-pack-size". + - "git fsck" gained --verbose option. This is really really + verbose but it might help you identify exact commit that is + corrupt in your repository. + + - "git format-patch" learned --numbered-files option. This + may be useful for MH users. + + - "git tag -n -l" shows tag annotations while listing tags. + + - "git cvsimport" can optionally use the separate-remote layout. + + - "git blame" can be told to see through commits that changes + whitespaces and indentation levels with "-w" option. + + - "git send-email" can be told not to thread the messages when + sending out more than one patches. + + - "git config" learned NUL terminated output format via -z to + help scripts. + * Updated behavior of existing commands. - - "git push" pretends that you immediately fetched back from + - "git mergetool" chooses its backend more wisely, taking + notice of its environment such as use of X, Gnome/KDE, etc. + + - "gitweb" shows merge commits a lot nicer than before. The + default view uses more compact --cc format, while the UI + allows to choose normal diff with any parent. + + - snapshot files "gitweb" creates from a repository at + $path/$project/.git are more useful. We use $project part + in the filename, which we used to discard. + + - "git cvsimort" creates lightweight tag; there is not any + interesting information we can record in an annotated tag, + and the handcrafted ones the old code created was not + properly formed anyway. + + - "git-push" pretends that you immediately fetched back from the remote by updating corresponding remote tracking branches if you have any. @@ -48,17 +99,25 @@ Updates since v1.5.2 - "git-apply --whitespace=strip" removes blank lines added at the end of the file. - - fetch over git native protocols with -v shows connection + - "git-fetch" over git native protocols with -v shows connection status, and the IP address of the other end, to help diagnosing problems. - - core.legacyheaders is no more, although we still can read - objects created in a new loose object format. + - We used to have core.legacyheaders configuration, when + set to false, allowed git to write loose objects in a format + that mimicks the format used by objects stored in packs. It + turns out that this was not so useful. Although we will + continue to read objects written in that format, we do not + honor that configuration anymore and create loose objects in + the legacy/traditional format. + + - "--find-copies-harder" option to diff family can now be + spelled as "-C -C" for brevity. - "git-mailsplit" (hence "git-am") can read from Maildir formatted mailboxes. - - "git cvsserver" does not barf upon seeing "cvs login" + - "git-cvsserver" does not barf upon seeing "cvs login" request. - "pack-objects" honors "delta" attribute set in @@ -68,10 +127,25 @@ Updates since v1.5.2 - new-workdir script (in contrib) can now be used with a bare repository. + - "git-mergetool" learned to use gvimdiff. + + - "gitview" (in contrib) has a better blame interface. + + - "git log" and friends did not handle a commit log message + that is larger than 16kB; they do now. + + - "--pretty=oneline" output format for "git log" and friends + deals with "malformed" commit log messages that have more + than one lines in the first paragraph better. We used to + show the first line, cutting the title at mid-sentence; we + concatenate them into a single line and treat the result as + "oneline". * Builds - - + - old-style function definitions (most notably, a function + without parameter defined with "func()", not "func(void)") + have been eradicated. * Performance Tweaks @@ -88,6 +162,10 @@ Updates since v1.5.2 the object requested the last time, which exploits the locality of references. + - verifying pack contents done by "git fsck --full" got boost + by carefully choosing the order to verify objects in them. + + Fixes since v1.5.2 ------------------ @@ -96,14 +174,11 @@ this release, unless otherwise noted. * Bugfixes - - .... This has not - been backported to 1.5.2.x series, as it is rather an - intrusive change. - + - "gitweb" had trouble handling non UTF-8 text with older + Encode.pm Perl module. -- exec >/var/tmp/1 -O=v1.5.2-45-ged82edc -O=v1.5.2-172-g1a8b769 +O=v1.5.2.2-603-g7c85173 echo O=`git describe refs/heads/master` git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index fcea1d74d5..f50f613879 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -178,6 +178,7 @@ git-show-ref plumbinginterrogators git-sh-setup purehelpers git-ssh-fetch synchingrepositories git-ssh-upload synchingrepositories +git-stash mainporcelain git-status mainporcelain git-stripspace purehelpers git-submodule mainporcelain diff --git a/Documentation/config.txt b/Documentation/config.txt index a2057d9d24..1d96adf30b 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -117,6 +117,18 @@ core.fileMode:: the working copy are ignored; useful on broken filesystems like FAT. See gitlink:git-update-index[1]. True by default. +core.quotepath:: + The commands that output paths (e.g. `ls-files`, + `diff`), when not given the `-z` option, will quote + "unusual" characters in the pathname by enclosing the + pathname in a double-quote pair and with backslashes the + same way strings in C source code are quoted. If this + variable is set to false, the bytes higher than 0x80 are + not quoted but output as verbatim. Note that double + quote, backslash and control characters are always + quoted without `-z` regardless of the setting of this + variable. + core.autocrlf:: If true, makes git convert `CRLF` at the end of lines in text files to `LF` when reading from the filesystem, and convert in reverse when @@ -172,6 +184,13 @@ repository that ends in "/.git" is assumed to be not bare (bare = false), while all other repositories are assumed to be bare (bare = true). +core.worktree:: + Set the path to the working tree. The value will not be + used in combination with repositories found automatically in + a .git directory (i.e. $GIT_DIR is not set). + This can be overriden by the GIT_WORK_TREE environment + variable and the '--work-tree' command line option. + core.logAllRefUpdates:: Updates to a ref <ref> is logged to the file "$GIT_DIR/logs/<ref>", by appending the new and old @@ -256,7 +275,7 @@ You probably do not need to adjust this value. + Common unit suffixes of 'k', 'm', or 'g' are supported. -core.excludeFile:: +core.excludesfile:: In addition to '.gitignore' (per-directory) and '.git/info/exclude', git looks into this file for patterns of files which are not meant to be tracked. See diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 18d49d2c3b..001503205b 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -126,6 +126,13 @@ the file that rename/copy produces, respectively. If there is need for such substitution then the whole pathname is put in double quotes. +The similarity index is the percentage of unchanged lines, and +the dissimilarity index is the percentage of changed lines. It +is a rounded down integer, followed by a percent sign. The +similarity index value of 100% is thus reserved for two equal +files, while 100% dissimilarity means that no line from the old +file made it into the new one. + combined diff format -------------------- diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 8d72bb9368..bb6b57dc2d 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -41,7 +41,7 @@ to happen. With a `-d` or `-D` option, `<branchname>` will be deleted. You may specify more than one branch for deletion. If the branch currently -has a ref log then the ref log will also be deleted. Use -r together with -d +has a reflog then the reflog will also be deleted. Use -r together with -d to delete remote-tracking branches. @@ -54,9 +54,9 @@ OPTIONS Delete a branch irrespective of its index status. -l:: - Create the branch's ref log. This activates recording of - all changes to made the branch ref, enabling use of date - based sha1 expressions such as "<branchname>@{yesterday}". + Create the branch's reflog. This activates recording of + all changes made to the branch ref, enabling use of date + based sha1 expressions such as "<branchname>@\{yesterday}". -f:: Force the creation of a new branch even if it means deleting diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index ea26da8e21..818b720b91 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -62,9 +62,9 @@ OPTIONS configuration variable. -l:: - Create the new branch's ref log. This activates recording of - all changes to made the branch ref, enabling use of date - based sha1 expressions such as "<branchname>@{yesterday}". + Create the new branch's reflog. This activates recording of + all changes made to the branch ref, enabling use of date + based sha1 expressions such as "<branchname>@\{yesterday}". -m:: If you have local modifications to one or more files that diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index a445781664..5f66a7fcd5 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -9,9 +9,9 @@ git-config - Get and set repository or global options SYNOPSIS -------- [verse] -'git-config' [--system | --global] [-z|--null] name [value [value_regex]] -'git-config' [--system | --global] --add name value -'git-config' [--system | --global] --replace-all name [value [value_regex]] +'git-config' [--system | --global] [type] [-z|--null] name [value [value_regex]] +'git-config' [--system | --global] [type] --add name value +'git-config' [--system | --global] [type] --replace-all name [value [value_regex]] 'git-config' [--system | --global] [type] [-z|--null] --get name [value_regex] 'git-config' [--system | --global] [type] [-z|--null] --get-all name [value_regex] 'git-config' [--system | --global] [type] [-z|--null] --get-regexp name_regex [value_regex] @@ -37,8 +37,7 @@ prepend a single exclamation mark in front (see also <<EXAMPLES>>). The type specifier can be either '--int' or '--bool', which will make 'git-config' ensure that the variable(s) are of the given type and convert the value to the canonical form (simple decimal number for int, -a "true" or "false" string for bool). Type specifiers currently only -take effect for reading operations. If no type specifier is passed, +a "true" or "false" string for bool). If no type specifier is passed, no checks or transformations are performed on the value. This command will fail if: diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 647de90361..e5638102ec 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -126,12 +126,13 @@ not add any suffix. CONFIGURATION ------------- You can specify extra mail header lines to be added to each -message in the repository configuration. Also you can specify -the default suffix different from the built-in one: +message in the repository configuration. You can also specify +new defaults for the subject prefix and file suffix. ------------ [format] headers = "Organization: git-foo\n" + subjectprefix = CHANGE suffix = .txt ------------ diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 234c22f57f..08512e0b8f 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git-fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] - [--full] [--strict] [--verbose] [<object>*] + [--full] [--strict] [--verbose] [--lost-found] [<object>*] DESCRIPTION ----------- @@ -64,6 +64,10 @@ index file and all SHA1 references in .git/refs/* as heads. --verbose:: Be chatty. +--lost-found:: + Write dangling refs into .git/commit/ or .git/other/, depending + on type. + It tests SHA1 and general object sanity, and it does full tracking of the resulting reachability and everything else. It prints out any corruption it finds (missing or bad objects), and if you use the diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0c00090a6b..96907d4863 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,7 +8,8 @@ git-rebase - Forward-port local commits to the updated upstream head SYNOPSIS -------- [verse] -'git-rebase' [-v] [--merge] [-C<n>] [--onto <newbase>] <upstream> [<branch>] +'git-rebase' [-i | --interactive] [-v | --verbose] [--merge] [-C<n>] + [-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>] 'git-rebase' --continue | --skip | --abort DESCRIPTION @@ -208,6 +209,14 @@ OPTIONS context exist they all must match. By default no context is ever ignored. +-i, \--interactive:: + Make a list of the commits which are about to be rebased. Let the + user edit that list before rebasing. + +-p, \--preserve-merges:: + Instead of ignoring merges, try to recreate them. This option + only works in interactive mode. + include::merge-strategies.txt[] NOTES @@ -226,9 +235,100 @@ pre-rebase hook script for an example. You must be in the top directory of your project to start (or continue) a rebase. Upon completion, <branch> will be the current branch. -Author +INTERACTIVE MODE +---------------- + +Rebasing interactively means that you have a chance to edit the commits +which are rebased. You can reorder the commits, and you can +remove them (weeding out bad or otherwise unwanted patches). + +The interactive mode is meant for this type of workflow: + +1. have a wonderful idea +2. hack on the code +3. prepare a series for submission +4. submit + +where point 2. consists of several instances of + +a. regular use + 1. finish something worthy of a commit + 2. commit +b. independent fixup + 1. realize that something does not work + 2. fix that + 3. commit it + +Sometimes the thing fixed in b.2. cannot be amended to the not-quite +perfect commit it fixes, because that commit is buried deeply in a +patch series. That is exactly what interactive rebase is for: use it +after plenty of "a"s and "b"s, by rearranging and editing +commits, and squashing multiple commits into one. + +Start it with the last commit you want to retain as-is: + + git rebase -i <after-this-commit> + +An editor will be fired up with all the commits in your current branch +(ignoring merge commits), which come after the given commit. You can +reorder the commits in this list to your heart's content, and you can +remove them. The list looks more or less like this: + +------------------------------------------- +pick deadbee The oneline of this commit +pick fa1afe1 The oneline of the next commit +... +------------------------------------------- + +The oneline descriptions are purely for your pleasure; `git-rebase` will +not look at them but at the commit names ("deadbee" and "fa1afe1" in this +example), so do not delete or edit the names. + +By replacing the command "pick" with the command "edit", you can tell +`git-rebase` to stop after applying that commit, so that you can edit +the files and/or the commit message, amend the commit, and continue +rebasing. + +If you want to fold two or more commits into one, replace the command +"pick" with "squash" for the second and subsequent commit. If the +commits had different authors, it will attribute the squashed commit to +the author of the last commit. + +In both cases, or when a "pick" does not succeed (because of merge +errors), the loop will stop to let you fix things, and you can continue +the loop with `git rebase --continue`. + +For example, if you want to reorder the last 5 commits, such that what +was HEAD~4 becomes the new HEAD. To achieve that, you would call +`git-rebase` like this: + +---------------------- +$ git rebase -i HEAD~5 +---------------------- + +And move the first patch to the end of the list. + +You might want to preserve merges, if you have a history like this: + +------------------ + X + \ + A---M---B + / +---o---O---P---Q +------------------ + +Suppose you want to rebase the side branch starting at "A" to "Q". Make +sure that the current HEAD is "B", and call + +----------------------------- +$ git rebase -i -p --onto Q O +----------------------------- + +Authors ------ -Written by Junio C Hamano <junkio@cox.net> +Written by Junio C Hamano <junkio@cox.net> and +Johannes E. Schindelin <johannes.schindelin@gmx.de> Documentation -------------- diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index 6914aa59c3..4ef1840472 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -48,8 +48,8 @@ standard input of the hook will be one line per ref to be updated: The refname value is relative to $GIT_DIR; e.g. for the master head this is "refs/heads/master". The two sha1 values before each refname are the object names for the refname before and after -the update. Refs to be created will have sha1-old equal to 0{40}, -while refs to be deleted will have sha1-new equal to 0{40}, otherwise +the update. Refs to be created will have sha1-old equal to 0\{40}, +while refs to be deleted will have sha1-new equal to 0\{40}, otherwise sha1-old and sha1-new should be valid objects in the repository. This hook is called before any refname is updated and before any @@ -71,7 +71,7 @@ The refname parameter is relative to $GIT_DIR; e.g. for the master head this is "refs/heads/master". The two sha1 arguments are the object names for the refname before and after the update. Note that the hook is called before the refname is updated, -so either sha1-old is 0{40} (meaning there is no such ref yet), +so either sha1-old is 0\{40} (meaning there is no such ref yet), or it should match what is recorded in refname. The hook should exit with non-zero status if it wants to disallow @@ -96,8 +96,8 @@ The refname value is relative to $GIT_DIR; e.g. for the master head this is "refs/heads/master". The two sha1 values before each refname are the object names for the refname before and after the update. Refs that were created will have sha1-old equal to -0{40}, while refs that were deleted will have sha1-new equal to -0{40}, otherwise sha1-old and sha1-new should be valid objects in +0\{40}, while refs that were deleted will have sha1-new equal to +0\{40}, otherwise sha1-old and sha1-new should be valid objects in the repository. Using this hook, it is easy to generate mails describing the updates diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index ab232c2f68..61a6022ce8 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -49,6 +49,9 @@ branch the `HEAD` at the remote repository actually points at. 'show':: Gives some information about the remote <name>. ++ +With `-n` option, the remote heads are not queried first with +`git ls-remote <name>`; cached information is used instead. 'prune':: @@ -56,6 +59,10 @@ Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in "remotes/<name>". ++ +With `-n` option, the remote heads are not confirmed first with `git +ls-remote <name>`; cached information is used instead. Use with +caution. 'update':: diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index c33a512ffb..28949397ca 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -14,7 +14,8 @@ DESCRIPTION ----------- This script is used to combine all objects that do not currently -reside in a "pack", into a pack. +reside in a "pack", into a pack. It can also be used to re-organise +existing packs into a single, more efficient pack. A pack is a collection of objects, individually compressed, with delta compression applied, stored in a single file, with an @@ -28,11 +29,13 @@ OPTIONS -a:: Instead of incrementally packing the unpacked objects, - pack everything available into a single pack. + pack everything referenced into a single pack. Especially useful when packing a repository that is used for private development and there is no need to worry - about people fetching via dumb file transfer protocols - from it. Use with '-d'. + about people fetching via dumb protocols from it. Use + with '-d'. This will clean up the objects that `git prune` + leaves behind, but `git fsck --full` shows as + dangling. -d:: After packing, if the newly created packs make some diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 32cb13faec..20dcac6252 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -284,9 +284,9 @@ excluded from the output. + With '\--pretty' format other than oneline (for obvious reasons), this causes the output to have two extra lines of information -taken from the reflog. By default, 'commit@{Nth}' notation is +taken from the reflog. By default, 'commit@\{Nth}' notation is used in the output. When the starting commit is specified as -'commit@{now}', output also uses 'commit@{timestamp}' notation +'commit@{now}', output also uses 'commit@\{timestamp}' notation instead. Under '\--pretty=oneline', the commit message is prefixed with this information on the same line. diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 87771b832b..eea9c9cfe9 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -90,8 +90,15 @@ OPTIONS Show `$GIT_DIR` if defined else show the path to the .git directory. --is-inside-git-dir:: - Return "true" if we are in the git directory, otherwise "false". - Some commands require to be run in a working directory. + When the current working directory is below the repository + directory print "true", otherwise "false". + +--is-inside-work-tree:: + When the current working directory is inside the work tree of the + repository print "true", otherwise "false". + +--is-bare-repository:: + When the repository is bare print "true", otherwise "false". --short, --short=number:: Instead of outputting the full SHA1 values of object names try to diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt new file mode 100644 index 0000000000..b7d263d650 --- /dev/null +++ b/Documentation/git-stash.txt @@ -0,0 +1,161 @@ +git-stash(1) +============ + +NAME +---- +git-stash - Stash the changes in a dirty working directory away + +SYNOPSIS +-------- +[verse] +'git-stash' (save | list | show [<stash>] | apply [<stash>] | clear) + +DESCRIPTION +----------- + +Use 'git-stash save' when you want to record the current state of the +working directory and the index, but want to go back to a clean +working directory. The command saves your local modifications away +and reverts the working directory to match the `HEAD` commit. + +The modifications stashed away by this command can be listed with +`git-stash list`, inspected with `git-stash show`, and restored +(potentially on top of a different commit) with `git-stash apply`. +Calling git-stash without any arguments is equivalent to `git-stash +list`. + +The latest stash you created is stored in `$GIT_DIR/refs/stash`; older +stashes are found in the reflog of this reference and can be named using +the usual reflog syntax (e.g. `stash@\{1}` is the most recently +created stash, `stash@\{2}` is the one before it, `stash@\{2.hours.ago}` +is also possible). + +OPTIONS +------- + +save:: + + Save your local modifications to a new 'stash', and run `git-reset + --hard` to revert them. + +list:: + + List the stashes that you currently have. Each 'stash' is listed + with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1} is + the one before, etc.), the name of the branch that was current when the + stash was made, and a short description of the commit the stash was + based on. ++ +---------------------------------------------------------------- +stash@{0}: submit: 6ebd0e2... Add git-stash +stash@{1}: master: 9cc0589... Merge branch 'master' of gfi +---------------------------------------------------------------- + +show [<stash>]:: + + Show the changes recorded in the stash as a diff between the the + stashed state and its original parent. When no `<stash>` is given, + shows the latest one. By default, the command shows the diffstat, but + it will accept any format known to `git-diff` (e.g., `git-stash show + -p stash@\{2}` to view the second most recent stash in patch form). + +apply [<stash>]:: + + Restore the changes recorded in the stash on top of the current + working tree state. When no `<stash>` is given, applies the latest + one. The working directory must match the index. ++ +This operation can fail with conflicts; you need to resolve them +by hand in the working tree. + +clear:: + Remove all the stashed states. Note that those states will then + be subject to pruning, and may be difficult or impossible to recover. + + +DISCUSSION +---------- + +A stash is represented as a commit whose tree records the state of the +working directory, and its first parent is the commit at `HEAD` when +the stash was created. The tree of the second parent records the +state of the index when the stash is made, and it is made a child of +the `HEAD` commit. The ancestry graph looks like this: + + .----W + / / + ...--H----I + +where `H` is the `HEAD` commit, `I` is a commit that records the state +of the index, and `W` is a commit that records the state of the working +tree. + + +EXAMPLES +-------- + +Pulling into a dirty tree:: + +When you are in the middle of something, you learn that there are +upstream changes that are possibly relevant to what you are +doing. When your local changes do not conflict with the changes in +the upstream, a simple `git pull` will let you move forward. ++ +However, there are cases in which your local changes do conflict with +the upstream changes, and `git pull` refuses to overwrite your +changes. In such a case, you can stash your changes away, +perform a pull, and then unstash, like this: ++ +---------------------------------------------------------------- +$ git pull +... +file foobar not up to date, cannot merge. +$ git stash +$ git pull +$ git stash apply +---------------------------------------------------------------- + +Interrupted workflow:: + +When you are in the middle of something, your boss comes in and +demands that you fix something immediately. Traditionally, you would +make a commit to a temporary branch to store your changes away, and +return to your original branch to make the emergency fix, like this: ++ +---------------------------------------------------------------- +... hack hack hack ... +$ git checkout -b my_wip +$ git commit -a -m "WIP" +$ git checkout master +$ edit emergency fix +$ git commit -a -m "Fix in a hurry" +$ git checkout my_wip +$ git reset --soft HEAD^ +... continue hacking ... +---------------------------------------------------------------- ++ +You can use `git-stash` to simplify the above, like this: ++ +---------------------------------------------------------------- +... hack hack hack ... +$ git stash +$ edit emergency fix +$ git commit -a -m "Fix in a hurry" +$ git stash apply +... continue hacking ... +---------------------------------------------------------------- + +SEE ALSO +-------- +gitlink:git-checkout[1], +gitlink:git-commit[1], +gitlink:git-reflog[1], +gitlink:git-reset[1] + +AUTHOR +------ +Written by Nanako Shiraishi <nanako3@bluebottle.com> + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index f8fb80f18b..7f0904e293 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -8,11 +8,19 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- +'git-submodule' [--quiet] [-b branch] add <repository> [<path>] 'git-submodule' [--quiet] [--cached] [status|init|update] [--] [<path>...] COMMANDS -------- +add:: + Add the given repository as a submodule at the given path + to the changeset to be committed next. In particular, the + repository is cloned at the specified path, added to the + changeset and registered in .gitmodules. If no path is + specified, the path is deduced from the repository specification. + status:: Show the status of the submodules. This will print the SHA-1 of the currently checked out commit for each submodule, along with the @@ -39,6 +47,9 @@ OPTIONS -q, --quiet:: Only print error messages. +-b, --branch:: + Branch of repository to add as submodule. + --cached:: Display the SHA-1 stored in the index, not the SHA-1 of the currently checked out submodule commit. This option is only valid for the diff --git a/Documentation/git.txt b/Documentation/git.txt index 20b5b7bb48..10c7bb3f45 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] - [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS] + [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] + [--help] COMMAND [ARGS] DESCRIPTION ----------- @@ -41,9 +42,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.5.2.2/git.html[documentation for release 1.5.2.2] +* link:v1.5.2.3/git.html[documentation for release 1.5.2.3] * release notes for + link:RelNotes-1.5.2.3.txt[1.5.2.3], link:RelNotes-1.5.2.2.txt[1.5.2.2], link:RelNotes-1.5.2.1.txt[1.5.2.1], link:RelNotes-1.5.2.txt[1.5.2]. @@ -103,6 +105,14 @@ OPTIONS Set the path to the repository. This can also be controlled by setting the GIT_DIR environment variable. +--work-tree=<path>:: + Set the path to the working tree. The value will not be + used in combination with repositories found automatically in + a .git directory (i.e. $GIT_DIR is not set). + This can also be controlled by setting the GIT_WORK_TREE + environment variable and the core.worktree configuration + variable. + --bare:: Same as --git-dir=`pwd`. @@ -347,6 +357,13 @@ git so take care if using Cogito etc. specifies a path to use instead of the default `.git` for the base of the repository. +'GIT_WORK_TREE':: + Set the path to the working tree. The value will not be + used in combination with repositories found automatically in + a .git directory (i.e. $GIT_DIR is not set). + This can also be controlled by the '--work-tree' command line + option and the core.worktree configuration variable. + git Commits ~~~~~~~~~~~ 'GIT_AUTHOR_NAME':: @@ -396,6 +413,16 @@ other 'GIT_PAGER':: This environment variable overrides `$PAGER`. +'GIT_FLUSH':: + If this environment variable is set to "1", then commands such + as git-blame (in incremental mode), git-rev-list, git-log, + git-whatchanged, etc., will force a flush of the output stream + after each commit-oriented record have been flushed. If this + variable is set to "0", the output of these commands will be done + using completely buffered I/O. If this environment variable is + not set, git will choose buffered or record-oriented flushing + based on whether stdout appears to be redirected to a file or not. + 'GIT_TRACE':: If this variable is set to "1", "2" or "true" (comparison is case insensitive), git will print `trace:` messages on @@ -94,9 +94,9 @@ all:: # Define OLD_ICONV if your library has an old iconv(), where the second # (input buffer pointer) parameter is declared with type (const char **). # -# Define NO_R_TO_GCC if your gcc does not like "-R/path/lib" that -# tells runtime paths to dynamic libraries; "-Wl,-rpath=/path/lib" -# is used instead. +# Define NO_R_TO_GCC_LINKER if your gcc does not like "-R/path/lib" +# that tells runtime paths to dynamic libraries; +# "-Wl,-rpath=/path/lib" is used instead. # # Define USE_NSEC below if you want git to care about sub-second file mtimes # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and @@ -204,7 +204,7 @@ SCRIPT_SH = \ git-fetch.sh \ git-ls-remote.sh \ git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ - git-pull.sh git-rebase.sh \ + git-pull.sh git-rebase.sh git-rebase--interactive.sh \ git-repack.sh git-request-pull.sh git-reset.sh \ git-sh-setup.sh \ git-tag.sh git-verify-tag.sh \ @@ -212,7 +212,8 @@ SCRIPT_SH = \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ git-merge-resolve.sh git-merge-ours.sh \ git-lost-found.sh git-quiltimport.sh git-submodule.sh \ - git-filter-branch.sh + git-filter-branch.sh \ + git-stash.sh SCRIPT_PERL = \ git-add--interactive.perl \ diff --git a/builtin-blame.c b/builtin-blame.c index f7e2c13885..da23a6f9c9 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1459,6 +1459,7 @@ static void found_guilty_entry(struct blame_entry *ent) printf("boundary\n"); } write_filename_info(suspect->path); + maybe_flush_or_die(stdout, "stdout"); } } diff --git a/builtin-config.c b/builtin-config.c index b96c9aa742..7d2063c1d2 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -138,9 +138,33 @@ free_strings: return ret; } +char *normalize_value(const char *key, const char *value) +{ + char *normalized; + + if (!value) + return NULL; + + if (type == T_RAW) + normalized = xstrdup(value); + else { + normalized = xmalloc(64); + if (type == T_INT) { + int v = git_config_int(key, value); + sprintf(normalized, "%d", v); + } + else if (type == T_BOOL) + sprintf(normalized, "%s", + git_config_bool(key, value) ? "true" : "false"); + } + + return normalized; +} + int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = 0; + char* value; setup_git_directory_gently(&nongit); while (1 < argc) { @@ -154,14 +178,14 @@ int cmd_config(int argc, const char **argv, const char *prefix) char *home = getenv("HOME"); if (home) { char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - setenv("GIT_CONFIG", user_config, 1); + setenv(CONFIG_ENVIRONMENT, user_config, 1); free(user_config); } else { die("$HOME not set"); } } else if (!strcmp(argv[1], "--system")) - setenv("GIT_CONFIG", ETC_GITCONFIG, 1); + setenv(CONFIG_ENVIRONMENT, ETC_GITCONFIG, 1); else if (!strcmp(argv[1], "--null") || !strcmp(argv[1], "-z")) { term = '\0'; delim = '\n'; @@ -217,9 +241,10 @@ int cmd_config(int argc, const char **argv, const char *prefix) use_key_regexp = 1; do_all = 1; return get_value(argv[2], NULL); - } else - - return git_config_set(argv[1], argv[2]); + } else { + value = normalize_value(argv[1], argv[2]); + return git_config_set(argv[1], value); + } case 4: if (!strcmp(argv[1], "--unset")) return git_config_set_multivar(argv[2], NULL, argv[3], 0); @@ -235,17 +260,21 @@ int cmd_config(int argc, const char **argv, const char *prefix) use_key_regexp = 1; do_all = 1; return get_value(argv[2], argv[3]); - } else if (!strcmp(argv[1], "--add")) - return git_config_set_multivar(argv[2], argv[3], "^$", 0); - else if (!strcmp(argv[1], "--replace-all")) - - return git_config_set_multivar(argv[2], argv[3], NULL, 1); - else - - return git_config_set_multivar(argv[1], argv[2], argv[3], 0); + } else if (!strcmp(argv[1], "--add")) { + value = normalize_value(argv[2], argv[3]); + return git_config_set_multivar(argv[2], value, "^$", 0); + } else if (!strcmp(argv[1], "--replace-all")) { + value = normalize_value(argv[2], argv[3]); + return git_config_set_multivar(argv[2], value, NULL, 1); + } else { + value = normalize_value(argv[1], argv[2]); + return git_config_set_multivar(argv[1], value, argv[3], 0); + } case 5: - if (!strcmp(argv[1], "--replace-all")) - return git_config_set_multivar(argv[2], argv[3], argv[4], 1); + if (!strcmp(argv[1], "--replace-all")) { + value = normalize_value(argv[2], argv[3]); + return git_config_set_multivar(argv[2], value, argv[4], 1); + } case 1: default: usage(git_config_set_usage); diff --git a/builtin-fsck.c b/builtin-fsck.c index 944a496650..a6ef65ea32 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -20,6 +20,7 @@ static int check_strict; static int keep_cache_objects; static unsigned char head_sha1[20]; static int errors_found; +static int write_lost_and_found; static int verbose; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 @@ -138,6 +139,21 @@ static void check_unreachable_object(struct object *obj) if (!obj->used) { printf("dangling %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); + if (write_lost_and_found) { + char *filename = git_path("lost-found/%s/%s", + obj->type == OBJ_COMMIT ? "commit" : "other", + sha1_to_hex(obj->sha1)); + FILE *f; + + if (safe_create_leading_directories(filename)) { + error("Could not create lost-found"); + return; + } + if (!(f = fopen(filename, "w"))) + die("Could not open %s", filename); + fprintf(f, "%s\n", sha1_to_hex(obj->sha1)); + fclose(f); + } return; } @@ -685,6 +701,12 @@ int cmd_fsck(int argc, char **argv, const char *prefix) verbose = 1; continue; } + if (!strcmp(arg, "--lost-found")) { + check_full = 1; + include_reflogs = 0; + write_lost_and_found = 1; + continue; + } if (*arg == '-') usage(fsck_usage); } diff --git a/builtin-log.c b/builtin-log.c index a4186eac8e..5dc2c1c230 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -265,6 +265,7 @@ static int istitlechar(char c) static char *extra_headers = NULL; static int extra_headers_size = 0; +static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_patch_suffix = ".patch"; static int git_format_config(const char *var, const char *value) @@ -290,6 +291,13 @@ static int git_format_config(const char *var, const char *value) if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { return 0; } + if (!strcmp(var, "format.subjectprefix")) { + if (!value) + die("format.subjectprefix without value"); + fmt_patch_subject_prefix = xstrdup(value); + return 0; + } + return git_log_config(var, value); } @@ -459,6 +467,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.diffopt.msg_sep = ""; rev.diffopt.recursive = 1; + rev.subject_prefix = fmt_patch_subject_prefix; rev.extra_headers = extra_headers; /* diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 5398a41415..61577ea13f 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -470,7 +470,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) } if (require_work_tree && - (is_bare_repository() || is_inside_git_dir())) + (!is_inside_work_tree() || is_inside_git_dir())) die("This operation must be run in a work tree"); pathspec = get_pathspec(prefix, argv + i); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 813aadf596..86db8b03fe 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -100,7 +100,7 @@ static void show_commit(struct commit *commit) printf("%s%c", buf, hdr_termination); free(buf); } - fflush(stdout); + maybe_flush_or_die(stdout, "stdout"); if (commit->parents) { free_commit_list(commit->parents); commit->parents = NULL; diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 37addb25fa..497903a85a 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -352,6 +352,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) : "false"); continue; } + if (!strcmp(arg, "--is-inside-work-tree")) { + printf("%s\n", is_inside_work_tree() ? "true" + : "false"); + continue; + } + if (!strcmp(arg, "--is-bare-repository")) { + printf("%s\n", is_bare_repository() ? "true" + : "false"); + continue; + } if (!prefixcmp(arg, "--since=")) { show_datestring("--max-age=", arg+8); continue; diff --git a/builtin-stripspace.c b/builtin-stripspace.c index 62bd4b547b..d8358e28f0 100644 --- a/builtin-stripspace.c +++ b/builtin-stripspace.c @@ -1,58 +1,79 @@ #include "builtin.h" +#include "cache.h" /* - * Remove empty lines from the beginning and end. + * Remove trailing spaces from a line. * - * Turn multiple consecutive empty lines into just one - * empty line. Return true if it is an incomplete line. + * If the line ends with newline, it will be removed too. + * Returns the new length of the string. */ -static int cleanup(char *line) +static int cleanup(char *line, int len) { - int len = strlen(line); + if (len) { + if (line[len - 1] == '\n') + len--; - if (len && line[len-1] == '\n') { - if (len == 1) - return 0; - do { - unsigned char c = line[len-2]; + while (len) { + unsigned char c = line[len - 1]; if (!isspace(c)) break; - line[len-2] = '\n'; len--; - line[len] = 0; - } while (len > 1); - return 0; + } + line[len] = 0; } - return 1; + return len; } -static void stripspace(FILE *in, FILE *out) +/* + * Remove empty lines from the beginning and end + * and also trailing spaces from every line. + * + * Turn multiple consecutive empty lines between paragraphs + * into just one empty line. + * + * If the input has only empty lines and spaces, + * no output will be produced. + * + * Enable skip_comments to skip every line starting with "#". + */ +void stripspace(FILE *in, FILE *out, int skip_comments) { int empties = -1; - int incomplete = 0; - char line[1024]; + int alloc = 1024; + char *line = xmalloc(alloc); + + while (fgets(line, alloc, in)) { + int len = strlen(line); - while (fgets(line, sizeof(line), in)) { - incomplete = cleanup(line); + while (len == alloc - 1 && line[len - 1] != '\n') { + alloc = alloc_nr(alloc); + line = xrealloc(line, alloc); + fgets(line + len, alloc - len, in); + len += strlen(line + len); + } + + if (skip_comments && line[0] == '#') + continue; + len = cleanup(line, len); /* Not just an empty line? */ - if (line[0] != '\n') { + if (len) { if (empties > 0) fputc('\n', out); empties = 0; fputs(line, out); + fputc('\n', out); continue; } if (empties < 0) continue; empties++; } - if (incomplete) - fputc('\n', out); + free(line); } int cmd_stripspace(int argc, const char **argv, const char *prefix) { - stripspace(stdin, stdout); + stripspace(stdin, stdout, 0); return 0; } @@ -7,6 +7,7 @@ extern const char git_version_string[]; extern const char git_usage_string[]; extern void help_unknown_cmd(const char *cmd); +extern void stripspace(FILE *in, FILE *out, int skip_comments); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern void prune_packed_objects(int); @@ -192,6 +192,7 @@ enum object_type { }; #define GIT_DIR_ENVIRONMENT "GIT_DIR" +#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY" #define INDEX_ENVIRONMENT "GIT_INDEX_FILE" @@ -207,6 +208,7 @@ enum object_type { extern int is_bare_repository_cfg; extern int is_bare_repository(void); extern int is_inside_git_dir(void); +extern int is_inside_work_tree(void); extern const char *get_git_dir(void); extern char *get_object_directory(void); extern char *get_refs_directory(void); @@ -292,6 +294,7 @@ extern int delete_ref(const char *, const unsigned char *sha1); /* Environment bits from configuration mechanism */ extern int trust_executable_bit; +extern int quote_path_fully; extern int has_symlinks; extern int assume_unchanged; extern int prefer_symlink_refs; @@ -532,6 +535,8 @@ extern char git_default_name[MAX_GITNAME]; extern const char *git_commit_encoding; extern const char *git_log_output_encoding; +/* IO helper functions */ +extern void maybe_flush_or_die(FILE *, const char *); extern int copy_fd(int ifd, int ofd); extern int read_in_full(int fd, void *buf, size_t count); extern int write_in_full(int fd, const void *buf, size_t count); @@ -271,6 +271,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.quotepath")) { + quote_path_fully = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "core.symlinks")) { has_symlinks = git_config_bool(var, value); return 0; @@ -587,6 +587,7 @@ pid_t git_connect(int fd[2], char *url, const char *prog, int flags) unsetenv(ALTERNATE_DB_ENVIRONMENT); unsetenv(DB_ENVIRONMENT); unsetenv(GIT_DIR_ENVIRONMENT); + unsetenv(GIT_WORK_TREE_ENVIRONMENT); unsetenv(GRAFT_ENVIRONMENT); unsetenv(INDEX_ENVIRONMENT); execlp("sh", "sh", "-c", command, NULL); @@ -1813,6 +1813,11 @@ static void diff_fill_sha1_info(struct diff_filespec *one) hashclr(one->sha1); } +static int similarity_index(struct diff_filepair *p) +{ + return p->score * 100 / MAX_SCORE; +} + static void run_diff(struct diff_filepair *p, struct diff_options *o) { const char *pgm = external_diff(); @@ -1847,23 +1852,20 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) "similarity index %d%%\n" "copy from %s\n" "copy to %s\n", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name_munged, other_munged); + similarity_index(p), name_munged, other_munged); break; case DIFF_STATUS_RENAMED: len += snprintf(msg + len, sizeof(msg) - len, "similarity index %d%%\n" "rename from %s\n" "rename to %s\n", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name_munged, other_munged); + similarity_index(p), name_munged, other_munged); break; case DIFF_STATUS_MODIFIED: if (p->score) { len += snprintf(msg + len, sizeof(msg) - len, "dissimilarity index %d%%\n", - (int)(0.5 + p->score * - 100.0/MAX_SCORE)); + similarity_index(p)); complete_rewrite = 1; break; } @@ -2387,8 +2389,7 @@ static void diff_flush_raw(struct diff_filepair *p, } if (p->score) - sprintf(status, "%c%03d", p->status, - (int)(0.5 + p->score * 100.0/MAX_SCORE)); + sprintf(status, "%c%03d", p->status, similarity_index(p)); else { status[0] = p->status; status[1] = 0; @@ -2670,8 +2671,7 @@ static void show_rename_copy(const char *renamecopy, struct diff_filepair *p) { char *names = pprint_rename(p->one->path, p->two->path); - printf(" %s %s (%d%%)\n", renamecopy, names, - (int)(0.5 + p->score * 100.0/MAX_SCORE)); + printf(" %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); free(names); show_mode_change(p, 0); } @@ -2695,7 +2695,7 @@ static void diff_summary(struct diff_filepair *p) if (p->score) { char *name = quote_one(p->two->path); printf(" rewrite %s (%d%%)\n", name, - (int)(0.5 + p->score * 100.0/MAX_SCORE)); + similarity_index(p)); free(name); show_mode_change(p, 0); } else show_mode_change(p, 1); @@ -3005,6 +3005,22 @@ void diffcore_std(struct diff_options *options) { if (options->quiet) return; + + /* + * break/rename count similarity differently depending on + * the binary-ness. + */ + if ((options->break_opt != -1) || (options->detect_rename)) { + struct diff_queue_struct *q = &diff_queued_diff; + int i; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + p->one->is_binary = file_is_binary(p->one); + p->two->is_binary = file_is_binary(p->two); + } + } + if (options->break_opt != -1) diffcore_break(options->break_opt); if (options->detect_rename) diff --git a/diffcore-break.c b/diffcore-break.c index 9c19b8cab7..ae8a7d03e2 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -66,8 +66,7 @@ static int should_break(struct diff_filespec *src, if (base_size < MINIMUM_BREAK_SIZE) return 0; /* we do not break too small filepair */ - if (diffcore_count_changes(src->data, src->size, - dst->data, dst->size, + if (diffcore_count_changes(src, dst, NULL, NULL, 0, &src_copied, &literal_added)) diff --git a/diffcore-delta.c b/diffcore-delta.c index 7338a40c59..a038b166c5 100644 --- a/diffcore-delta.c +++ b/diffcore-delta.c @@ -5,23 +5,20 @@ /* * Idea here is very simple. * - * We have total of (sz-N+1) N-byte overlapping sequences in buf whose - * size is sz. If the same N-byte sequence appears in both source and - * destination, we say the byte that starts that sequence is shared - * between them (i.e. copied from source to destination). + * Almost all data we are interested in are text, but sometimes we have + * to deal with binary data. So we cut them into chunks delimited by + * LF byte, or 64-byte sequence, whichever comes first, and hash them. * - * For each possible N-byte sequence, if the source buffer has more - * instances of it than the destination buffer, that means the - * difference are the number of bytes not copied from source to - * destination. If the counts are the same, everything was copied - * from source to destination. If the destination has more, - * everything was copied, and destination added more. + * For those chunks, if the source buffer has more instances of it + * than the destination buffer, that means the difference are the + * number of bytes not copied from source to destination. If the + * counts are the same, everything was copied from source to + * destination. If the destination has more, everything was copied, + * and destination added more. * * We are doing an approximation so we do not really have to waste * memory by actually storing the sequence. We just hash them into * somewhere around 2^16 hashbuckets and count the occurrences. - * - * The length of the sequence is arbitrarily set to 8 for now. */ /* Wild guess at the initial hash size */ @@ -125,11 +122,14 @@ static struct spanhash_top *add_spanhash(struct spanhash_top *top, } } -static struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz) +static struct spanhash_top *hash_chars(struct diff_filespec *one) { int i, n; unsigned int accum1, accum2, hashval; struct spanhash_top *hash; + unsigned char *buf = one->data; + unsigned int sz = one->size; + int is_text = !one->is_binary; i = INITIAL_HASH_SIZE; hash = xmalloc(sizeof(*hash) + sizeof(struct spanhash) * (1<<i)); @@ -143,6 +143,11 @@ static struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz) unsigned int c = *buf++; unsigned int old_1 = accum1; sz--; + + /* Ignore CR in CRLF sequence if text */ + if (is_text && c == '\r' && sz && *buf == '\n') + continue; + accum1 = (accum1 << 7) ^ (accum2 >> 25); accum2 = (accum2 << 7) ^ (old_1 >> 25); accum1 += c; @@ -156,8 +161,8 @@ static struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz) return hash; } -int diffcore_count_changes(void *src, unsigned long src_size, - void *dst, unsigned long dst_size, +int diffcore_count_changes(struct diff_filespec *src, + struct diff_filespec *dst, void **src_count_p, void **dst_count_p, unsigned long delta_limit, @@ -172,14 +177,14 @@ int diffcore_count_changes(void *src, unsigned long src_size, if (src_count_p) src_count = *src_count_p; if (!src_count) { - src_count = hash_chars(src, src_size); + src_count = hash_chars(src); if (src_count_p) *src_count_p = src_count; } if (dst_count_p) dst_count = *dst_count_p; if (!dst_count) { - dst_count = hash_chars(dst, dst_size); + dst_count = hash_chars(dst); if (dst_count_p) *dst_count_p = dst_count; } diff --git a/diffcore-rename.c b/diffcore-rename.c index 79c984c9cf..6bde4396f2 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -138,6 +138,7 @@ struct diff_score { int src; /* index in rename_src */ int dst; /* index in rename_dst */ int score; + int name_score; }; static int estimate_similarity(struct diff_filespec *src, @@ -189,8 +190,7 @@ static int estimate_similarity(struct diff_filespec *src, delta_limit = (unsigned long) (base_size * (MAX_SCORE-minimum_score) / MAX_SCORE); - if (diffcore_count_changes(src->data, src->size, - dst->data, dst->size, + if (diffcore_count_changes(src, dst, &src->cnt_data, &dst->cnt_data, delta_limit, &src_copied, &literal_added)) @@ -201,11 +201,8 @@ static int estimate_similarity(struct diff_filespec *src, */ if (!dst->size) score = 0; /* should not happen */ - else { + else score = (int)(src_copied * MAX_SCORE / max_size); - if (basename_same(src, dst)) - score++; - } return score; } @@ -242,6 +239,10 @@ static void record_rename_pair(int dst_index, int src_index, int score) static int score_compare(const void *a_, const void *b_) { const struct diff_score *a = a_, *b = b_; + + if (a->score == b->score) + return b->name_score - a->name_score; + return b->score - a->score; } @@ -360,6 +361,7 @@ void diffcore_rename(struct diff_options *options) m->dst = i; m->score = estimate_similarity(one, two, minimum_score); + m->name_score = basename_same(one, two); diff_free_filespec_data(one); } /* We do not need the text anymore */ diff --git a/diffcore.h b/diffcore.h index 7b9294eab2..0c8abb5b94 100644 --- a/diffcore.h +++ b/diffcore.h @@ -37,6 +37,7 @@ struct diff_filespec { #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0) unsigned should_free : 1; /* data should be free()'ed */ unsigned should_munmap : 1; /* data should be munmap()'ed */ + unsigned is_binary : 1; /* data should be considered "binary" */ }; extern struct diff_filespec *alloc_filespec(const char *); @@ -103,8 +104,8 @@ void diff_debug_queue(const char *, struct diff_queue_struct *); #define diff_debug_queue(a,b) do {} while(0) #endif -extern int diffcore_count_changes(void *src, unsigned long src_size, - void *dst, unsigned long dst_size, +extern int diffcore_count_changes(struct diff_filespec *src, + struct diff_filespec *dst, void **src_count_p, void **dst_count_p, unsigned long delta_limit, diff --git a/environment.c b/environment.c index 8b9b89d0a0..1c2773f1bd 100644 --- a/environment.c +++ b/environment.c @@ -12,6 +12,7 @@ char git_default_email[MAX_GITNAME]; char git_default_name[MAX_GITNAME]; int trust_executable_bit = 1; +int quote_path_fully = 1; int has_symlinks = 1; int assume_unchanged; int prefer_symlink_refs; diff --git a/git-clone.sh b/git-clone.sh index dacaaa678f..262508683d 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -72,6 +72,17 @@ Perhaps git-update-server-info needs to be run there?" rm -fr "$clone_tmp" http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" || rm -f "$GIT_DIR/REMOTE_HEAD" + if test -f "$GIT_DIR/REMOTE_HEAD"; then + head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"` + case "$head_sha1" in + 'ref: refs/'*) + ;; + *) + git-http-fetch $v -a "$head_sha1" "$1" || + rm -f "$GIT_DIR/REMOTE_HEAD" + ;; + esac + fi } quiet= diff --git a/git-commit.sh b/git-commit.sh index 5547a02954..d43bdd87c0 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -483,34 +483,8 @@ fi >>"$GIT_DIR"/COMMIT_EDITMSG # Author if test '' != "$use_commit" then - pick_author_script=' - /^author /{ - s/'\''/'\''\\'\'\''/g - h - s/^author \([^<]*\) <[^>]*> .*$/\1/ - s/'\''/'\''\'\'\''/g - s/.*/GIT_AUTHOR_NAME='\''&'\''/p - - g - s/^author [^<]* <\([^>]*\)> .*$/\1/ - s/'\''/'\''\'\'\''/g - s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p - - g - s/^author [^<]* <[^>]*> \(.*\)$/\1/ - s/'\''/'\''\'\'\''/g - s/.*/GIT_AUTHOR_DATE='\''&'\''/p - - q - } - ' - encoding=$(git config i18n.commitencoding || echo UTF-8) - set_author_env=`git show -s --pretty=raw --encoding="$encoding" "$use_commit" | - LANG=C LC_ALL=C sed -ne "$pick_author_script"` - eval "$set_author_env" - export GIT_AUTHOR_NAME - export GIT_AUTHOR_EMAIL - export GIT_AUTHOR_DATE + eval "$(get_author_ident_from_commit "$use_commit")" + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE fi if test '' != "$force_author" then diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 69ccb88dde..ba23eb8eeb 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -1007,7 +1007,7 @@ if ($orig_branch) { if ($opt_r && $opt_o ne 'HEAD'); system('git-update-ref', 'HEAD', "$orig_branch"); unless ($opt_i) { - system('git checkout'); + system('git checkout -f'); die "checkout failed: $?\n" if $?; } } diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 8fa5ce6467..a2fcebc1c6 100644 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -312,9 +312,10 @@ case "$GIT_DIR" in /*) ;; *) - export GIT_DIR="$(pwd)/../../$GIT_DIR" + GIT_DIR="$(pwd)/../../$GIT_DIR" ;; esac +export GIT_DIR GIT_WORK_TREE=. export GIT_INDEX_FILE="$(pwd)/../index" git-read-tree # seed the index file diff --git a/git-gui/Makefile b/git-gui/Makefile index 3de0de1a23..1bac6fed46 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -7,6 +7,8 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN -include GIT-VERSION-FILE +uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') + SCRIPT_SH = git-gui.sh GITGUI_BUILT_INS = git-citool ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) @@ -29,11 +31,35 @@ ifndef INSTALL INSTALL = install endif +INSTALL_D0 = $(INSTALL) -d -m755 # space is required here +INSTALL_D1 = +INSTALL_R0 = $(INSTALL) -m644 # space is required here +INSTALL_R1 = +INSTALL_X0 = $(INSTALL) -m755 # space is required here +INSTALL_X1 = +INSTALL_L0 = rm -f # space is required here +INSTALL_L1 = && ln # space is required here +INSTALL_L2 = +INSTALL_L3 = + ifndef V - QUIET_GEN = @echo ' ' GEN $@; - QUIET_BUILT_IN = @echo ' ' BUILTIN $@; - QUIET_INDEX = @echo ' ' INDEX $(dir $@); + QUIET = @ + QUIET_GEN = $(QUIET)echo ' ' GEN $@ && + QUIET_BUILT_IN = $(QUIET)echo ' ' BUILTIN $@ && + QUIET_INDEX = $(QUIET)echo ' ' INDEX $(dir $@) && QUIET_2DEVNULL = 2>/dev/null + + INSTALL_D0 = dir= + INSTALL_D1 = && echo ' ' DEST $$dir && $(INSTALL) -d -m755 "$$dir" + INSTALL_R0 = src= + INSTALL_R1 = && echo ' ' INSTALL 644 `basename $$src` && $(INSTALL) -m644 $$src + INSTALL_X0 = src= + INSTALL_X1 = && echo ' ' INSTALL 755 `basename $$src` && $(INSTALL) -m755 $$src + + INSTALL_L0 = dst= + INSTALL_L1 = && src= + INSTALL_L2 = && dst= + INSTALL_L3 = && echo ' ' 'LINK ' `basename "$$dst"` '->' `basename "$$src"` && rm -f "$$dst" && ln "$$src" "$$dst" endif TCL_PATH ?= tclsh @@ -58,11 +84,15 @@ exedir_SQ = $(subst ','\'',$(exedir)) $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(QUIET_GEN)rm -f $@ $@+ && \ + GITGUI_RELATIVE= && \ if test '$(exedir_SQ)' = '$(libdir_SQ)'; then \ - GITGUI_RELATIVE=1; \ + if test "$(uname_O)" = Cygwin; \ + then GITGUI_RELATIVE= ; \ + else GITGUI_RELATIVE=1; \ + fi; \ fi && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ - -e 's|^exec wish "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \ + -e 's|^ exec wish "$$0"| exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \ -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ -e 's|@@GITGUI_RELATIVE@@|'$$GITGUI_RELATIVE'|' \ -e $$GITGUI_RELATIVE's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \ @@ -109,12 +139,12 @@ GIT-GUI-VARS: .FORCE-GIT-GUI-VARS all:: $(ALL_PROGRAMS) lib/tclIndex install: all - $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' - $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)' - $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) - $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)' - $(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)' - $(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;) + $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1) + $(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)' + $(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true + $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(INSTALL_D1) + $(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' + $(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true dist-version: @mkdir -p $(TARDIR) diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 3237f3d596..9df2e47029 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -1,6 +1,12 @@ #!/bin/sh # Tcl ignores the next line -*- tcl -*- \ -exec wish "$0" -- "$@" + if test "z$*" = zversion \ + || test "z$*" = z--version; \ + then \ + echo 'git-gui version @@GITGUI_VERSION@@'; \ + exit; \ + fi; \ + exec wish "$0" -- "$@" set appvers {@@GITGUI_VERSION@@} set copyright { @@ -302,11 +308,6 @@ proc tk_optionMenu {w varName args} { ## ## version check -if {{--version} eq $argv || {version} eq $argv} { - puts "git-gui version $appvers" - exit -} - set req_maj 1 set req_min 5 @@ -1580,8 +1581,7 @@ if {[is_MacOSX]} { # -- Tools Menu # - if {[file exists /usr/local/miga/lib/gui-miga] - && [file exists .pvcsrc]} { + if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} { proc do_miga {} { global ui_status_value if {![lock_index update]} return diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index 139171d39e..b523654815 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -272,6 +272,8 @@ constructor new {i_commit i_path} { set cursorW %W tk_popup $w.ctxm %X %Y " + bind $i <Shift-Tab> "[list focus $w_cviewer];break" + bind $i <Tab> "[list focus $w_cviewer];break" } foreach i [concat $w_columns $w_cviewer] { @@ -287,8 +289,10 @@ constructor new {i_commit i_path} { bind $i <Control-Key-f> {catch {%W yview scroll 1 pages};break} } + bind $w_cviewer <Shift-Tab> "[list focus $w_file];break" + bind $w_cviewer <Tab> "[list focus $w_file];break" bind $w_cviewer <Button-1> [list focus $w_cviewer] - bind $top <Visibility> [list focus $top] + bind $w_file <Visibility> [list focus $w_file] grid configure $w.header -sticky ew grid configure $w.file_pane -sticky nsew @@ -483,7 +487,11 @@ method _read_file {fd jump} { } ifdeleted { catch {close $fd} } method _exec_blame {cur_w cur_d options cur_s} { - set cmd [list nice git blame] + set cmd [list] + if {![is_Windows] || [is_Cygwin]} { + lappend cmd nice + } + lappend cmd git blame set cmd [concat $cmd $options] lappend cmd --incremental if {$commit eq {}} { diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh new file mode 100755 index 0000000000..0c2a9697c4 --- /dev/null +++ b/git-rebase--interactive.sh @@ -0,0 +1,410 @@ +#!/bin/sh +# +# Copyright (c) 2006 Johannes E. Schindelin + +# SHORT DESCRIPTION +# +# This script makes it easy to fix up commits in the middle of a series, +# and rearrange commits. +# +# 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>])' + +. git-sh-setup +require_work_tree + +DOTEST="$GIT_DIR/.dotest-merge" +TODO="$DOTEST"/todo +DONE="$DOTEST"/done +REWRITTEN="$DOTEST"/rewritten +PRESERVE_MERGES= +STRATEGY= +VERBOSE= + +warn () { + echo "$*" >&2 +} + +require_clean_work_tree () { + # test if working tree is dirty + git rev-parse --verify HEAD > /dev/null && + git update-index --refresh && + git diff-files --quiet && + git diff-index --cached --quiet HEAD || + die "Working tree is dirty" +} + +ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION" + +comment_for_reflog () { + case "$ORIG_REFLOG_ACTION" in + ''|rebase*) + GIT_REFLOG_ACTION="rebase -i ($1)" + export GIT_REFLOG_ACTION + esac +} + +mark_action_done () { + sed -e 1q < "$TODO" >> "$DONE" + sed -e 1d < "$TODO" >> "$TODO".new + mv -f "$TODO".new "$TODO" +} + +make_patch () { + parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null) + git diff "$parent_sha1".."$1" > "$DOTEST"/patch +} + +die_with_patch () { + make_patch "$1" + die "$2" +} + +die_abort () { + rm -rf "$DOTEST" + die "$1" +} + +pick_one () { + case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac + git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" + test -d "$REWRITTEN" && + pick_one_preserving_merges "$@" && return + parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null) + current_sha1=$(git rev-parse --verify HEAD) + if [ $current_sha1 = $parent_sha1 ]; then + git reset --hard $sha1 + sha1=$(git rev-parse --short $sha1) + warn Fast forward to $sha1 + else + git cherry-pick $STRATEGY "$@" + fi +} + +pick_one_preserving_merges () { + case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac + sha1=$(git rev-parse $sha1) + + if [ -f "$DOTEST"/current-commit ] + then + current_commit=$(cat "$DOTEST"/current-commit) && + git rev-parse HEAD > "$REWRITTEN"/$current_commit && + rm "$DOTEST"/current-commit || + die "Cannot write current commit's replacement sha1" + fi + + # rewrite parents; if none were rewritten, we can fast-forward. + fast_forward=t + preserve=t + new_parents= + for p in $(git rev-list --parents -1 $sha1 | cut -d\ -f2-) + do + if [ -f "$REWRITTEN"/$p ] + then + preserve=f + new_p=$(cat "$REWRITTEN"/$p) + test $p != $new_p && fast_forward=f + case "$new_parents" in + *$new_p*) + ;; # do nothing; that parent is already there + *) + new_parents="$new_parents $new_p" + esac + fi + done + case $fast_forward in + t) + echo "Fast forward to $sha1" + test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1 + ;; + f) + test "a$1" = a-n && die "Refusing to squash a merge: $sha1" + + first_parent=$(expr "$new_parents" : " \([^ ]*\)") + # detach HEAD to current parent + git checkout $first_parent 2> /dev/null || + die "Cannot move HEAD to $first_parent" + + echo $sha1 > "$DOTEST"/current-commit + case "$new_parents" in + \ *\ *) + # redo merge + author_script=$(get_author_ident_from_commit $sha1) + eval "$author_script" + msg="$(git cat-file commit $sha1 | \ + sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")" + # NEEDSWORK: give rerere a chance + if ! git merge $STRATEGY -m "$msg" $new_parents + then + echo "$msg" > "$GIT_DIR"/MERGE_MSG + warn Error redoing merge $sha1 + warn + warn After fixup, please use + die "$author_script git commit" + fi + ;; + *) + git cherry-pick $STRATEGY "$@" || + die_with_patch $sha1 "Could not pick $sha1" + esac + esac +} + +do_next () { + read command sha1 rest < "$TODO" + case "$command" in + \#|'') + mark_action_done + continue + ;; + pick) + comment_for_reflog pick + + mark_action_done + pick_one $sha1 || + die_with_patch $sha1 "Could not apply $sha1... $rest" + ;; + edit) + comment_for_reflog edit + + mark_action_done + pick_one $sha1 || + die_with_patch $sha1 "Could not apply $sha1... $rest" + make_patch $sha1 + warn + warn "You can amend the commit now, with" + warn + warn " git commit --amend" + warn + exit 0 + ;; + squash) + comment_for_reflog squash + + test -z "$(grep -ve '^$' -e '^#' < $DONE)" && + die "Cannot 'squash' without a previous commit" + + mark_action_done + failed=f + pick_one -n $sha1 || failed=t + MSG="$DOTEST"/message + echo "# This is a combination of two commits." > "$MSG" + echo "# The first commit's message is:" >> "$MSG" + echo >> "$MSG" + git cat-file commit HEAD | sed -e '1,/^$/d' >> "$MSG" + echo >> "$MSG" + echo "# And this is the 2nd commit message:" >> "$MSG" + echo >> "$MSG" + git cat-file commit $sha1 | sed -e '1,/^$/d' >> "$MSG" + git reset --soft HEAD^ + author_script=$(get_author_ident_from_commit $sha1) + case $failed in + f) + # This is like --amend, but with a different message + eval "$author_script" + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE + git commit -F "$MSG" -e + ;; + t) + cp "$MSG" "$GIT_DIR"/MERGE_MSG + warn + warn "Could not apply $sha1... $rest" + warn "After you fixed that, commit the result with" + warn + warn " $(echo $author_script | tr '\012' ' ') \\" + warn " git commit -F \"$GIT_DIR\"/MERGE_MSG -e" + die_with_patch $sha1 "" + esac + ;; + *) + warn "Unknown command: $command $sha1 $rest" + die_with_patch $sha1 "Please fix this in the file $TODO." + esac + test -s "$TODO" && return + + comment_for_reflog finish && + HEADNAME=$(cat "$DOTEST"/head-name) && + OLDHEAD=$(cat "$DOTEST"/head) && + SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) && + if [ -d "$REWRITTEN" ] + then + test -f "$DOTEST"/current-commit && + current_commit=$(cat "$DOTEST"/current-commit) && + git rev-parse HEAD > "$REWRITTEN"/$current_commit + NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD) + else + NEWHEAD=$(git rev-parse HEAD) + fi && + message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" && + git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD && + git symbolic-ref HEAD $HEADNAME && + rm -rf "$DOTEST" && + warn "Successfully rebased and updated $HEADNAME." + + exit +} + +do_rest () { + while : + do + do_next + done + test -f "$DOTEST"/verbose && + git diff --stat $(cat "$DOTEST"/head)..HEAD + exit +} + +while case $# in 0) break ;; esac +do + case "$1" in + --continue) + comment_for_reflog continue + + test -d "$DOTEST" || die "No interactive rebase running" + + require_clean_work_tree + do_rest + ;; + --abort) + comment_for_reflog abort + + test -d "$DOTEST" || die "No interactive rebase running" + + HEADNAME=$(cat "$DOTEST"/head-name) + HEAD=$(cat "$DOTEST"/head) + git symbolic-ref HEAD $HEADNAME && + git reset --hard $HEAD && + rm -rf "$DOTEST" + exit + ;; + --skip) + comment_for_reflog skip + + test -d "$DOTEST" || die "No interactive rebase running" + + git reset --hard && do_rest + ;; + -s|--strategy) + shift + case "$#,$1" in + *,*=*) + STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;; + 1,*) + usage ;; + *) + STRATEGY="-s $2" + shift ;; + esac + ;; + --merge) + # we use merge anyway + ;; + -C*) + die "Interactive rebase uses merge, so $1 does not make sense" + ;; + -v|--verbose) + VERBOSE=t + ;; + -p|--preserve-merges) + PRESERVE_MERGES=t + ;; + -i|--interactive) + # yeah, we know + ;; + ''|-h) + usage + ;; + *) + test -d "$DOTEST" && + die "Interactive rebase already started" + + git var GIT_COMMITTER_IDENT >/dev/null || + die "You need to set your committer info first" + + 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 + + if [ ! -z "$2"] + then + git show-ref --verify --quiet "refs/heads/$2" || + die "Invalid branchname: $2" + git checkout "$2" || + die "Could not checkout $2" + fi + + HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?" + UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" + + test -z "$ONTO" && ONTO=$UPSTREAM + + mkdir "$DOTEST" || die "Could not create temporary $DOTEST" + : > "$DOTEST"/interactive || die "Could not mark as interactive" + git symbolic-ref HEAD > "$DOTEST"/head-name || + die "Could not get HEAD" + + echo $HEAD > "$DOTEST"/head + echo $UPSTREAM > "$DOTEST"/upstream + echo $ONTO > "$DOTEST"/onto + test t = "$VERBOSE" && : > "$DOTEST"/verbose + if [ t = "$PRESERVE_MERGES" ] + then + # $REWRITTEN contains files for each commit that is + # reachable by at least one merge base of $HEAD and + # $UPSTREAM. They are not necessarily rewritten, but + # their children might be. + # This ensures that commits on merged, but otherwise + # unrelated side branches are left alone. (Think "X" + # in the man page's example.) + mkdir "$REWRITTEN" && + for c in $(git merge-base --all $HEAD $UPSTREAM) + do + echo $ONTO > "$REWRITTEN"/$c || + die "Could not init rewritten commits" + done + MERGES_OPTION= + else + MERGES_OPTION=--no-merges + fi + + SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) + SHORTHEAD=$(git rev-parse --short $HEAD) + SHORTONTO=$(git rev-parse --short $ONTO) + cat > "$TODO" << EOF +# Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO +# +# Commands: +# pick = use commit +# edit = use commit, but stop for amending +# squash = use commit, but meld into previous commit +EOF + git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ + --abbrev=7 --reverse $UPSTREAM..$HEAD | \ + sed "s/^/pick /" >> "$TODO" + + test -z "$(grep -ve '^$' -e '^#' < $TODO)" && + die_abort "Nothing to do" + + cp "$TODO" "$TODO".backup + ${VISUAL:-${EDITOR:-vi}} "$TODO" || + die "Could not execute editor" + + test -z "$(grep -ve '^$' -e '^#' < $TODO)" && + die_abort "Nothing to do" + + git checkout $ONTO && do_rest + esac + shift +done diff --git a/git-rebase.sh b/git-rebase.sh index 2aa3a011db..388752661f 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]' +USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]' LONG_USAGE='git-rebase replaces <branch> with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> @@ -120,6 +120,16 @@ finish_rb_merge () { echo "All done." } +is_interactive () { + test -f "$dotest"/interactive || + while case $#,"$1" in 0,|*,-i|*,--interactive) break ;; esac + do + shift + done && test -n "$1" +} + +is_interactive "$@" && exec git-rebase--interactive "$@" + while case "$#" in 0) break ;; esac do case "$1" in diff --git a/git-sh-setup.sh b/git-sh-setup.sh index f24c7f2d23..98959600eb 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -29,11 +29,7 @@ set_reflog_action() { } is_bare_repository () { - git-config --bool --get core.bare || - case "$GIT_DIR" in - .git | */.git) echo false ;; - *) echo true ;; - esac + git-rev-parse --is-bare-repository } cd_to_toplevel () { @@ -48,11 +44,38 @@ cd_to_toplevel () { } require_work_tree () { - test $(is_bare_repository) = false && + test $(git-rev-parse --is-inside-work-tree) = true && test $(git-rev-parse --is-inside-git-dir) = false || die "fatal: $0 cannot be used without a working tree." } +get_author_ident_from_commit () { + pick_author_script=' + /^author /{ + s/'\''/'\''\\'\'\''/g + h + s/^author \([^<]*\) <[^>]*> .*$/\1/ + s/'\''/'\''\'\'\''/g + s/.*/GIT_AUTHOR_NAME='\''&'\''/p + + g + s/^author [^<]* <\([^>]*\)> .*$/\1/ + s/'\''/'\''\'\'\''/g + s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p + + g + s/^author [^<]* <[^>]*> \(.*\)$/\1/ + s/'\''/'\''\'\'\''/g + s/.*/GIT_AUTHOR_DATE='\''&'\''/p + + q + } + ' + encoding=$(git config i18n.commitencoding || echo UTF-8) + git show -s --pretty=raw --encoding="$encoding" "$1" | + LANG=C LC_ALL=C sed -ne "$pick_author_script" +} + if [ -z "$LONG_USAGE" ] then LONG_USAGE="Usage: $0 $USAGE" diff --git a/git-stash.sh b/git-stash.sh new file mode 100755 index 0000000000..18d3322ab5 --- /dev/null +++ b/git-stash.sh @@ -0,0 +1,165 @@ +#!/bin/sh +# Copyright (c) 2007, Nanako Shiraishi + +USAGE='[ | list | show | apply | clear]' + +. git-sh-setup +require_work_tree + +TMP="$GIT_DIR/.git-stash.$$" +trap 'rm -f "$TMP-*"' 0 + +ref_stash=refs/stash + +no_changes () { + git-diff-index --quiet --cached HEAD && + git-diff-files --quiet +} + +clear_stash () { + logfile="$GIT_DIR/logs/$ref_stash" && + mkdir -p "$(dirname "$logfile")" && + : >"$logfile" +} + +save_stash () { + if no_changes + then + echo >&2 'No local changes to save' + exit 0 + fi + test -f "$GIT_DIR/logs/$ref_stash" || + clear_stash || die "Cannot initialize stash" + + # state of the base commit + if b_commit=$(git-rev-parse --verify HEAD) + then + head=$(git-log --abbrev-commit --pretty=oneline -n 1 HEAD) + else + die "You do not have the initial commit yet" + fi + + if branch=$(git-symbolic-ref -q HEAD) + then + branch=${branch#refs/heads/} + else + branch='(no branch)' + fi + msg=$(printf '%s: %s' "$branch" "$head") + + # state of the index + i_tree=$(git-write-tree) && + i_commit=$(printf 'index on %s' "$msg" | + git-commit-tree $i_tree -p $b_commit) || + die "Cannot save the current index state" + + # state of the working tree + w_tree=$( ( + GIT_INDEX_FILE="$TMP-index" && + export GIT_INDEX_FILE && + + rm -f "$TMP-index" && + git-read-tree $i_tree && + git-add -u && + git-write-tree && + rm -f "$TMP-index" + ) ) || + die "Cannot save the current worktree state" + + # create the stash + w_commit=$(printf 'WIP on %s' "$msg" | + git-commit-tree $w_tree -p $b_commit -p $i_commit) || + die "Cannot record working tree state" + + git-update-ref -m "$msg" $ref_stash $w_commit || + die "Cannot save the current status" + printf >&2 'Saved WIP on %s\n' "$msg" +} + +have_stash () { + git-rev-parse --verify $ref_stash >/dev/null 2>&1 +} + +list_stash () { + have_stash || return 0 + git-log --pretty=oneline -g "$@" $ref_stash | + sed -n -e 's/^[.0-9a-f]* refs\///p' +} + +show_stash () { + flags=$(git-rev-parse --no-revs --flags "$@") + if test -z "$flags" + then + flags=--stat + fi + s=$(git-rev-parse --revs-only --no-flags --default $ref_stash "$@") + + w_commit=$(git-rev-parse --verify "$s") && + b_commit=$(git-rev-parse --verify "$s^") && + git-diff $flags $b_commit $w_commit +} + +apply_stash () { + git-diff-files --quiet || + die 'Cannot restore on top of a dirty state' + + # current index state + c_tree=$(git-write-tree) || + die 'Cannot apply a stash in the middle of a merge' + + s=$(git-rev-parse --revs-only --no-flags --default $ref_stash "$@") && + w_tree=$(git-rev-parse --verify "$s:") && + b_tree=$(git-rev-parse --verify "$s^:") || + die "$*: no valid stashed state found" + + eval " + GITHEAD_$w_tree='Stashed changes' && + GITHEAD_$c_tree='Updated upstream' && + GITHEAD_$b_tree='Version stash was based on' && + export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree + " + + if git-merge-recursive $b_tree -- $c_tree $w_tree + then + # No conflict + a="$TMP-added" && + git-diff --cached --name-only --diff-filter=A $c_tree >"$a" && + git-read-tree --reset $c_tree && + git-update-index --add --stdin <"$a" || + die "Cannot unstage modified files" + git-status + rm -f "$a" + else + # Merge conflict; keep the exit status from merge-recursive + exit + fi +} + +# Main command set +case "$1" in +list | '') + test $# -gt 0 && shift + if test $# = 0 + then + set x -n 10 + shift + fi + list_stash "$@" + ;; +show) + shift + show_stash "$@" + ;; +apply) + shift + apply_stash "$@" + ;; +clear) + clear_stash + ;; +save) + save_stash && git-reset --hard + ;; +*) + usage +esac diff --git a/git-submodule.sh b/git-submodule.sh index 89a3885350..c29e2c3c9d 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -1,13 +1,15 @@ #!/bin/sh # -# git-submodules.sh: init, update or list git submodules +# git-submodules.sh: add, init, update or list git submodules # # Copyright (c) 2007 Lars Hjemli -USAGE='[--quiet] [--cached] [status|init|update] [--] [<path>...]' +USAGE='[--quiet] [--cached] [add <repo> [-b branch]|status|init|update] [--] [<path>...]' . git-sh-setup require_work_tree +add= +branch= init= update= status= @@ -25,6 +27,18 @@ say() fi } +# NEEDSWORK: identical function exists in get_repo_base in clone.sh +get_repo_base() { + ( + cd "`/bin/pwd`" && + cd "$1" || cd "$1.git" && + { + cd .git + pwd + } + ) 2>/dev/null +} + # # Map submodule path to submodule name # @@ -42,6 +56,11 @@ module_name() # # Clone a submodule # +# Prior to calling, modules_update checks that a possibly existing +# path is not a git repository. +# Likewise, module_add checks that path does not exist at all, +# since it is the location of a new submodule. +# module_clone() { path=$1 @@ -66,6 +85,53 @@ module_clone() } # +# Add a new submodule to the working tree, .gitmodules and the index +# +# $@ = repo [path] +# +# optional branch is stored in global branch variable +# +module_add() +{ + repo=$1 + path=$2 + + if test -z "$repo"; then + usage + fi + + # Turn the source into an absolute path if + # it is local + if base=$(get_repo_base "$repo"); then + repo="$base" + 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 + + test -e "$path" && + die "'$path' already exists" + + git-ls-files --error-unmatch "$path" > /dev/null 2>&1 && + die "'$path' already exists in the index" + + module_clone "$path" "$repo" || exit + (unset GIT_DIR && cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || + die "Unable to checkout submodule '$path'" + git add "$path" || + die "Failed to add submodule '$path'" + + GIT_CONFIG=.gitmodules git config submodule."$path".path "$path" && + GIT_CONFIG=.gitmodules git config submodule."$path".url "$repo" && + git add .gitmodules || + die "Failed to register submodule '$path'" +} + +# # Register submodules in .git/config # # $@ = requested paths (default to all) @@ -133,6 +199,18 @@ modules_update() done } +set_name_rev () { + revname=$( ( + unset GIT_DIR && + cd "$1" && { + git-describe "$2" 2>/dev/null || + git-describe --tags "$2" 2>/dev/null || + git-describe --contains --tags "$2" + } + ) ) + test -z "$revname" || revname=" ($revname)" +} + # # List all submodules, prefixed with: # - submodule not initialized @@ -155,17 +233,18 @@ modules_list() say "-$sha1 $path" continue; fi - revname=$(unset GIT_DIR && cd "$path" && git-describe $sha1) + revname=$(unset GIT_DIR && cd "$path" && git-describe --tags $sha1) + set_name_rev "$path" $"sha1" if git diff-files --quiet -- "$path" then - say " $sha1 $path ($revname)" + say " $sha1 $path$revname" else if test -z "$cached" then sha1=$(unset GIT_DIR && cd "$path" && git-rev-parse --verify HEAD) - revname=$(unset GIT_DIR && cd "$path" && git-describe $sha1) + set_name_rev "$path" $"sha1" fi - say "+$sha1 $path ($revname)" + say "+$sha1 $path$revname" fi done } @@ -173,6 +252,9 @@ modules_list() while case "$#" in 0) break ;; esac do case "$1" in + add) + add=1 + ;; init) init=1 ;; @@ -185,6 +267,14 @@ do -q|--quiet) quiet=1 ;; + -b|--branch) + case "$2" in + '') + usage + ;; + esac + branch="$2"; shift + ;; --cached) cached=1 ;; @@ -201,14 +291,27 @@ do shift done -case "$init,$update,$status,$cached" in -1,,,) +case "$add,$branch" in +1,*) + ;; +,) + ;; +,*) + usage + ;; +esac + +case "$add,$init,$update,$status,$cached" in +1,,,,) + module_add "$@" + ;; +,1,,,) modules_init "$@" ;; -,1,,) +,,1,,) modules_update "$@" ;; -,,*,*) +,,,1,*) modules_list "$@" ;; *) diff --git a/git-svn.perl b/git-svn.perl index 50128d7285..51979f9639 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -374,16 +374,9 @@ sub cmd_dcommit { die "Unable to determine upstream SVN information from ", "$head history\n"; } - my $c = $refs[-1]; my $last_rev; - foreach my $d (@refs) { - if (!verify_ref("$d~1")) { - fatal "Commit $d\n", - "has no parent commit, and therefore ", - "nothing to diff against.\n", - "You should be working from a repository ", - "originally created by git-svn\n"; - } + my ($linear_refs, $parents) = linearize_history($gs, \@refs); + foreach my $d (@$linear_refs) { unless (defined $last_rev) { (undef, $last_rev, undef) = cmt_metadata("$d~1"); unless (defined $last_rev) { @@ -405,6 +398,9 @@ sub cmd_dcommit { svn_path => ''); if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\n$d~1 == $d\n"; + } elsif ($parents->{$d} && @{$parents->{$d}}) { + $gs->{inject_parents_dcommit}->{$last_rev} = + $parents->{$d}; } } } @@ -596,8 +592,7 @@ sub post_fetch_checkout { my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index"; return if -f $index; - chomp(my $bare = `git config --bool --get core.bare`); - return if $bare eq 'true'; + return if command_oneline(qw/rev-parse --is-inside-work-tree/) eq 'false'; return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true'; command_noisy(qw/read-tree -m -u -v HEAD HEAD/); print STDERR "Checked out HEAD:\n ", @@ -787,12 +782,12 @@ sub read_repo_config { sub extract_metadata { my $id = shift or return (undef, undef, undef); - my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+) + my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+) \s([a-f\d\-]+)$/x); if (!defined $rev || !$uuid || !$url) { # some of the original repositories I made had # identifiers like this: - ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/); + ($rev, $uuid) = ($id =~/^\s*git-svn-id:\s(\d+)\@([a-f\d\-]+)/); } return ($url, $rev, $uuid); } @@ -804,25 +799,87 @@ sub cmt_metadata { sub working_head_info { my ($head, $refs) = @_; - my ($fh, $ctx) = command_output_pipe('rev-list', $head); - while (my $hash = <$fh>) { - chomp($hash); - my ($url, $rev, $uuid) = cmt_metadata($hash); + my ($fh, $ctx) = command_output_pipe('log', $head); + my $hash; + my %max; + while (<$fh>) { + if ( m{^commit ($::sha1)$} ) { + unshift @$refs, $hash if $hash and $refs; + $hash = $1; + next; + } + next unless s{^\s*(git-svn-id:)}{$1}; + my ($url, $rev, $uuid) = extract_metadata($_); 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_db_get($rev); if ($c && $c eq $hash) { close $fh; # break the pipe return ($url, $rev, $uuid, $gs); + } else { + $max{$url} ||= $gs->rev_db_max; } } } - unshift @$refs, $hash if $refs; } command_close_pipe($fh, $ctx); (undef, undef, undef, undef); } +sub read_commit_parents { + my ($parents, $c) = @_; + my ($fh, $ctx) = command_output_pipe(qw/cat-file commit/, $c); + while (<$fh>) { + chomp; + last if ''; + /^parent ($sha1)/ or next; + push @{$parents->{$c}}, $1; + } + close $fh; # break the pipe +} + +sub linearize_history { + my ($gs, $refs) = @_; + my %parents; + foreach my $c (@$refs) { + read_commit_parents(\%parents, $c); + } + + my @linear_refs; + my %skip = (); + my $last_svn_commit = $gs->last_commit; + foreach my $c (reverse @$refs) { + next if $c eq $last_svn_commit; + last if $skip{$c}; + + unshift @linear_refs, $c; + $skip{$c} = 1; + + # we only want the first parent to diff against for linear + # history, we save the rest to inject when we finalize the + # svn commit + my $fp_a = verify_ref("$c~1"); + my $fp_b = shift @{$parents{$c}} if $parents{$c}; + if (!$fp_a || !$fp_b) { + die "Commit $c\n", + "has no parent commit, and therefore ", + "nothing to diff against.\n", + "You should be working from a repository ", + "originally created by git-svn\n"; + } + if ($fp_a ne $fp_b) { + die "$c~1 = $fp_a, however parsing commit $c ", + "revealed that:\n$c~1 = $fp_b\nBUG!\n"; + } + + foreach my $p (@{$parents{$c}}) { + $skip{$p} = 1; + } + } + (\@linear_refs, \%parents); +} + package Git::SVN; use strict; use warnings; @@ -1543,6 +1600,11 @@ sub get_commit_parents { if (my $cur = ::verify_ref($self->refname.'^0')) { push @tmp, $cur; } + if (my $ipd = $self->{inject_parents_dcommit}) { + if (my $commit = delete $ipd->{$log_entry->{revision}}) { + push @tmp, @$commit; + } + } push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp); while (my $p = shift @tmp) { next if $seen{$p}; @@ -1966,16 +2028,19 @@ sub rebuild { return; } print "Rebuilding $db_path ...\n"; - my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname); + my ($log, $ctx) = command_output_pipe("log", $self->refname); my $latest; my $full_url = $self->full_url; remove_username($full_url); my $svn_uuid; - while (<$rev_list>) { - chomp; - my $c = $_; - die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o; - my ($url, $rev, $uuid) = ::cmt_metadata($c); + my $c; + while (<$log>) { + if ( m{^commit ($::sha1)$} ) { + $c = $1; + next; + } + next unless s{^\s*(git-svn-id:)}{$1}; + my ($url, $rev, $uuid) = ::extract_metadata($_); remove_username($url); # ignore merges (from set-tree) @@ -1993,7 +2058,7 @@ sub rebuild { $self->rev_db_set($rev, $c); print "r$rev = $c\n"; } - command_close_pipe($rev_list, $ctx); + command_close_pipe($log, $ctx); print "Done rebuilding $db_path\n"; } @@ -2925,6 +2990,7 @@ sub new { SVN::Client::get_ssl_server_trust_file_provider(), SVN::Client::get_simple_prompt_provider( \&Git::SVN::Prompt::simple, 2), + SVN::Client::get_ssl_client_cert_file_provider(), SVN::Client::get_ssl_client_cert_prompt_provider( \&Git::SVN::Prompt::ssl_client_cert, 2), SVN::Client::get_ssl_client_cert_pw_prompt_provider( diff --git a/git-tag.sh b/git-tag.sh index c84043902f..1ff5b41e7f 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -19,28 +19,40 @@ do case "$1" in -a) annotate=1 + shift ;; -s) annotate=1 signed=1 + shift ;; -f) force=1 + shift ;; -n) - case $2 in - -*) LINES=1 # no argument + case "$#,$2" in + 1,* | *,-*) + LINES=1 # no argument ;; *) shift LINES=$(expr "$1" : '\([0-9]*\)') [ -z "$LINES" ] && LINES=1 # 1 line is default when -n is used ;; esac + shift ;; -l) list=1 shift - PATTERN="$1" # select tags by shell pattern, not re + case $# in + 0) PATTERN= + ;; + *) + PATTERN="$1" # select tags by shell pattern, not re + shift + ;; + esac git rev-parse --symbolic --tags | sort | while read TAG do @@ -51,12 +63,16 @@ do [ "$LINES" -le 0 ] && { echo "$TAG"; continue ;} OBJTYPE=$(git cat-file -t "$TAG") case $OBJTYPE in - tag) ANNOTATION=$(git cat-file tag "$TAG" | - sed -e '1,/^$/d' \ - -e '/^-----BEGIN PGP SIGNATURE-----$/Q' ) - printf "%-15s %s\n" "$TAG" "$ANNOTATION" | - sed -e '2,$s/^/ /' \ - -e "${LINES}q" + tag) + ANNOTATION=$(git cat-file tag "$TAG" | + sed -e '1,/^$/d' | + sed -n -e " + /^-----BEGIN PGP SIGNATURE-----\$/q + 2,\$s/^/ / + p + ${LINES}q + ") + printf "%-15s %s\n" "$TAG" "$ANNOTATION" ;; *) echo "$TAG" ;; @@ -70,7 +86,9 @@ do if test "$#" = "0"; then die "error: option -m needs an argument" else + message="$1" message_given=1 + shift fi ;; -F) @@ -81,13 +99,19 @@ do else message="$(cat "$1")" message_given=1 + shift fi ;; -u) annotate=1 signed=1 shift - username="$1" + if test "$#" = "0"; then + die "error: option -u needs an argument" + else + username="$1" + shift + fi ;; -d) shift @@ -122,7 +146,6 @@ do break ;; esac - shift done [ -n "$list" ] && exit 0 diff --git a/git-verify-tag.sh b/git-verify-tag.sh index f2d5597dba..68858b694d 100755 --- a/git-verify-tag.sh +++ b/git-verify-tag.sh @@ -37,8 +37,9 @@ esac trap 'rm -f "$GIT_DIR/.tmp-vtag"' 0 git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1 - -cat "$GIT_DIR/.tmp-vtag" | -sed '/-----BEGIN PGP/Q' | +sed -n -e ' + /^-----BEGIN PGP SIGNATURE-----$/q + p +' <"$GIT_DIR/.tmp-vtag" | gpg --verify "$GIT_DIR/.tmp-vtag" - || exit 1 rm -f "$GIT_DIR/.tmp-vtag" @@ -4,7 +4,7 @@ #include "quote.h" const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]"; + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; static void prepend_to_path(const char *dir, int len) { @@ -28,7 +28,7 @@ static void prepend_to_path(const char *dir, int len) free(path); } -static int handle_options(const char*** argv, int* argc) +static int handle_options(const char*** argv, int* argc, int* envchanged) { int handled = 0; @@ -64,14 +64,34 @@ static int handle_options(const char*** argv, int* argc) usage(git_usage_string); } setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1); + if (envchanged) + *envchanged = 1; (*argv)++; (*argc)--; handled++; } else if (!prefixcmp(cmd, "--git-dir=")) { setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1); + if (envchanged) + *envchanged = 1; + } else if (!strcmp(cmd, "--work-tree")) { + if (*argc < 2) { + fprintf(stderr, "No directory given for --work-tree.\n" ); + usage(git_usage_string); + } + setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1); + if (envchanged) + *envchanged = 1; + (*argv)++; + (*argc)--; + } else if (!prefixcmp(cmd, "--work-tree=")) { + setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1); + if (envchanged) + *envchanged = 1; } else if (!strcmp(cmd, "--bare")) { static char git_dir[PATH_MAX+1]; setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1); + if (envchanged) + *envchanged = 1; } else { fprintf(stderr, "Unknown option: %s\n", cmd); usage(git_usage_string); @@ -150,7 +170,7 @@ static int split_cmdline(char *cmdline, const char ***argv) static int handle_alias(int *argcp, const char ***argv) { - int nongit = 0, ret = 0, saved_errno = errno; + int nongit = 0, envchanged = 0, ret = 0, saved_errno = errno; const char *subdir; int count, option_count; const char** new_argv; @@ -161,6 +181,21 @@ static int handle_alias(int *argcp, const char ***argv) git_config(git_alias_config); if (alias_string) { if (alias_string[0] == '!') { + if (*argcp > 1) { + int i, sz = PATH_MAX; + char *s = xmalloc(sz), *new_alias = s; + + add_to_string(&s, &sz, alias_string, 0); + free(alias_string); + alias_string = new_alias; + for (i = 1; i < *argcp && + !add_to_string(&s, &sz, " ", 0) && + !add_to_string(&s, &sz, (*argv)[i], 1) + ; i++) + ; /* do nothing */ + if (!sz) + die("Too many or long arguments"); + } trace_printf("trace: alias to shell cmd: %s => %s\n", alias_command, alias_string + 1); ret = system(alias_string + 1); @@ -171,7 +206,11 @@ static int handle_alias(int *argcp, const char ***argv) alias_string + 1, alias_command); } count = split_cmdline(alias_string, &new_argv); - option_count = handle_options(&new_argv, &count); + option_count = handle_options(&new_argv, &count, &envchanged); + if (envchanged) + die("alias '%s' changes environment variables\n" + "You can use '!git' in the alias to do this.", + alias_command); memmove(new_argv - option_count, new_argv, count * sizeof(char *)); new_argv -= option_count; @@ -214,17 +253,56 @@ const char git_version_string[] = GIT_VERSION; * require working tree to be present -- anything uses this needs * RUN_SETUP for reading from the configuration file. */ -#define NOT_BARE (1<<2) +#define NEED_WORK_TREE (1<<2) + +struct cmd_struct { + const char *cmd; + int (*fn)(int, const char **, const char *); + int option; +}; -static void handle_internal_command(int argc, const char **argv, char **envp) +static int run_command(struct cmd_struct *p, int argc, const char **argv) +{ + int status; + struct stat st; + const char *prefix; + + prefix = NULL; + if (p->option & RUN_SETUP) + prefix = setup_git_directory(); + if (p->option & USE_PAGER) + setup_pager(); + if ((p->option & NEED_WORK_TREE) && + (!is_inside_work_tree() || is_inside_git_dir())) + die("%s must be run in a work tree", p->cmd); + trace_argv_printf(argv, argc, "trace: built-in: git"); + + status = p->fn(argc, argv, prefix); + if (status) + return status; + + /* Somebody closed stdout? */ + if (fstat(fileno(stdout), &st)) + return 0; + /* Ignore write errors for pipes and sockets.. */ + if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) + return 0; + + /* Check for ENOSPC and EIO errors.. */ + if (fflush(stdout)) + die("write failure on standard output: %s", strerror(errno)); + if (ferror(stdout)) + die("unknown write failure on standard output"); + if (fclose(stdout)) + die("close failed on standard output: %s", strerror(errno)); + return 0; +} + +static void handle_internal_command(int argc, const char **argv) { const char *cmd = argv[0]; - static struct cmd_struct { - const char *cmd; - int (*fn)(int, const char **, const char *); - int option; - } commands[] = { - { "add", cmd_add, RUN_SETUP | NOT_BARE }, + static struct cmd_struct commands[] = { + { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "annotate", cmd_annotate, RUN_SETUP | USE_PAGER }, { "apply", cmd_apply }, { "archive", cmd_archive }, @@ -234,9 +312,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, - { "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE }, + { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE }, { "cherry", cmd_cherry, RUN_SETUP }, - { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE }, + { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config }, { "count-objects", cmd_count_objects, RUN_SETUP }, @@ -264,7 +342,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "mailsplit", cmd_mailsplit }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file }, - { "mv", cmd_mv, RUN_SETUP | NOT_BARE }, + { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, { "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER }, @@ -277,9 +355,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "rerere", cmd_rerere, RUN_SETUP }, { "rev-list", cmd_rev_list, RUN_SETUP }, { "rev-parse", cmd_rev_parse, RUN_SETUP }, - { "revert", cmd_revert, RUN_SETUP | NOT_BARE }, - { "rm", cmd_rm, RUN_SETUP | NOT_BARE }, - { "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE }, + { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, + { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE }, + { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE }, { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, @@ -307,25 +385,13 @@ static void handle_internal_command(int argc, const char **argv, char **envp) for (i = 0; i < ARRAY_SIZE(commands); i++) { struct cmd_struct *p = commands+i; - const char *prefix; if (strcmp(p->cmd, cmd)) continue; - - prefix = NULL; - if (p->option & RUN_SETUP) - prefix = setup_git_directory(); - if (p->option & USE_PAGER) - setup_pager(); - if ((p->option & NOT_BARE) && - (is_bare_repository() || is_inside_git_dir())) - die("%s must be run in a work tree", cmd); - trace_argv_printf(argv, argc, "trace: built-in: git"); - - exit(p->fn(argc, argv, prefix)); + exit(run_command(p, argc, argv)); } } -int main(int argc, const char **argv, char **envp) +int main(int argc, const char **argv) { const char *cmd = argv[0] ? argv[0] : "git-help"; char *slash = strrchr(cmd, '/'); @@ -358,14 +424,14 @@ int main(int argc, const char **argv, char **envp) if (!prefixcmp(cmd, "git-")) { cmd += 4; argv[0] = cmd; - handle_internal_command(argc, argv, envp); + handle_internal_command(argc, argv); die("cannot handle %s internally", cmd); } /* Look for flags.. */ argv++; argc--; - handle_options(&argv, &argc); + handle_options(&argv, &argc, NULL); if (argc > 0) { if (!prefixcmp(argv[0], "--")) argv[0] += 2; @@ -390,7 +456,7 @@ int main(int argc, const char **argv, char **envp) while (1) { /* See if it's an internal command */ - handle_internal_command(argc, argv, envp); + handle_internal_command(argc, argv); /* .. then try the external ones */ execv_git_cmd(argv); diff --git a/log-tree.c b/log-tree.c index 0cf21bc051..ced3f332ef 100644 --- a/log-tree.c +++ b/log-tree.c @@ -408,5 +408,6 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) shown = 1; } opt->loginfo = NULL; + maybe_flush_or_die(stdout, "stdout"); return shown; } @@ -252,7 +252,7 @@ char *enter_repo(char *path, int strict) if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && validate_headref("HEAD") == 0) { - setenv("GIT_DIR", ".", 1); + setenv(GIT_DIR_ENVIRONMENT, ".", 1); check_repository_format(); return path; } @@ -188,7 +188,8 @@ static int quote_c_style_counted(const char *name, int namelen, #define EMITQ() EMIT('\\') const char *sp; - int ch, count = 0, needquote = 0; + unsigned char ch; + int count = 0, needquote = 0; if (!no_dq) EMIT('"'); @@ -197,7 +198,7 @@ static int quote_c_style_counted(const char *name, int namelen, if (!ch) break; if ((ch < ' ') || (ch == '"') || (ch == '\\') || - (ch >= 0177)) { + (quote_path_fully && (ch >= 0177))) { needquote = 1; switch (ch) { case '\a': EMITQ(); ch = 'a'; break; diff --git a/reachable.c b/reachable.c index ff3dd34962..6383401e2d 100644 --- a/reachable.c +++ b/reachable.c @@ -21,6 +21,14 @@ static void process_blob(struct blob *blob, /* Nothing to do, really .. The blob lookup was the important part */ } +static void process_gitlink(const unsigned char *sha1, + struct object_array *p, + struct name_path *path, + const char *name) +{ + /* I don't think we want to recurse into this, really. */ +} + static void process_tree(struct tree *tree, struct object_array *p, struct name_path *path, @@ -47,6 +55,8 @@ static void process_tree(struct tree *tree, while (tree_entry(&desc, &entry)) { if (S_ISDIR(entry.mode)) process_tree(lookup_tree(entry.sha1), p, &me, entry.path); + else if (S_ISGITLINK(entry.mode)) + process_gitlink(entry.sha1, p, &me, entry.path); else process_blob(lookup_blob(entry.sha1), p, &me, entry.path); } @@ -159,6 +169,16 @@ static void add_cache_refs(struct rev_info *revs) read_cache(); for (i = 0; i < active_nr; i++) { + /* + * The index can contain blobs and GITLINKs, GITLINKs are hashes + * that don't actually point to objects in the repository, it's + * almost guaranteed that they are NOT blobs, so we don't call + * lookup_blob() on them, to avoid populating the hash table + * with invalid information + */ + if (S_ISGITLINK(ntohl(active_cache[i]->ce_mode))) + continue; + lookup_blob(active_cache[i]->sha1); /* * We could add the blobs to the pending list, but quite diff --git a/read-cache.c b/read-cache.c index 4362b11f47..a363f312c7 100644 --- a/read-cache.c +++ b/read-cache.c @@ -350,6 +350,34 @@ int remove_file_from_index(struct index_state *istate, const char *path) return 0; } +static int compare_name(struct cache_entry *ce, const char *path, int namelen) +{ + return namelen != ce_namelen(ce) || memcmp(path, ce->name, namelen); +} + +static int index_name_pos_also_unmerged(struct index_state *istate, + const char *path, int namelen) +{ + int pos = index_name_pos(istate, path, namelen); + struct cache_entry *ce; + + if (pos >= 0) + return pos; + + /* maybe unmerged? */ + pos = -1 - pos; + if (pos >= istate->cache_nr || + compare_name((ce = istate->cache[pos]), path, namelen)) + return -1; + + /* order of preference: stage 2, 1, 3 */ + if (ce_stage(ce) == 1 && pos + 1 < istate->cache_nr && + ce_stage((ce = istate->cache[pos + 1])) == 2 && + !compare_name(ce, path, namelen)) + pos++; + return pos; +} + int add_file_to_index(struct index_state *istate, const char *path, int verbose) { int size, namelen; @@ -380,7 +408,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose) * from it, otherwise assume unexecutable regular file. */ struct cache_entry *ent; - int pos = index_name_pos(istate, path, namelen); + int pos = index_name_pos_also_unmerged(istate, path, namelen); ent = (0 <= pos) ? istate->cache[pos] : NULL; ce->ce_mode = ce_mode_from_stat(ent, st.st_mode); @@ -544,6 +544,13 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, if (!pat) continue; } + else if (prefixcmp(src->name, "refs/heads/")) + /* + * "matching refs"; traditionally we pushed everything + * including refs outside refs/heads/ hierarchy, but + * that does not make much sense these days. + */ + continue; if (pat) { const char *dst_side = pat->dst ? pat->dst : pat->src; diff --git a/revision.c b/revision.c index 7834bb108e..5184716bf0 100644 --- a/revision.c +++ b/revision.c @@ -667,7 +667,6 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->min_age = -1; revs->skip_count = -1; revs->max_count = -1; - revs->subject_prefix = "PATCH"; revs->prune_fn = NULL; revs->prune_data = NULL; @@ -95,7 +95,7 @@ void verify_non_filename(const char *prefix, const char *arg) const char *name; struct stat st; - if (is_inside_git_dir()) + if (!is_inside_work_tree() || is_inside_git_dir()) return; if (*arg == '-') return; /* flag */ @@ -174,41 +174,96 @@ static int inside_git_dir = -1; int is_inside_git_dir(void) { - if (inside_git_dir < 0) { - char buffer[1024]; - - if (is_bare_repository()) - return (inside_git_dir = 1); - if (getcwd(buffer, sizeof(buffer))) { - const char *git_dir = get_git_dir(), *cwd = buffer; - while (*git_dir && *git_dir == *cwd) { - git_dir++; - cwd++; - } - inside_git_dir = !*git_dir; - } else - inside_git_dir = 0; + if (inside_git_dir >= 0) + return inside_git_dir; + die("BUG: is_inside_git_dir called before setup_git_directory"); +} + +static int inside_work_tree = -1; + +int is_inside_work_tree(void) +{ + if (inside_git_dir >= 0) + return inside_work_tree; + die("BUG: is_inside_work_tree called before setup_git_directory"); +} + +static char *gitworktree_config; + +static int git_setup_config(const char *var, const char *value) +{ + if (!strcmp(var, "core.worktree")) { + if (gitworktree_config) + strlcpy(gitworktree_config, value, PATH_MAX); + return 0; } - return inside_git_dir; + return git_default_config(var, value); } const char *setup_git_directory_gently(int *nongit_ok) { static char cwd[PATH_MAX+1]; - const char *gitdirenv; - int len, offset; + char worktree[PATH_MAX+1], gitdir[PATH_MAX+1]; + const char *gitdirenv, *gitworktree; + int wt_rel_gitdir = 0; - /* - * If GIT_DIR is set explicitly, we're not going - * to do any discovery, but we still do repository - * validation. - */ gitdirenv = getenv(GIT_DIR_ENVIRONMENT); - if (gitdirenv) { - if (PATH_MAX - 40 < strlen(gitdirenv)) - die("'$%s' too big", GIT_DIR_ENVIRONMENT); - if (is_git_directory(gitdirenv)) + if (!gitdirenv) { + int len, offset; + + if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/') + die("Unable to read current working directory"); + + offset = len = strlen(cwd); + for (;;) { + if (is_git_directory(".git")) + break; + if (offset == 0) { + offset = -1; + break; + } + chdir(".."); + while (cwd[--offset] != '/') + ; /* do nothing */ + } + + if (offset >= 0) { + inside_work_tree = 1; + git_config(git_default_config); + if (offset == len) { + inside_git_dir = 0; + return NULL; + } + + cwd[len++] = '/'; + cwd[len] = '\0'; + inside_git_dir = !prefixcmp(cwd + offset + 1, ".git/"); + return cwd + offset + 1; + } + + if (chdir(cwd)) + die("Cannot come back to cwd"); + if (!is_git_directory(".")) { + if (nongit_ok) { + *nongit_ok = 1; + return NULL; + } + die("Not a git repository"); + } + setenv(GIT_DIR_ENVIRONMENT, cwd, 1); + gitdirenv = getenv(GIT_DIR_ENVIRONMENT); + if (!gitdirenv) + die("getenv after setenv failed"); + } + + if (PATH_MAX - 40 < strlen(gitdirenv)) { + if (nongit_ok) { + *nongit_ok = 1; return NULL; + } + die("$%s too big", GIT_DIR_ENVIRONMENT); + } + if (!is_git_directory(gitdirenv)) { if (nongit_ok) { *nongit_ok = 1; return NULL; @@ -218,41 +273,92 @@ const char *setup_git_directory_gently(int *nongit_ok) if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/') die("Unable to read current working directory"); + if (chdir(gitdirenv)) { + if (nongit_ok) { + *nongit_ok = 1; + return NULL; + } + die("Cannot change directory to $%s '%s'", + GIT_DIR_ENVIRONMENT, gitdirenv); + } + if (!getcwd(gitdir, sizeof(gitdir)-1) || gitdir[0] != '/') + die("Unable to read current working directory"); + if (chdir(cwd)) + die("Cannot come back to cwd"); - offset = len = strlen(cwd); - for (;;) { - if (is_git_directory(".git")) - break; - chdir(".."); - do { - if (!offset) { - if (is_git_directory(cwd)) { - if (chdir(cwd)) - die("Cannot come back to cwd"); - setenv(GIT_DIR_ENVIRONMENT, cwd, 1); - inside_git_dir = 1; - return NULL; - } - if (nongit_ok) { - if (chdir(cwd)) - die("Cannot come back to cwd"); - *nongit_ok = 1; - return NULL; - } - die("Not a git repository"); + /* + * In case there is a work tree we may change the directory, + * therefore make GIT_DIR an absolute path. + */ + if (gitdirenv[0] != '/') { + setenv(GIT_DIR_ENVIRONMENT, gitdir, 1); + gitdirenv = getenv(GIT_DIR_ENVIRONMENT); + if (!gitdirenv) + die("getenv after setenv failed"); + if (PATH_MAX - 40 < strlen(gitdirenv)) { + if (nongit_ok) { + *nongit_ok = 1; + return NULL; } - } while (cwd[--offset] != '/'); + die("$%s too big after expansion to absolute path", + GIT_DIR_ENVIRONMENT); + } + } + + strcat(cwd, "/"); + strcat(gitdir, "/"); + inside_git_dir = !prefixcmp(cwd, gitdir); + + gitworktree = getenv(GIT_WORK_TREE_ENVIRONMENT); + if (!gitworktree) { + gitworktree_config = worktree; + worktree[0] = '\0'; + } + git_config(git_setup_config); + if (!gitworktree) { + gitworktree_config = NULL; + if (worktree[0]) + gitworktree = worktree; + if (gitworktree && gitworktree[0] != '/') + wt_rel_gitdir = 1; + } + + if (wt_rel_gitdir && chdir(gitdirenv)) + die("Cannot change directory to $%s '%s'", + GIT_DIR_ENVIRONMENT, gitdirenv); + if (gitworktree && chdir(gitworktree)) { + if (nongit_ok) { + if (wt_rel_gitdir && chdir(cwd)) + die("Cannot come back to cwd"); + *nongit_ok = 1; + return NULL; + } + if (wt_rel_gitdir) + die("Cannot change directory to working tree '%s'" + " from $%s", gitworktree, GIT_DIR_ENVIRONMENT); + else + die("Cannot change directory to working tree '%s'", + gitworktree); } + if (!getcwd(worktree, sizeof(worktree)-1) || worktree[0] != '/') + die("Unable to read current working directory"); + strcat(worktree, "/"); + inside_work_tree = !prefixcmp(cwd, worktree); - if (offset == len) + if (gitworktree && inside_work_tree && !prefixcmp(worktree, gitdir) && + strcmp(worktree, gitdir)) { + inside_git_dir = 0; + } + + if (!inside_work_tree) { + if (chdir(cwd)) + die("Cannot come back to cwd"); return NULL; + } - /* Make "offset" point to past the '/', and add a '/' at the end */ - offset++; - cwd[len++] = '/'; - cwd[len] = 0; - inside_git_dir = !prefixcmp(cwd + offset, ".git/"); - return cwd + offset; + if (!strcmp(cwd, worktree)) + return NULL; + return cwd+strlen(worktree); } int git_config_perm(const char *var, const char *value) diff --git a/t/t0022-crlf-rename.sh b/t/t0022-crlf-rename.sh new file mode 100755 index 0000000000..430a1d1d38 --- /dev/null +++ b/t/t0022-crlf-rename.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +test_description='ignore CR in CRLF sequence while computing similiarity' + +. ./test-lib.sh + +test_expect_success setup ' + + cat ../t0022-crlf-rename.sh >sample && + git add sample && + + test_tick && + git commit -m Initial && + + sed -e "s/\$/
/" ../t0022-crlf-rename.sh >elpmas && + git add elpmas && + rm -f sample && + + test_tick && + git commit -a -m Second + +' + +test_expect_success 'diff -M' ' + + git diff-tree -M -r --name-status HEAD^ HEAD | + sed -e "s/R[0-9]*/RNUM/" >actual && + echo "RNUM sample elpmas" >expect && + diff -u expect actual + +' + +test_done diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh new file mode 100755 index 0000000000..f4294d72d9 --- /dev/null +++ b/t/t0030-stripspace.sh @@ -0,0 +1,355 @@ +#!/bin/sh +# +# Copyright (c) 2007 Carlos Rica +# + +test_description='git-stripspace' + +. ./test-lib.sh + +t40='A quick brown fox jumps over the lazy do' +s40=' ' +sss="$s40$s40$s40$s40$s40$s40$s40$s40$s40$s40" # 400 +ttt="$t40$t40$t40$t40$t40$t40$t40$t40$t40$t40" # 400 + +test_expect_success \ + 'long lines without spaces should be unchanged' ' + echo "$ttt" >expect && + git-stripspace <expect >actual && + git diff expect actual && + + echo "$ttt$ttt" >expect && + git-stripspace <expect >actual && + git diff expect actual && + + echo "$ttt$ttt$ttt" >expect && + git-stripspace <expect >actual && + git diff expect actual && + + echo "$ttt$ttt$ttt$ttt" >expect && + git-stripspace <expect >actual && + git diff expect actual +' + +test_expect_success \ + 'lines with spaces at the beginning should be unchanged' ' + echo "$sss$ttt" >expect && + git-stripspace <expect >actual && + git diff expect actual && + + echo "$sss$sss$ttt" >expect && + git-stripspace <expect >actual && + git diff expect actual && + + echo "$sss$sss$sss$ttt" >expect && + git-stripspace <expect >actual && + git diff expect actual +' + +test_expect_success \ + 'lines with intermediate spaces should be unchanged' ' + echo "$ttt$sss$ttt" >expect && + git-stripspace <expect >actual && + git diff expect actual && + + echo "$ttt$sss$sss$ttt" >expect && + git-stripspace <expect >actual && + git diff expect actual +' + +test_expect_success \ + 'consecutive blank lines should be unified' ' + printf "$ttt\n\n$ttt\n" > expect && + printf "$ttt\n\n\n\n\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt\n\n$ttt\n" > expect && + printf "$ttt$ttt\n\n\n\n\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt$ttt\n\n$ttt\n" > expect && + printf "$ttt$ttt$ttt\n\n\n\n\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n\n$ttt\n" > expect && + printf "$ttt\n\n\n\n\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n\n$ttt$ttt\n" > expect && + printf "$ttt\n\n\n\n\n$ttt$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n\n$ttt$ttt$ttt\n" > expect && + printf "$ttt\n\n\n\n\n$ttt$ttt$ttt\n" | git-stripspace >actual && + git diff expect actual +' + +test_expect_success \ + 'consecutive blank lines at the beginning should be removed' ' + printf "" > expect && + printf "\n" | git-stripspace >actual && + git diff expect actual && + + printf "" > expect && + printf "\n\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "" > expect && + printf "$sss\n$sss\n$sss\n" | git-stripspace >actual && + git diff expect actual && + + printf "" > expect && + printf "$sss$sss\n$sss\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "" > expect && + printf "\n$sss\n$sss$sss\n" | git-stripspace >actual && + git diff expect actual && + + printf "" > expect && + printf "$sss$sss$sss$sss\n\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "" > expect && + printf "\n$sss$sss$sss$sss\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "" > expect && + printf "\n\n$sss$sss$sss$sss\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "\n\n\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt\n" > expect && + printf "\n\n\n$ttt$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt$ttt\n" > expect && + printf "\n\n\n$ttt$ttt$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt$ttt$ttt\n" > expect && + printf "\n\n\n$ttt$ttt$ttt$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$sss\n$sss\n$sss\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "\n$sss\n$sss$sss\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$sss$sss\n$sss\n\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$sss$sss$sss\n\n\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "\n$sss$sss$sss\n\n$ttt\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "\n\n$sss$sss$sss\n$ttt\n" | git-stripspace >actual && + git diff expect actual +' + +test_expect_success \ + 'consecutive blank lines at the end should be removed' ' + printf "$ttt\n" > expect && + printf "$ttt\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$ttt\n\n\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt\n" > expect && + printf "$ttt$ttt\n\n\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt$ttt\n" > expect && + printf "$ttt$ttt$ttt\n\n\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt$ttt$ttt\n" > expect && + printf "$ttt$ttt$ttt$ttt\n\n\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$ttt\n$sss\n$sss\n$sss\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$ttt\n\n$sss\n$sss$sss\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$ttt\n$sss$sss\n$sss\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$ttt\n$sss$sss$sss\n\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$ttt\n\n$sss$sss$sss\n\n" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" > expect && + printf "$ttt\n\n\n$sss$sss$sss\n" | git-stripspace >actual && + git diff expect actual +' + +test_expect_success \ + 'text without newline at end should end with newline' ' + test `printf "$ttt" | git-stripspace | wc -l` -gt 0 && + test `printf "$ttt$ttt" | git-stripspace | wc -l` -gt 0 && + test `printf "$ttt$ttt$ttt" | git-stripspace | wc -l` -gt 0 && + test `printf "$ttt$ttt$ttt$ttt" | git-stripspace | wc -l` -gt 0 +' + +# text plus spaces at the end: + +test_expect_success \ + 'text plus spaces without newline at end should end with newline' ' + test `printf "$ttt$sss" | git-stripspace | wc -l` -gt 0 && + test `printf "$ttt$ttt$sss" | git-stripspace | wc -l` -gt 0 && + test `printf "$ttt$ttt$ttt$sss" | git-stripspace | wc -l` -gt 0 + test `printf "$ttt$sss$sss" | git-stripspace | wc -l` -gt 0 && + test `printf "$ttt$ttt$sss$sss" | git-stripspace | wc -l` -gt 0 && + test `printf "$ttt$sss$sss$sss" | git-stripspace | wc -l` -gt 0 +' + +test_expect_failure \ + 'text plus spaces without newline at end should not show spaces' ' + printf "$ttt$sss" | git-stripspace | grep -q " " || + printf "$ttt$ttt$sss" | git-stripspace | grep -q " " || + printf "$ttt$ttt$ttt$sss" | git-stripspace | grep -q " " || + printf "$ttt$sss$sss" | git-stripspace | grep -q " " || + printf "$ttt$ttt$sss$sss" | git-stripspace | grep -q " " || + printf "$ttt$sss$sss$sss" | git-stripspace | grep -q " " +' + +test_expect_success \ + 'text plus spaces without newline should show the correct lines' ' + printf "$ttt\n" >expect && + printf "$ttt$sss" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" >expect && + printf "$ttt$sss$sss" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt\n" >expect && + printf "$ttt$sss$sss$sss" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt\n" >expect && + printf "$ttt$ttt$sss" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt\n" >expect && + printf "$ttt$ttt$sss$sss" | git-stripspace >actual && + git diff expect actual && + + printf "$ttt$ttt$ttt\n" >expect && + printf "$ttt$ttt$ttt$sss" | git-stripspace >actual && + git diff expect actual +' + +test_expect_failure \ + 'text plus spaces at end should not show spaces' ' + echo "$ttt$sss" | git-stripspace | grep -q " " || + echo "$ttt$ttt$sss" | git-stripspace | grep -q " " || + echo "$ttt$ttt$ttt$sss" | git-stripspace | grep -q " " || + echo "$ttt$sss$sss" | git-stripspace | grep -q " " || + echo "$ttt$ttt$sss$sss" | git-stripspace | grep -q " " || + echo "$ttt$sss$sss$sss" | git-stripspace | grep -q " " +' + +test_expect_success \ + 'text plus spaces at end should be cleaned and newline must remain' ' + echo "$ttt" >expect && + echo "$ttt$sss" | git-stripspace >actual && + git diff expect actual && + + echo "$ttt" >expect && + echo "$ttt$sss$sss" | git-stripspace >actual && + git diff expect actual && + + echo "$ttt" >expect && + echo "$ttt$sss$sss$sss" | git-stripspace >actual && + git diff expect actual && + + echo "$ttt$ttt" >expect && + echo "$ttt$ttt$sss" | git-stripspace >actual && + git diff expect actual && + + echo "$ttt$ttt" >expect && + echo "$ttt$ttt$sss$sss" | git-stripspace >actual && + git diff expect actual && + + echo "$ttt$ttt$ttt" >expect && + echo "$ttt$ttt$ttt$sss" | git-stripspace >actual && + git diff expect actual +' + +# spaces only: + +test_expect_success \ + 'spaces with newline at end should be replaced with empty string' ' + printf "" >expect && + + echo | git-stripspace >actual && + git diff expect actual && + + echo "$sss" | git-stripspace >actual && + git diff expect actual && + + echo "$sss$sss" | git-stripspace >actual && + git diff expect actual && + + echo "$sss$sss$sss" | git-stripspace >actual && + git diff expect actual && + + echo "$sss$sss$sss$sss" | git-stripspace >actual && + git diff expect actual +' + +test_expect_failure \ + 'spaces without newline at end should not show spaces' ' + printf "" | git-stripspace | grep -q " " || + printf "$sss" | git-stripspace | grep -q " " || + printf "$sss$sss" | git-stripspace | grep -q " " || + printf "$sss$sss$sss" | git-stripspace | grep -q " " || + printf "$sss$sss$sss$sss" | git-stripspace | grep -q " " +' + +test_expect_success \ + 'spaces without newline at end should be replaced with empty string' ' + printf "" >expect && + + printf "" | git-stripspace >actual && + git diff expect actual + + printf "$sss$sss" | git-stripspace >actual && + git diff expect actual + + printf "$sss$sss$sss" | git-stripspace >actual && + git diff expect actual + + printf "$sss$sss$sss$sss" | git-stripspace >actual && + git diff expect actual +' + +test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 7a77bef4c0..a2c11c4639 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -471,11 +471,57 @@ test_expect_success bool ' done && cmp expect result' -test_expect_failure 'invalid bool' ' +test_expect_failure 'invalid bool (--get)' ' git-config bool.nobool foobar && git-config --bool --get bool.nobool' +test_expect_failure 'invalid bool (set)' ' + + git-config --bool bool.nobool foobar' + +rm .git/config + +cat > expect <<\EOF +[bool] + true1 = true + true2 = true + true3 = true + true4 = true + false1 = false + false2 = false + false3 = false + false4 = false +EOF + +test_expect_success 'set --bool' ' + + git-config --bool bool.true1 01 && + git-config --bool bool.true2 -1 && + git-config --bool bool.true3 YeS && + git-config --bool bool.true4 true && + git-config --bool bool.false1 000 && + git-config --bool bool.false2 "" && + git-config --bool bool.false3 nO && + git-config --bool bool.false4 FALSE && + cmp expect .git/config' + +rm .git/config + +cat > expect <<\EOF +[int] + val1 = 1 + val2 = -1 + val3 = 5242880 +EOF + +test_expect_success 'set --int' ' + + git-config --int int.val1 01 && + git-config --int int.val2 -1 && + git-config --int int.val3 5m && + cmp expect .git/config' + rm .git/config git-config quote.leading " test" @@ -529,25 +575,23 @@ cat > .git/config <<\EOF EOF cat > expect <<\EOF -Key: section.sub=section.val1 -Value: foo=bar -Key: section.sub=section.val2 -Value: foo -bar -Key: section.sub=section.val3 -Value: +section.sub=section.val1 +foo=barQsection.sub=section.val2 +foo +barQsection.sub=section.val3 -Key: section.sub=section.val4 -Value: -Key: section.sub=section.val5 +Qsection.sub=section.val4 +Qsection.sub=section.val5Q EOF -git config --null --list | perl -0ne 'chop;($key,$value)=split(/\n/,$_,2);print "Key: $key\n";print "Value: $value\n" if defined($value)' > result +git config --null --list | tr '[\000]' 'Q' > result +echo >>result test_expect_success '--null --list' 'cmp result expect' -git config --null --get-regexp 'val[0-9]' | perl -0ne 'chop;($key,$value)=split(/\n/,$_,2);print "Key: $key\n";print "Value: $value\n" if defined($value)' > result +git config --null --get-regexp 'val[0-9]' | tr '[\000]' 'Q' > result +echo >>result test_expect_success '--null --get-regexp' 'cmp result expect' diff --git a/t/t1420-lost-found.sh b/t/t1420-lost-found.sh new file mode 100755 index 0000000000..dc9e402c55 --- /dev/null +++ b/t/t1420-lost-found.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test fsck --lost-found' +. ./test-lib.sh + +test_expect_success setup ' + git config core.logAllRefUpdates 0 && + : > file1 && + git add file1 && + test_tick && + git commit -m initial && + echo 1 > file1 && + echo 2 > file2 && + git add file1 file2 && + test_tick && + git commit -m second && + echo 3 > file3 && + git add file3 +' + +test_expect_success 'lost and found something' ' + git rev-parse HEAD > lost-commit && + git rev-parse :file3 > lost-other && + test_tick && + git reset --hard HEAD^ && + git fsck --lost-found && + test 2 = $(ls .git/lost-found/*/* | wc -l) && + test -f .git/lost-found/commit/$(cat lost-commit) && + test -f .git/lost-found/other/$(cat lost-other) +' + +test_done diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh new file mode 100755 index 0000000000..ec4996637d --- /dev/null +++ b/t/t1500-rev-parse.sh @@ -0,0 +1,77 @@ +#!/bin/sh + +test_description='test git rev-parse' +. ./test-lib.sh + +test_rev_parse() { + name=$1 + shift + + test_expect_success "$name: is-bare-repository" \ + "test '$1' = \"\$(git rev-parse --is-bare-repository)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: is-inside-git-dir" \ + "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: is-inside-work-tree" \ + "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: prefix" \ + "test '$1' = \"\$(git rev-parse --show-prefix)\"" + shift + [ $# -eq 0 ] && return +} + +test_rev_parse toplevel false false true '' + +cd .git || exit 1 +test_rev_parse .git/ false true true .git/ +cd objects || exit 1 +test_rev_parse .git/objects/ false true true .git/objects/ +cd ../.. || exit 1 + +mkdir -p sub/dir || exit 1 +cd sub/dir || exit 1 +test_rev_parse subdirectory false false true sub/dir/ +cd ../.. || exit 1 + +git config core.bare true +test_rev_parse 'core.bare = true' true false true + +git config --unset core.bare +test_rev_parse 'core.bare undefined' false false true + +mkdir work || exit 1 +cd work || exit 1 +export GIT_DIR=../.git +export GIT_CONFIG="$GIT_DIR"/config + +git config core.bare false +test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true '' + +git config core.bare true +test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false true '' + +git config --unset core.bare +test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true '' + +mv ../.git ../repo.git || exit 1 +export GIT_DIR=../repo.git +export GIT_CONFIG="$GIT_DIR"/config + +git config core.bare false +test_rev_parse 'GIT_DIR=../repo.git, core.bare = false' false false true '' + +git config core.bare true +test_rev_parse 'GIT_DIR=../repo.git, core.bare = true' true false true '' + +git config --unset core.bare +test_rev_parse 'GIT_DIR=../repo.git, core.bare undefined' true false true '' + +test_done diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh new file mode 100755 index 0000000000..aadeeab9ab --- /dev/null +++ b/t/t1501-worktree.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +test_description='test separate work tree' +. ./test-lib.sh + +test_rev_parse() { + name=$1 + shift + + test_expect_success "$name: is-bare-repository" \ + "test '$1' = \"\$(git rev-parse --is-bare-repository)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: is-inside-git-dir" \ + "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: is-inside-work-tree" \ + "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\"" + shift + [ $# -eq 0 ] && return + + test_expect_success "$name: prefix" \ + "test '$1' = \"\$(git rev-parse --show-prefix)\"" + shift + [ $# -eq 0 ] && return +} + +mkdir -p work/sub/dir || exit 1 +mv .git repo.git || exit 1 + +say "core.worktree = relative path" +export GIT_DIR=repo.git +export GIT_CONFIG=$GIT_DIR/config +unset GIT_WORK_TREE +git config core.worktree ../work +test_rev_parse 'outside' false false false +cd work || exit 1 +export GIT_DIR=../repo.git +export GIT_CONFIG=$GIT_DIR/config +test_rev_parse 'inside' false false true '' +cd sub/dir || exit 1 +export GIT_DIR=../../../repo.git +export GIT_CONFIG=$GIT_DIR/config +test_rev_parse 'subdirectory' false false true sub/dir/ +cd ../../.. || exit 1 + +say "core.worktree = absolute path" +export GIT_DIR=$(pwd)/repo.git +export GIT_CONFIG=$GIT_DIR/config +git config core.worktree "$(pwd)/work" +test_rev_parse 'outside' false false false +cd work || exit 1 +test_rev_parse 'inside' false false true '' +cd sub/dir || exit 1 +test_rev_parse 'subdirectory' false false true sub/dir/ +cd ../../.. || exit 1 + +say "GIT_WORK_TREE=relative path (override core.worktree)" +export GIT_DIR=$(pwd)/repo.git +export GIT_CONFIG=$GIT_DIR/config +git config core.worktree non-existent +export GIT_WORK_TREE=work +test_rev_parse 'outside' false false false +cd work || exit 1 +export GIT_WORK_TREE=. +test_rev_parse 'inside' false false true '' +cd sub/dir || exit 1 +export GIT_WORK_TREE=../.. +test_rev_parse 'subdirectory' false false true sub/dir/ +cd ../../.. || exit 1 + +mv work repo.git/work + +say "GIT_WORK_TREE=absolute path, work tree below git dir" +export GIT_DIR=$(pwd)/repo.git +export GIT_CONFIG=$GIT_DIR/config +export GIT_WORK_TREE=$(pwd)/repo.git/work +test_rev_parse 'outside' false false false +cd repo.git || exit 1 +test_rev_parse 'in repo.git' false true false +cd objects || exit 1 +test_rev_parse 'in repo.git/objects' false true false +cd ../work || exit 1 +test_rev_parse 'in repo.git/work' false false true '' +cd sub/dir || exit 1 +test_rev_parse 'in repo.git/sub/dir' false false true sub/dir/ +cd ../../../.. || exit 1 + +test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh new file mode 100755 index 0000000000..883cf29595 --- /dev/null +++ b/t/t3404-rebase-interactive.sh @@ -0,0 +1,191 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='git rebase interactive + +This test runs git rebase "interactively", by faking an edit, and verifies +that the result still makes sense. +' +. ./test-lib.sh + +# set up two branches like this: +# +# A - B - C - D - E +# \ +# F - G - H +# \ +# I +# +# where B, D and G touch the same file. + +test_expect_success 'setup' ' + : > file1 && + git add file1 && + test_tick && + git commit -m A && + git tag A && + echo 1 > file1 && + test_tick && + git commit -m B file1 && + : > file2 && + git add file2 && + test_tick && + git commit -m C && + echo 2 > file1 && + test_tick && + git commit -m D file1 && + : > file3 && + git add file3 && + test_tick && + git commit -m E && + git checkout -b branch1 A && + : > file4 && + git add file4 && + test_tick && + git commit -m F && + git tag F && + echo 3 > file1 && + test_tick && + git commit -m G file1 && + : > file5 && + git add file5 && + test_tick && + git commit -m H && + git checkout -b branch2 F && + : > file6 && + git add file6 && + test_tick && + git commit -m I && + git tag I +' + +cat > fake-editor.sh << EOF +#!/bin/sh +test "\$1" = .git/COMMIT_EDITMSG && exit +test -z "\$FAKE_LINES" && exit +grep -v "^#" < "\$1" > "\$1".tmp +rm "\$1" +cat "\$1".tmp +action=pick +for line in \$FAKE_LINES; do + case \$line in + squash) + action="\$line";; + *) + echo sed -n "\${line}s/^pick/\$action/p" + sed -n "\${line}p" < "\$1".tmp + sed -n "\${line}s/^pick/\$action/p" < "\$1".tmp >> "\$1" + action=pick;; + esac +done +EOF + +chmod a+x fake-editor.sh +VISUAL="$(pwd)/fake-editor.sh" +export VISUAL + +test_expect_success 'no changes are a nop' ' + git rebase -i F && + test $(git rev-parse I) = $(git rev-parse HEAD) +' + +test_expect_success 'rebase on top of a non-conflicting commit' ' + git checkout branch1 && + git tag original-branch1 && + git rebase -i branch2 && + test file6 = $(git diff --name-only original-branch1) && + test $(git rev-parse I) = $(git rev-parse HEAD~2) +' + +test_expect_success 'reflog for the branch shows state before rebase' ' + test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1) +' + +test_expect_success 'exchange two commits' ' + FAKE_LINES="2 1" git rebase -i HEAD~2 && + test H = $(git cat-file commit HEAD^ | tail -n 1) && + test G = $(git cat-file commit HEAD | tail -n 1) +' + +cat > expect << EOF +diff --git a/file1 b/file1 +index e69de29..00750ed 100644 +--- a/file1 ++++ b/file1 +@@ -0,0 +1 @@ ++3 +EOF + +cat > expect2 << EOF +<<<<<<< HEAD:file1 +2 +======= +3 +>>>>>>> b7ca976... G:file1 +EOF + +test_expect_success 'stop on conflicting pick' ' + git tag new-branch1 && + ! git rebase -i master && + diff -u expect .git/.dotest-merge/patch && + diff -u expect2 file1 && + test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) && + test 0 = $(grep -v "^#" < .git/.dotest-merge/todo | wc -l) +' + +test_expect_success 'abort' ' + git rebase --abort && + test $(git rev-parse new-branch1) = $(git rev-parse HEAD) && + ! test -d .git/.dotest-merge +' + +test_expect_success 'retain authorship' ' + echo A > file7 && + git add file7 && + test_tick && + GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" && + git tag twerp && + git rebase -i --onto master HEAD^ && + git show HEAD | grep "^Author: Twerp Snog" +' + +test_expect_success 'squash' ' + git reset --hard twerp && + echo B > file7 && + test_tick && + GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 && + echo "******************************" && + FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 && + test B = $(cat file7) && + test $(git rev-parse HEAD^) = $(git rev-parse master) +' + +test_expect_success 'retain authorship when squashing' ' + git show HEAD | grep "^Author: Nitfol" +' + +test_expect_success 'preserve merges with -p' ' + git checkout -b to-be-preserved master^ && + : > unrelated-file && + git add unrelated-file && + test_tick && + git commit -m "unrelated" && + git checkout -b to-be-rebased master && + echo B > file1 && + test_tick && + git commit -m J file1 && + test_tick && + git merge to-be-preserved && + echo C > file1 && + test_tick && + git commit -m K file1 && + 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_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index ad8cc7d4ae..e6466d74a0 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -110,4 +110,37 @@ test_expect_success 'check correct prefix detection' ' git add 1/2/a 1/3/b 1/2/c ' +test_expect_success 'git add with filemode=0, symlinks=0, and unmerged entries' ' + for s in 1 2 3 + do + echo $s > stage$s + echo "100755 $(git hash-object -w stage$s) $s file" + echo "120000 $(printf $s | git hash-object -w -t blob --stdin) $s symlink" + done | git update-index --index-info && + git config core.filemode 0 && + git config core.symlinks 0 && + echo new > file && + echo new > symlink && + git add file symlink && + git ls-files --stage | grep "^100755 .* 0 file$" && + git ls-files --stage | grep "^120000 .* 0 symlink$" +' + +test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over stage 1' ' + git rm --cached -f file symlink && + ( + echo "100644 $(git hash-object -w stage1) 1 file" + echo "100755 $(git hash-object -w stage2) 2 file" + echo "100644 $(printf $s | git hash-object -w -t blob --stdin) 1 symlink" + echo "120000 $(printf $s | git hash-object -w -t blob --stdin) 2 symlink" + ) | git update-index --index-info && + git config core.filemode 0 && + git config core.symlinks 0 && + echo new > file && + echo new > symlink && + git add file symlink && + git ls-files --stage | grep "^100755 .* 0 file$" && + git ls-files --stage | grep "^120000 .* 0 symlink$" +' + test_done diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh new file mode 100755 index 0000000000..63f950b179 --- /dev/null +++ b/t/t3902-quoted.sh @@ -0,0 +1,126 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='quoted output' + +. ./test-lib.sh + +FN='濱野' +GN='純' +HT=' ' +LF=' +' +DQ='"' + +for_each_name () { + for name in \ + Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \ + "$FN$HT$GN" "$FN$LF$GN" "$FN $GN" "$FN$GN" "$FN$DQ$GN" \ + "With SP in it" + do + eval "$1" + done +} + +test_expect_success setup ' + + for_each_name "echo initial >\"\$name\"" + git add . && + git commit -q -m Initial && + + for_each_name "echo second >\"\$name\"" && + git commit -a -m Second + + for_each_name "echo modified >\"\$name\"" + +' + +cat >expect.quoted <<\EOF +Name +"Name and a\nLF" +"Name and an\tHT" +"Name\"" +With SP in it +"\346\277\261\351\207\216\t\347\264\224" +"\346\277\261\351\207\216\n\347\264\224" +"\346\277\261\351\207\216 \347\264\224" +"\346\277\261\351\207\216\"\347\264\224" +"\346\277\261\351\207\216\347\264\224" +EOF + +cat >expect.raw <<\EOF +Name +"Name and a\nLF" +"Name and an\tHT" +"Name\"" +With SP in it +"濱野\t純" +"濱野\n純" +濱野 純 +"濱野\"純" +濱野純 +EOF + +test_expect_success 'check fully quoted output from ls-files' ' + + git ls-files >current && diff -u expect.quoted current + +' + +test_expect_success 'check fully quoted output from diff-files' ' + + git diff --name-only >current && + diff -u expect.quoted current + +' + +test_expect_success 'check fully quoted output from diff-index' ' + + git diff --name-only HEAD >current && + diff -u expect.quoted current + +' + +test_expect_success 'check fully quoted output from diff-tree' ' + + git diff --name-only HEAD^ HEAD >current && + diff -u expect.quoted current + +' + +test_expect_success 'setting core.quotepath' ' + + git config --bool core.quotepath false + +' + +test_expect_success 'check fully quoted output from ls-files' ' + + git ls-files >current && diff -u expect.raw current + +' + +test_expect_success 'check fully quoted output from diff-files' ' + + git diff --name-only >current && + diff -u expect.raw current + +' + +test_expect_success 'check fully quoted output from diff-index' ' + + git diff --name-only HEAD >current && + diff -u expect.raw current + +' + +test_expect_success 'check fully quoted output from diff-tree' ' + + git diff --name-only HEAD^ HEAD >current && + diff -u expect.raw current + +' + +test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 8f4c29a6b5..b453b42af7 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -242,6 +242,8 @@ format-patch --inline --stdout initial..side format-patch --inline --stdout initial..master^ format-patch --inline --stdout initial..master format-patch --inline --stdout --subject-prefix=TESTCASE initial..master +config format.subjectprefix DIFFERENT_PREFIX +format-patch --inline --stdout initial..master^^ diff --abbrev initial..side diff -r initial..side diff --git a/t/t4013/diff.config_format.subjectprefix_DIFFERENT_PREFIX b/t/t4013/diff.config_format.subjectprefix_DIFFERENT_PREFIX new file mode 100644 index 0000000000..78f8970e2b --- /dev/null +++ b/t/t4013/diff.config_format.subjectprefix_DIFFERENT_PREFIX @@ -0,0 +1,2 @@ +$ git config format.subjectprefix DIFFERENT_PREFIX +$ diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ new file mode 100644 index 0000000000..b8e81e1552 --- /dev/null +++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ @@ -0,0 +1,60 @@ +$ git format-patch --inline --stdout initial..master^^ +From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:01:00 +0000 +Subject: [DIFFERENT_PREFIX] Second +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n" + +This is a multi-part message in MIME format. +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/plain; charset=UTF-8; format=fixed +Content-Transfer-Encoding: 8bit + + +This is the second commit. +--- + dir/sub | 2 ++ + file0 | 3 +++ + file2 | 3 --- + 3 files changed, 5 insertions(+), 3 deletions(-) + delete mode 100644 file2 +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Transfer-Encoding: 8bit +Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 + +--------------g-i-t--v-e-r-s-i-o-n-- + + +$ diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 08d58e1c8c..c0fa2ba404 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -226,7 +226,7 @@ test_expect_success 'push with colon-less refspec (3)' ' git branch -f frotz master && git push testrepo frotz && check_push_result $the_commit heads/frotz && - test "$( cd testrepo && git show-ref | wc -l )" = 1 + test 1 = $( cd testrepo && git show-ref | wc -l ) ' test_expect_success 'push with colon-less refspec (4)' ' @@ -239,7 +239,7 @@ test_expect_success 'push with colon-less refspec (4)' ' git tag -f frotz && git push testrepo frotz && check_push_result $the_commit tags/frotz && - test "$( cd testrepo && git show-ref | wc -l )" = 1 + test 1 = $( cd testrepo && git show-ref | wc -l ) ' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh new file mode 100755 index 0000000000..5d15449be5 --- /dev/null +++ b/t/t7004-tag.sh @@ -0,0 +1,686 @@ +#!/bin/sh +# +# Copyright (c) 2007 Carlos Rica +# + +test_description='git-tag + +Basic tests for operations with tags.' + +. ./test-lib.sh + +# creating and listing lightweight tags: + +tag_exists () { + git show-ref --quiet --verify refs/tags/"$1" +} + +# todo: git tag -l now returns always zero, when fixed, change this test +test_expect_success 'listing all tags in an empty tree should succeed' \ + 'git tag -l' + +test_expect_success 'listing all tags in an empty tree should output nothing' \ + 'test `git-tag -l | wc -l` -eq 0' + +test_expect_failure 'looking for a tag in an empty tree should fail' \ + 'tag_exists mytag' + +test_expect_success 'creating a tag in an empty tree should 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 && + ! tag_exists mytaghead +' + +test_expect_success 'creating a tag for an unknown revision should fail' ' + ! git-tag mytagnorev aaaaaaaaaaa && + ! tag_exists mytagnorev +' + +# commit used in the tests, test_tick is also called here to freeze the date: +test_expect_success 'creating a tag using default HEAD should succeed' ' + test_tick && + echo foo >foo && + git add foo && + git commit -m Foo && + git tag mytag +' + +test_expect_success 'listing all tags if one exists should succeed' \ + 'git-tag -l' + +test_expect_success 'listing all tags if one exists should output that tag' \ + 'test `git-tag -l` = mytag' + +# pattern matching: + +test_expect_success 'listing a tag using a matching pattern should succeed' \ + 'git-tag -l mytag' + +test_expect_success \ + 'listing a tag using a matching pattern should output that tag' \ + 'test `git-tag -l mytag` = mytag' + +# todo: git tag -l now returns always zero, when fixed, change this test +test_expect_success \ + 'listing tags using a non-matching pattern should suceed' \ + 'git-tag -l xxx' + +test_expect_success \ + 'listing tags using a non-matching pattern should output nothing' \ + 'test `git-tag -l xxx | wc -l` -eq 0' + +# special cases for creating tags: + +test_expect_failure \ + 'trying to create a tag with the name of one existing should 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 `git-tag -l | wc -l` -eq 1 +' + +test_expect_success 'creating a tag using HEAD directly should succeed' ' + git tag myhead HEAD && + tag_exists myhead +' + +# deleting tags: + +test_expect_success 'trying to delete an unknown tag should fail' ' + ! tag_exists unknown-tag && + ! git-tag -d unknown-tag +' + +cat >expect <<EOF +myhead +mytag +EOF +test_expect_success \ + 'trying to delete tags without params should succeed and do nothing' ' + git tag -l > actual && git diff expect actual && + git-tag -d && + git tag -l > actual && git diff expect actual +' + +test_expect_success \ + 'deleting two existing tags in one command should succeed' ' + tag_exists mytag && + tag_exists myhead && + git-tag -d mytag myhead && + ! tag_exists mytag && + ! tag_exists myhead +' + +test_expect_success \ + 'creating a tag with the name of another deleted one should succeed' ' + ! tag_exists mytag && + git-tag mytag && + tag_exists mytag +' + +test_expect_success \ + 'trying to delete two tags, existing and not, should fail in the 2nd' ' + tag_exists mytag && + ! tag_exists myhead && + ! git-tag -d mytag anothertag && + ! tag_exists mytag && + ! tag_exists myhead +' + +test_expect_failure 'trying to delete an already deleted tag should fail' \ + 'git-tag -d mytag' + +# listing various tags with pattern matching: + +cat >expect <<EOF +a1 +aa1 +cba +t210 +t211 +v0.2.1 +v1.0 +v1.0.1 +v1.1.3 +EOF +test_expect_success 'listing all tags should print them ordered' ' + git tag v1.0.1 && + git tag t211 && + git tag aa1 && + git tag v0.2.1 && + git tag v1.1.3 && + git tag cba && + git tag a1 && + git tag v1.0 && + git tag t210 && + git tag -l > actual + git diff expect actual +' + +cat >expect <<EOF +a1 +aa1 +cba +EOF +test_expect_success \ + 'listing tags with substring as pattern must print those matching' ' + git-tag -l a > actual && + git-diff expect actual +' + +cat >expect <<EOF +v0.2.1 +v1.0.1 +v1.1.3 +EOF +test_expect_success \ + 'listing tags with substring as pattern must print those matching' ' + git-tag -l .1 > actual && + git-diff expect actual +' + +cat >expect <<EOF +t210 +t211 +EOF +test_expect_success \ + 'listing tags with substring as pattern must print those matching' ' + git-tag -l t21 > actual && + git-diff expect actual +' + +cat >expect <<EOF +a1 +aa1 +EOF +test_expect_success \ + 'listing tags using a name as pattern must print those matching' ' + git-tag -l a1 > actual && + git-diff expect actual +' + +cat >expect <<EOF +v1.0 +v1.0.1 +EOF +test_expect_success \ + 'listing tags using a name as pattern must print those matching' ' + git-tag -l v1.0 > actual && + git-diff expect actual +' + +cat >expect <<EOF +v1.1.3 +EOF +test_expect_success \ + 'listing tags with ? in the pattern should print those matching' ' + git-tag -l "1.1?" > actual && + git-diff expect actual +' + +>expect +test_expect_success \ + 'listing tags using v.* should print nothing because none have v.' ' + git-tag -l "v.*" > actual && + git-diff expect actual +' + +cat >expect <<EOF +v0.2.1 +v1.0 +v1.0.1 +v1.1.3 +EOF +test_expect_success \ + 'listing tags using v* should print only those having v' ' + git-tag -l "v*" > actual && + git-diff expect actual +' + +# creating and verifying lightweight tags: + +test_expect_success \ + 'a non-annotated tag created without parameters should point to HEAD' ' + git-tag non-annotated-tag && + test $(git-cat-file -t non-annotated-tag) = commit && + test $(git-rev-parse non-annotated-tag) = $(git-rev-parse HEAD) +' + +test_expect_failure 'trying to verify an unknown tag should fail' \ + 'git-tag -v unknown-tag' + +test_expect_failure \ + 'trying to verify a non-annotated and non-signed tag should fail' \ + 'git-tag -v non-annotated-tag' + +# creating annotated tags: + +get_tag_msg () { + git cat-file tag "$1" | sed -e "/BEGIN PGP/q" +} + +# run test_tick before committing always gives the time in that timezone +get_tag_header () { +cat <<EOF +object $2 +type $3 +tag $1 +tagger C O Mitter <committer@example.com> $4 -0700 + +EOF +} + +commit=$(git rev-parse HEAD) +time=$test_tick + +get_tag_header annotated-tag $commit commit $time >expect +echo "A message" >>expect +test_expect_success \ + 'creating an annotated tag with -m message should succeed' ' + git-tag -m "A message" annotated-tag && + get_tag_msg annotated-tag >actual && + git diff expect actual +' + +cat >msgfile <<EOF +Another message +in a file. +EOF +get_tag_header file-annotated-tag $commit commit $time >expect +cat msgfile >>expect +test_expect_success \ + 'creating an annotated tag with -F messagefile should succeed' ' + git-tag -F msgfile file-annotated-tag && + get_tag_msg file-annotated-tag >actual && + git diff expect actual +' + +# blank and empty messages: + +get_tag_header empty-annotated-tag $commit commit $time >expect +test_expect_success \ + 'creating a tag with an empty -m message should succeed' ' + git-tag -m "" empty-annotated-tag && + get_tag_msg empty-annotated-tag >actual && + git diff expect actual +' + +>emptyfile +get_tag_header emptyfile-annotated-tag $commit commit $time >expect +test_expect_success \ + 'creating a tag with an empty -F messagefile should succeed' ' + git-tag -F emptyfile emptyfile-annotated-tag && + get_tag_msg emptyfile-annotated-tag >actual && + git diff expect actual +' + +printf '\n\n \n\t\nLeading blank lines\n' >blanksfile +printf '\n\t \t \nRepeated blank lines\n' >>blanksfile +printf '\n\n\nTrailing spaces \t \n' >>blanksfile +printf '\nTrailing blank lines\n\n\t \n\n' >>blanksfile +get_tag_header blanks-annotated-tag $commit commit $time >expect +cat >>expect <<EOF +Leading blank lines + +Repeated blank lines + +Trailing spaces + +Trailing blank lines +EOF +test_expect_success \ + 'extra blanks in the message for an annotated tag should be removed' ' + git-tag -F blanksfile blanks-annotated-tag && + get_tag_msg blanks-annotated-tag >actual && + git diff expect actual +' + +get_tag_header blank-annotated-tag $commit commit $time >expect +test_expect_success \ + 'creating a tag with blank -m message with spaces should succeed' ' + git-tag -m " " blank-annotated-tag && + get_tag_msg blank-annotated-tag >actual && + git diff expect actual +' + +echo ' ' >blankfile +echo '' >>blankfile +echo ' ' >>blankfile +get_tag_header blankfile-annotated-tag $commit commit $time >expect +test_expect_success \ + 'creating a tag with blank -F messagefile with spaces should succeed' ' + git-tag -F blankfile blankfile-annotated-tag && + get_tag_msg blankfile-annotated-tag >actual && + git diff expect actual +' + +printf ' ' >blanknonlfile +get_tag_header blanknonlfile-annotated-tag $commit commit $time >expect +test_expect_success \ + 'creating a tag with -F file of spaces and no newline should succeed' ' + git-tag -F blanknonlfile blanknonlfile-annotated-tag && + get_tag_msg blanknonlfile-annotated-tag >actual && + git diff expect actual +' + +# messages with commented lines: + +cat >commentsfile <<EOF +# A comment + +############ +The message. +############ +One line. + + +# commented lines +# commented lines + +Another line. +# comments + +Last line. +EOF +get_tag_header comments-annotated-tag $commit commit $time >expect +cat >>expect <<EOF +The message. +One line. + +Another line. + +Last line. +EOF +test_expect_success \ + 'creating a tag using a -F messagefile with #comments should succeed' ' + git-tag -F commentsfile comments-annotated-tag && + get_tag_msg comments-annotated-tag >actual && + git diff expect actual +' + +get_tag_header comment-annotated-tag $commit commit $time >expect +test_expect_success \ + 'creating a tag with a #comment in the -m message should succeed' ' + git-tag -m "#comment" comment-annotated-tag && + get_tag_msg comment-annotated-tag >actual && + git diff expect actual +' + +echo '#comment' >commentfile +echo '' >>commentfile +echo '####' >>commentfile +get_tag_header commentfile-annotated-tag $commit commit $time >expect +test_expect_success \ + 'creating a tag with #comments in the -F messagefile should succeed' ' + git-tag -F commentfile commentfile-annotated-tag && + get_tag_msg commentfile-annotated-tag >actual && + git diff expect actual +' + +printf '#comment' >commentnonlfile +get_tag_header commentnonlfile-annotated-tag $commit commit $time >expect +test_expect_success \ + 'creating a tag with a file of #comment and no newline should succeed' ' + git-tag -F commentnonlfile commentnonlfile-annotated-tag && + get_tag_msg commentnonlfile-annotated-tag >actual && + git diff expect actual +' + +# trying to verify annotated non-signed tags: + +test_expect_success \ + 'trying to verify an annotated non-signed tag should fail' ' + tag_exists annotated-tag && + ! 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 +' + +# creating and verifying signed tags: + +gpg --version >/dev/null +if [ $? -eq 127 ]; then + echo "Skipping signed tags tests, because gpg was not found" + test_done + exit +fi + +# key generation info: gpg --homedir t/t7004 --gen-key +# Type DSA and Elgamal, size 2048 bits, no expiration date. +# Name and email: C O Mitter <committer@example.com> +# No password given, to enable non-interactive operation. + +cp -R ../t7004 ./gpghome +chmod 0700 gpghome +export GNUPGHOME="$(pwd)/gpghome" + +get_tag_header signed-tag $commit commit $time >expect +echo 'A signed tag message' >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success 'creating a signed tag with -m message should succeed' ' + git-tag -s -m "A signed tag message" signed-tag && + get_tag_msg signed-tag >actual && + git-diff expect actual +' + +test_expect_success 'verifying a signed tag should succeed' \ + 'git-tag -v signed-tag' + +test_expect_success 'verifying a forged tag should fail' ' + forged=$(git cat-file tag signed-tag | + sed -e "s/signed-tag/forged-tag/" | + git mktag) && + git tag forged-tag $forged && + ! git-tag -v forged-tag +' + +# blank and empty messages for signed tags: + +get_tag_header empty-signed-tag $commit commit $time >expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with an empty -m message should succeed' ' + git-tag -s -m "" empty-signed-tag && + get_tag_msg empty-signed-tag >actual && + git diff expect actual && + git-tag -v empty-signed-tag +' + +>sigemptyfile +get_tag_header emptyfile-signed-tag $commit commit $time >expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with an empty -F messagefile should succeed' ' + git-tag -s -F sigemptyfile emptyfile-signed-tag && + get_tag_msg emptyfile-signed-tag >actual && + git diff expect actual && + git-tag -v emptyfile-signed-tag +' + +printf '\n\n \n\t\nLeading blank lines\n' > sigblanksfile +printf '\n\t \t \nRepeated blank lines\n' >>sigblanksfile +printf '\n\n\nTrailing spaces \t \n' >>sigblanksfile +printf '\nTrailing blank lines\n\n\t \n\n' >>sigblanksfile +get_tag_header blanks-signed-tag $commit commit $time >expect +cat >>expect <<EOF +Leading blank lines + +Repeated blank lines + +Trailing spaces + +Trailing blank lines +EOF +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'extra blanks in the message for a signed tag should be removed' ' + git-tag -s -F sigblanksfile blanks-signed-tag && + get_tag_msg blanks-signed-tag >actual && + git diff expect actual && + git-tag -v blanks-signed-tag +' + +get_tag_header blank-signed-tag $commit commit $time >expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with a blank -m message should succeed' ' + git-tag -s -m " " blank-signed-tag && + get_tag_msg blank-signed-tag >actual && + git diff expect actual && + git-tag -v blank-signed-tag +' + +echo ' ' >sigblankfile +echo '' >>sigblankfile +echo ' ' >>sigblankfile +get_tag_header blankfile-signed-tag $commit commit $time >expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with blank -F file with spaces should succeed' ' + git-tag -s -F sigblankfile blankfile-signed-tag && + get_tag_msg blankfile-signed-tag >actual && + git diff expect actual && + git-tag -v blankfile-signed-tag +' + +printf ' ' >sigblanknonlfile +get_tag_header blanknonlfile-signed-tag $commit commit $time >expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with spaces and no newline should succeed' ' + git-tag -s -F sigblanknonlfile blanknonlfile-signed-tag && + get_tag_msg blanknonlfile-signed-tag >actual && + git diff expect actual && + git-tag -v signed-tag +' + +# messages with commented lines for signed tags: + +cat >sigcommentsfile <<EOF +# A comment + +############ +The message. +############ +One line. + + +# commented lines +# commented lines + +Another line. +# comments + +Last line. +EOF +get_tag_header comments-signed-tag $commit commit $time >expect +cat >>expect <<EOF +The message. +One line. + +Another line. + +Last line. +EOF +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with a -F file with #comments should succeed' ' + git-tag -s -F sigcommentsfile comments-signed-tag && + get_tag_msg comments-signed-tag >actual && + git diff expect actual && + git-tag -v comments-signed-tag +' + +get_tag_header comment-signed-tag $commit commit $time >expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with #commented -m message should succeed' ' + git-tag -s -m "#comment" comment-signed-tag && + get_tag_msg comment-signed-tag >actual && + git diff expect actual && + git-tag -v comment-signed-tag +' + +echo '#comment' >sigcommentfile +echo '' >>sigcommentfile +echo '####' >>sigcommentfile +get_tag_header commentfile-signed-tag $commit commit $time >expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with #commented -F messagefile should succeed' ' + git-tag -s -F sigcommentfile commentfile-signed-tag && + get_tag_msg commentfile-signed-tag >actual && + git diff expect actual && + git-tag -v commentfile-signed-tag +' + +printf '#comment' >sigcommentnonlfile +get_tag_header commentnonlfile-signed-tag $commit commit $time >expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with a #comment and no newline should succeed' ' + git-tag -s -F sigcommentnonlfile commentnonlfile-signed-tag && + get_tag_msg commentnonlfile-signed-tag >actual && + git diff expect actual && + git-tag -v commentnonlfile-signed-tag +' + +# tags pointing to objects different from commits: + +tree=$(git rev-parse HEAD^{tree}) +blob=$(git rev-parse HEAD:foo) +tag=$(git rev-parse signed-tag) + +get_tag_header tree-signed-tag $tree tree $time >expect +echo "A message for a tree" >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag pointing to a tree should succeed' ' + git-tag -s -m "A message for a tree" tree-signed-tag HEAD^{tree} && + get_tag_msg tree-signed-tag >actual && + git diff expect actual +' + +get_tag_header blob-signed-tag $blob blob $time >expect +echo "A message for a blob" >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag pointing to a blob should succeed' ' + git-tag -s -m "A message for a blob" blob-signed-tag HEAD:foo && + get_tag_msg blob-signed-tag >actual && + git diff expect actual +' + +get_tag_header tag-signed-tag $tag tag $time >expect +echo "A message for another tag" >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag pointing to another tag should succeed' ' + git-tag -s -m "A message for another tag" tag-signed-tag signed-tag && + get_tag_msg tag-signed-tag >actual && + git diff expect actual +' + +# try to verify without gpg: + +rm -rf gpghome +test_expect_failure \ + 'verify signed tag fails when public key is not present' \ + 'git-tag -v signed-tag' + +test_done diff --git a/t/t7004/pubring.gpg b/t/t7004/pubring.gpg Binary files differnew file mode 100644 index 0000000000..83855fa4e1 --- /dev/null +++ b/t/t7004/pubring.gpg diff --git a/t/t7004/random_seed b/t/t7004/random_seed Binary files differnew file mode 100644 index 0000000000..8fed1339ed --- /dev/null +++ b/t/t7004/random_seed diff --git a/t/t7004/secring.gpg b/t/t7004/secring.gpg Binary files differnew file mode 100644 index 0000000000..d831cd9eb3 --- /dev/null +++ b/t/t7004/secring.gpg diff --git a/t/t7004/trustdb.gpg b/t/t7004/trustdb.gpg Binary files differnew file mode 100644 index 0000000000..abace962b8 --- /dev/null +++ b/t/t7004/trustdb.gpg diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh new file mode 100755 index 0000000000..d6ca955081 --- /dev/null +++ b/t/t9114-git-svn-dcommit-merge.sh @@ -0,0 +1,89 @@ +#!/bin/sh +# +# Copyright (c) 2007 Eric Wong +# Based on a script by Joakim Tjernlund <joakim.tjernlund@transmode.se> + +test_description='git-svn dcommit handles merges' + +. ./lib-git-svn.sh + +big_text_block () { +cat << EOF +# +# (C) Copyright 2000 - 2005 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. +# +# See file CREDITS for list of people who contributed to this +# project. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, +# MA 02111-1307 USA +# +EOF +} + +test_expect_success 'setup svn repository' " + svn co $svnrepo mysvnwork && + mkdir -p mysvnwork/trunk && + cd mysvnwork && + big_text_block >> trunk/README && + svn add trunk && + svn ci -m 'first commit' trunk && + cd .. + " + +test_expect_success 'setup git mirror and merge' " + git svn init $svnrepo -t tags -T trunk -b branches && + git svn fetch && + git checkout --track -b svn remotes/trunk && + git checkout -b merge && + echo new file > new_file && + git add new_file && + git commit -a -m 'New file' && + echo hello >> README && + git commit -a -m 'hello' && + echo add some stuff >> new_file && + git commit -a -m 'add some stuff' && + git checkout svn && + mv -f README tmp && + echo friend > README && + cat tmp >> README && + git commit -a -m 'friend' && + git pull . merge + " + +test_debug 'gitk --all & sleep 1' + +test_expect_success 'verify pre-merge ancestry' " + test x\`git rev-parse --verify refs/heads/svn^2\` = \ + x\`git rev-parse --verify refs/heads/merge\` && + git cat-file commit refs/heads/svn^ | grep '^friend$' + " + +test_expect_success 'git svn dcommit merges' " + git svn dcommit + " + +test_debug 'gitk --all & sleep 1' + +test_expect_success 'verify post-merge ancestry' " + test x\`git rev-parse --verify refs/heads/svn\` = \ + x\`git rev-parse --verify refs/remotes/trunk \` && + test x\`git rev-parse --verify refs/heads/svn^2\` = \ + x\`git rev-parse --verify refs/heads/merge\` && + git cat-file commit refs/heads/svn^ | grep '^friend$' + " + +test_done diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 0331770686..641303e0a1 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -38,7 +38,7 @@ echo >empty && git commit -q -m "First Commit" && git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && - GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" || + GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" || exit 1 # note that cvs doesn't accept absolute pathnames @@ -255,7 +255,7 @@ rm -fr "$SERVERDIR" cd "$WORKDIR" && git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && -GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" || +GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" || exit 1 test_expect_success 'cvs update (create new file)' \ diff --git a/t/test-lib.sh b/t/test-lib.sh index 8bf4cf49a2..78d7e87e86 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -26,6 +26,7 @@ GIT_COMMITTER_EMAIL=committer@example.com GIT_COMMITTER_NAME='C O Mitter' unset GIT_DIFF_OPTS unset GIT_DIR +unset GIT_WORK_TREE unset GIT_EXTERNAL_DIFF unset GIT_INDEX_FILE unset GIT_OBJECT_DIRECTORY diff --git a/write_or_die.c b/write_or_die.c index 5c4bc8515a..e125e11d3b 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -1,5 +1,45 @@ #include "cache.h" +/* + * Some cases use stdio, but want to flush after the write + * to get error handling (and to get better interactive + * behaviour - not buffering excessively). + * + * Of course, if the flush happened within the write itself, + * we've already lost the error code, and cannot report it any + * more. So we just ignore that case instead (and hope we get + * the right error code on the flush). + * + * If the file handle is stdout, and stdout is a file, then skip the + * flush entirely since it's not needed. + */ +void maybe_flush_or_die(FILE *f, const char *desc) +{ + static int skip_stdout_flush = -1; + struct stat st; + char *cp; + + if (f == stdout) { + if (skip_stdout_flush < 0) { + cp = getenv("GIT_FLUSH"); + if (cp) + skip_stdout_flush = (atoi(cp) == 0); + else if ((fstat(fileno(stdout), &st) == 0) && + S_ISREG(st.st_mode)) + skip_stdout_flush = 1; + else + skip_stdout_flush = 0; + } + if (skip_stdout_flush && !ferror(f)) + return; + } + if (fflush(f)) { + if (errno == EPIPE) + exit(0); + die("write failure on %s: %s", desc, strerror(errno)); + } +} + int read_in_full(int fd, void *buf, size_t count) { char *p = buf; |