diff options
54 files changed, 1909 insertions, 555 deletions
diff --git a/.gitignore b/.gitignore index 7f2cd5508..d706dd92c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ git-mailsplit git-merge git-merge-base git-merge-index +git-merge-file git-merge-tree git-merge-octopus git-merge-one-file diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl index ad03755d8..6a361a213 100644 --- a/Documentation/callouts.xsl +++ b/Documentation/callouts.xsl @@ -13,4 +13,18 @@ <xsl:apply-templates/> <xsl:text>.br </xsl:text> </xsl:template> + +<!-- sorry, this is not about callouts, but attempts to work around + spurious .sp at the tail of the line docbook stylesheets seem to add --> +<xsl:template match="simpara"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:if test="not(ancestor::authorblurb) and + not(ancestor::personblurb)"> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + </xsl:stylesheet> diff --git a/Documentation/config.txt b/Documentation/config.txt index f5a552ee8..a3587f839 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -137,10 +137,6 @@ branch.<name>.merge:: this option, `git pull` defaults to merge the first refspec fetched. Specify multiple values to get an octopus merge. -color.pager:: - A boolean to enable/disable colored output when the pager is in - use (default is true). - color.diff:: When true (or `always`), always use colors in patch. When false (or `never`), never. When set to `auto`, use @@ -157,6 +153,24 @@ color.diff.<slot>:: `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, or `white`. +color.pager:: + A boolean to enable/disable colored output when the pager is in + use (default is true). + +color.status:: + A boolean to enable/disable color in the output of + gitlink:git-status[1]. May be set to `true` (or `always`), + `false` (or `never`) or `auto`, in which case colors are used + only when the output is to a terminal. Defaults to false. + +color.status.<slot>:: + Use customized color for status colorization. `<slot>` is + one of `header` (the header text of the status message), + `updated` (files which are updated but not committed), + `changed` (files which are changed but not updated in the index), + or `untracked` (files which are not tracked by git). The values of + these variables may be specified as in color.diff.<slot>. + diff.renameLimit:: The number of files to consider when performing the copy/rename detection; equivalent to the git diff option '-l'. @@ -271,20 +285,6 @@ showbranch.default:: The default set of branches for gitlink:git-show-branch[1]. See gitlink:git-show-branch[1]. -color.status:: - A boolean to enable/disable color in the output of - gitlink:git-status[1]. May be set to `true` (or `always`), - `false` (or `never`) or `auto`, in which case colors are used - only when the output is to a terminal. Defaults to false. - -color.status.<slot>:: - Use customized color for status colorization. `<slot>` is - one of `header` (the header text of the status message), - `updated` (files which are updated but not committed), - `changed` (files which are changed but not updated in the index), - or `untracked` (files which are not tracked by git). The values of - these variables may be specified as in color.diff.<slot>. - tar.umask:: By default, gitlink:git-tar-tree[1] sets file and directories modes to 0666 or 0777. While this is both useful and acceptable for projects diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 6342ea33e..d86c0e7f1 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -3,7 +3,7 @@ git-add(1) NAME ---- -git-add - Add files to the index file +git-add - Add file contents to the changeset to be committed next SYNOPSIS -------- @@ -11,16 +11,31 @@ SYNOPSIS DESCRIPTION ----------- -A simple wrapper for git-update-index to add files to the index, -for people used to do "cvs add". +All the changed file contents to be committed together in a single set +of changes must be "added" with the 'add' command before using the +'commit' command. This is not only for adding new files. Even modified +files must be added to the set of changes about to be committed. -It only adds non-ignored files, to add ignored files use +This command can be performed multiple times before a commit. The added +content corresponds to the state of specified file(s) at the time the +'add' command is used. This means the 'commit' command will not consider +subsequent changes to already added content if it is not added again before +the commit. + +The 'git status' command can be used to obtain a summary of what is included +for the next commit. + +This command only adds non-ignored files, to add ignored files use "git update-index --add". +Please see gitlink:git-commit[1] for alternative ways to add content to a +commit. + + OPTIONS ------- <file>...:: - Files to add to the index (see gitlink:git-ls-files[1]). + Files to add content from. -n:: Don't actually add the file(s), just show if they exist. @@ -34,27 +49,12 @@ OPTIONS for command-line options). -DISCUSSION ----------- - -The list of <file> given to the command is fed to `git-ls-files` -command to list files that are not registered in the index and -are not ignored/excluded by `$GIT_DIR/info/exclude` file or -`.gitignore` file in each directory. This means two things: - -. You can put the name of a directory on the command line, and - the command will add all files in it and its subdirectories; - -. Giving the name of a file that is already in index does not - run `git-update-index` on that path. - - EXAMPLES -------- git-add Documentation/\\*.txt:: - Adds all `\*.txt` files that are not in the index under - `Documentation` directory and its subdirectories. + Adds content from all `\*.txt` files under `Documentation` + directory and its subdirectories. + Note that the asterisk `\*` is quoted from the shell in this example; this lets the command to include the files from @@ -62,15 +62,18 @@ subdirectories of `Documentation/` directory. git-add git-*.sh:: - Adds all git-*.sh scripts that are not in the index. + Considers adding content from all git-*.sh scripts. Because this example lets shell expand the asterisk (i.e. you are listing the files explicitly), it does not - add `subdir/git-foo.sh` to the index. + consider `subdir/git-foo.sh`. See Also -------- +gitlink:git-status[1] gitlink:git-rm[1] -gitlink:git-ls-files[1] +gitlink:git-mv[1] +gitlink:git-commit[1] +gitlink:git-update-index[1] Author ------ diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 4f5b5d502..71417feba 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,8 +8,9 @@ git-branch - List, create, or delete branches. SYNOPSIS -------- [verse] -'git-branch' [-r] [-a] [-v] [--abbrev=<length>] +'git-branch' [-r | -a] [-v [--abbrev=<length>]] 'git-branch' [-l] [-f] <branchname> [<start-point>] +'git-branch' (-m | -M) [<oldbranch>] <newbranch> 'git-branch' (-d | -D) <branchname>... DESCRIPTION @@ -24,6 +25,12 @@ It will start out with a head equal to the one given as <start-point>. If no <start-point> is given, the branch will be created with a head equal to that of the currently checked out branch. +With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>. +If <oldbranch> had a corresponding reflog, it is renamed to match +<newbranch>, and a reflog entry is created to remember the branch +renaming. If <newbranch> exists, -M must be used to force the rename +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. @@ -46,6 +53,12 @@ OPTIONS Force the creation of a new branch even if it means deleting a branch that already exists with the same name. +-m:: + Move/rename a branch and the corresponding reflog. + +-M:: + Move/rename a branch even if the new branchname already exists. + -r:: List the remote-tracking branches. @@ -53,7 +66,7 @@ OPTIONS List both remote-tracking branches and local branches. -v:: - Show sha1 and subject message for each head. + Show sha1 and commit subjectline for each head. --abbrev=<length>:: Alter minimum display length for sha1 in output listing, @@ -70,6 +83,12 @@ OPTIONS be given as a branch name, a commit-id, or a tag. If this option is omitted, the current branch is assumed. +<oldbranch>:: + The name of an existing branch to rename. + +<newbranch>:: + The new name for an existing branch. The same restrictions as for + <branchname> applies. Examples diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 517a86b23..97d66ef4d 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -14,25 +14,41 @@ SYNOPSIS DESCRIPTION ----------- -Updates the index file for given paths, or all modified files if -'-a' is specified, and makes a commit object. The command specified -by either the VISUAL or EDITOR environment variables are used to edit -the commit log message. +Use 'git commit' when you want to record your changes into the repository +along with a log message describing what the commit is about. All changes +to be committed must be explicitly identified using one of the following +methods: -Several environment variable are used during commits. They are -documented in gitlink:git-commit-tree[1]. +1. by using gitlink:git-add[1] to incrementally "add" changes to the + next commit before using the 'commit' command (Note: even modified + files must be "added"); +2. by using gitlink:git-rm[1] to identify content removal for the next + commit, again before using the 'commit' command; + +3. by directly listing files containing changes to be committed as arguments + to the 'commit' command, in which cases only those files alone will be + considered for the commit; + +4. by using the -a switch with the 'commit' command to automatically "add" + changes from all known files i.e. files that have already been committed + before, and perform the actual commit. + +The gitlink:git-status[1] command can be used to obtain a +summary of what is included by any of the above for the next +commit by giving the same set of parameters you would give to +this command. + +If you make a commit and then found a mistake immediately after +that, you can recover from it with gitlink:git-reset[1]. -This command can run `commit-msg`, `pre-commit`, and -`post-commit` hooks. See link:hooks.html[hooks] for more -information. OPTIONS ------- -a|--all:: - Update all paths in the index file. This flag notices - files that have been modified and deleted, but new files - you have not told git about are not affected. + Tell the command to automatically stage files that have + been modified and deleted, but new files you have not + told git about are not affected. -c or -C <commit>:: Take existing commit object, and reuse the log message @@ -55,16 +71,13 @@ OPTIONS -s|--signoff:: Add Signed-off-by line at the end of the commit message. --v|--verify:: - Look for suspicious lines the commit introduces, and - abort committing if there is one. The definition of - 'suspicious lines' is currently the lines that has - trailing whitespaces, and the lines whose indentation - has a SP character immediately followed by a TAB - character. This is the default. - --n|--no-verify:: - The opposite of `--verify`. +--no-verify:: + By default, the command looks for suspicious lines the + commit introduces, and aborts committing if there is one. + The definition of 'suspicious lines' is currently the + lines that has trailing whitespaces, and the lines whose + indentation has a SP character immediately followed by a + TAB character. This option turns off the check. -e|--edit:: The message taken from file with `-F`, command line with @@ -95,69 +108,137 @@ but can be used to amend a merge commit. -- -i|--include:: - Instead of committing only the files specified on the - command line, update them in the index file and then - commit the whole index. This is the traditional - behavior. - --o|--only:: - Commit only the files specified on the command line. - This format cannot be used during a merge, nor when the - index and the latest commit does not match on the - specified paths to avoid confusion. + Before making a commit out of staged contents so far, + stage the contents of paths given on the command line + as well. This is usually not what you want unless you + are concluding a conflicted merge. \--:: Do not interpret any more arguments as options. <file>...:: - Files to be committed. The meaning of these is - different between `--include` and `--only`. Without - either, it defaults `--only` semantics. - -If you make a commit and then found a mistake immediately after -that, you can recover from it with gitlink:git-reset[1]. + When files are given on the command line, the command + commits the contents of the named files, without + recording the changes already staged. The contents of + these files are also staged for the next commit on top + of what have been staged before. -Discussion ----------- - -`git commit` without _any_ parameter commits the tree structure -recorded by the current index file. This is a whole-tree commit -even the command is invoked from a subdirectory. - -`git commit --include paths...` is equivalent to - - git update-index --remove paths... - git commit - -That is, update the specified paths to the index and then commit -the whole tree. - -`git commit paths...` largely bypasses the index file and -commits only the changes made to the specified paths. It has -however several safety valves to prevent confusion. - -. It refuses to run during a merge (i.e. when - `$GIT_DIR/MERGE_HEAD` exists), and reminds trained git users - that the traditional semantics now needs -i flag. - -. It refuses to run if named `paths...` are different in HEAD - and the index (ditto about reminding). Added paths are OK. - This is because an earlier `git diff` (not `git diff HEAD`) - would have shown the differences since the last `git - update-index paths...` to the user, and an inexperienced user - may mistakenly think that the changes between the index and - the HEAD (i.e. earlier changes made before the last `git - update-index paths...` was done) are not being committed. - -. It reads HEAD commit into a temporary index file, updates the - specified `paths...` and makes a commit. At the same time, - the real index file is also updated with the same `paths...`. +EXAMPLES +-------- +When recording your own work, the contents of modified files in +your working tree are temporarily stored to a staging area +called the "index" with gitlink:git-add[1]. Removal +of a file is staged with gitlink:git-rm[1]. After building the +state to be committed incrementally with these commands, `git +commit` (without any pathname parameter) is used to record what +has been staged so far. This is the most basic form of the +command. An example: + +------------ +$ edit hello.c +$ git rm goodbye.c +$ git add hello.c +$ git commit +------------ + +//////////// +We should fix 'git rm' to remove goodbye.c from both index and +working tree for the above example. +//////////// + +Instead of staging files after each individual change, you can +tell `git commit` to notice the changes to the files whose +contents are tracked in +your working tree and do corresponding `git add` and `git rm` +for you. That is, this example does the same as the earlier +example if there is no other change in your working tree: + +------------ +$ edit hello.c +$ rm goodbye.c +$ git commit -a +------------ + +The command `git commit -a` first looks at your working tree, +notices that you have modified hello.c and removed goodbye.c, +and performs necessary `git add` and `git rm` for you. + +After staging changes to many files, you can alter the order the +changes are recorded in, by giving pathnames to `git commit`. +When pathnames are given, the command makes a commit that +only records the changes made to the named paths: + +------------ +$ edit hello.c hello.h +$ git add hello.c hello.h +$ edit Makefile +$ git commit Makefile +------------ + +This makes a commit that records the modification to `Makefile`. +The changes staged for `hello.c` and `hello.h` are not included +in the resulting commit. However, their changes are not lost -- +they are still staged and merely held back. After the above +sequence, if you do: + +------------ +$ git commit +------------ + +this second commit would record the changes to `hello.c` and +`hello.h` as expected. + +After a merge (initiated by either gitlink:git-merge[1] or +gitlink:git-pull[1]) stops because of conflicts, cleanly merged +paths are already staged to be committed for you, and paths that +conflicted are left in unmerged state. You would have to first +check which paths are conflicting with gitlink:git-status[1] +and after fixing them manually in your working tree, you would +stage the result as usual with gitlink:git-add[1]: + +------------ +$ git status | grep unmerged +unmerged: hello.c +$ edit hello.c +$ git add hello.c +------------ + +After resolving conflicts and staging the result, `git ls-files -u` +would stop mentioning the conflicted path. When you are done, +run `git commit` to finally record the merge: + +------------ +$ git commit +------------ + +As with the case to record your own changes, you can use `-a` +option to save typing. One difference is that during a merge +resolution, you cannot use `git commit` with pathnames to +alter the order the changes are committed, because the merge +should be recorded as a single commit. In fact, the command +refuses to run when given pathnames (but see `-i` option). + + +ENVIRONMENT VARIABLES +--------------------- +The command specified by either the VISUAL or EDITOR environment +variables is used to edit the commit log message. + +HOOKS +----- +This command can run `commit-msg`, `pre-commit`, and +`post-commit` hooks. See link:hooks.html[hooks] for more +information. -`git commit --all` updates the index file with _all_ changes to -the working tree, and makes a whole-tree commit, regardless of -which subdirectory the command is invoked in. +SEE ALSO +-------- +gitlink:git-add[1], +gitlink:git-rm[1], +gitlink:git-mv[1], +gitlink:git-merge[1], +gitlink:git-commit-tree[1] Author ------ diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 6cd060108..0cf505ea8 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -40,8 +40,8 @@ If "git-merge-index" is called with multiple <file>s (or -a) then it processes them in turn only stopping if merge returns a non-zero exit code. -Typically this is run with the a script calling the merge command from -the RCS package. +Typically this is run with the a script calling git's imitation of +the merge command from the RCS package. A sample script called "git-merge-one-file" is included in the distribution. diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 11bd9c0ad..0ff2890c7 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index SYNOPSIS -------- -'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) +'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) DESCRIPTION @@ -71,6 +71,20 @@ OPTIONS directory. Note that the `<prefix>/` value must end with a slash. +--exclude-per-directory=<gitignore>:: + When running the command with `-u` and `-m` options, the + merge result may need to overwrite paths that are not + tracked in the current branch. The command usually + refuses to proceed with the merge to avoid losing such a + path. However this safety valve sometimes gets in the + way. For example, it often happens that the other + branch added a file that used to be a generated file in + your branch, and the safety valve triggers when you try + to switch to that branch after you ran `make` but before + running `make clean` to remove the generated file. This + option tells the command to read per-directory exclude + file (usually '.gitignore') and allows such an untracked + but explicitly ignored file to be overwritten. <tree-ish#>:: The id of the tree object(s) to be read/merged. diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index 8b6b65123..116dca4c0 100644 --- a/Documentation/git-rerere.txt +++ b/Documentation/git-rerere.txt @@ -7,8 +7,7 @@ git-rerere - Reuse recorded resolve SYNOPSIS -------- -'git-rerere' - +'git-rerere' [clear|diff|status] DESCRIPTION ----------- @@ -27,6 +26,38 @@ results and applying the previously recorded hand resolution. You need to create `$GIT_DIR/rr-cache` directory to enable this command. + +COMMANDS +-------- + +Normally, git-rerere is run without arguments or user-intervention. +However, it has several commands that allow it to interact with +its working state. + +'clear':: + +This resets the metadata used by rerere if a merge resolution is to be +is aborted. Calling gitlink:git-am[1] --skip or gitlink:git-rebase[1] +[--skip|--abort] will automatcally invoke this command. + +'diff':: + +This displays diffs for the current state of the resolution. It is +useful for tracking what has changed while the user is resolving +conflicts. Additional arguments are passed directly to the system +diff(1) command installed in PATH. + +'status':: + +Like diff, but this only prints the filenames that will be tracked +for resolutions. + +'gc':: + +This command is used to prune records of conflicted merge that +occurred long time ago. + + DISCUSSION ---------- diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 42b6e7d7d..6389de5ef 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -23,6 +23,7 @@ $ echo 'hello world' > file.txt $ git add . $ git commit -a -m "initial commit" Committing initial tree 92b8b694ffb1675e5975148e1121810081dbdffe + create mode 100644 file.txt $ echo 'hello world!' >file.txt $ git commit -a -m "add emphasis" ------------------------------------------------ diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index fe4491de4..02dede320 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -87,14 +87,48 @@ thorough description. Tools that turn commits into email, for example, use the first line on the Subject line and the rest of the commit in the body. -To add a new file, first create the file, then ------------------------------------------------- -$ git add path/to/new/file ------------------------------------------------- +Git tracks content not files +---------------------------- + +With git you have to explicitly "add" all the changed _content_ you +want to commit together. This can be done in a few different ways: + +1) By using 'git add <file_spec>...' + + This can be performed multiple times before a commit. Note that this + is not only for adding new files. Even modified files must be + added to the set of changes about to be committed. The "git status" + command gives you a summary of what is included so far for the + next commit. When done you should use the 'git commit' command to + make it real. + + Note: don't forget to 'add' a file again if you modified it after the + first 'add' and before 'commit'. Otherwise only the previous added + state of that file will be committed. This is because git tracks + content, so what you're really 'add'ing to the commit is the *content* + of the file in the state it is in when you 'add' it. + +2) By using 'git commit -a' directly + + This is a quick way to automatically 'add' the content from all files + that were modified since the previous commit, and perform the actual + commit without having to separately 'add' them beforehand. This will + not add content from new files i.e. files that were never added before. + Those files still have to be added explicitly before performing a + commit. + +But here's a twist. If you do 'git commit <file1> <file2> ...' then only +the changes belonging to those explicitly specified files will be +committed, entirely bypassing the current "added" changes. Those "added" +changes will still remain available for a subsequent commit though. + +However, for normal usage you only have to remember 'git add' + 'git commit' +and/or 'git commit -a'. + -then commit as usual. No special command is required when removing a -file; just remove it, then tell `commit` about the file as usual. +Viewing the changelog +--------------------- At any point you can view the history of your changes using @@ -82,15 +82,6 @@ Issues of note: do that even if it wasn't for git. There's no point in living in the dark ages any more. - - "merge", the standard UNIX three-way merge program. It usually - comes with the "rcs" package on most Linux distributions, so if - you have a developer install you probably have it already, but a - "graphical user desktop" install might have left it out. - - You'll only need the merge program if you do development using - git, and if you only use git to track other peoples work you'll - never notice the lack of it. - - "wish", the Tcl/Tk windowing shell is used in gitk to show the history graphically @@ -279,6 +279,7 @@ BUILTIN_OBJS = \ builtin-ls-tree.o \ builtin-mailinfo.o \ builtin-mailsplit.o \ + builtin-merge-file.o \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ @@ -737,7 +738,8 @@ $(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) rm -f $@ && $(AR) rcs $@ $(LIB_OBJS) -XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o +XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ + xdiff/xmerge.o $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h diff --git a/builtin-add.c b/builtin-add.c index febb75ed9..b3f920676 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -94,9 +94,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); - if (read_cache() < 0) - die("index file corrupt"); - for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -131,6 +128,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) return 0; } + if (read_cache() < 0) + die("index file corrupt"); + for (i = 0; i < dir.nr; i++) add_file_to_index(dir.entries[i]->name, verbose); diff --git a/builtin-branch.c b/builtin-branch.c index 3d5cb0e4b..560309cb1 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -5,18 +5,71 @@ * Based on git-branch.sh by Junio C Hamano. */ +#include "color.h" #include "cache.h" #include "refs.h" #include "commit.h" #include "builtin.h" static const char builtin_branch_usage[] = -"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r | -a] [-v] [--abbrev=<length>] "; + "git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [-r | -a] [-v [--abbrev=<length>]]"; static const char *head; static unsigned char head_sha1[20]; +static int branch_use_color; +static char branch_colors[][COLOR_MAXLEN] = { + "\033[m", /* reset */ + "", /* PLAIN (normal) */ + "\033[31m", /* REMOTE (red) */ + "", /* LOCAL (normal) */ + "\033[32m", /* CURRENT (green) */ +}; +enum color_branch { + COLOR_BRANCH_RESET = 0, + COLOR_BRANCH_PLAIN = 1, + COLOR_BRANCH_REMOTE = 2, + COLOR_BRANCH_LOCAL = 3, + COLOR_BRANCH_CURRENT = 4, +}; + +static int parse_branch_color_slot(const char *var, int ofs) +{ + if (!strcasecmp(var+ofs, "plain")) + return COLOR_BRANCH_PLAIN; + if (!strcasecmp(var+ofs, "reset")) + return COLOR_BRANCH_RESET; + if (!strcasecmp(var+ofs, "remote")) + return COLOR_BRANCH_REMOTE; + if (!strcasecmp(var+ofs, "local")) + return COLOR_BRANCH_LOCAL; + if (!strcasecmp(var+ofs, "current")) + return COLOR_BRANCH_CURRENT; + die("bad config variable '%s'", var); +} + +int git_branch_config(const char *var, const char *value) +{ + if (!strcmp(var, "color.branch")) { + branch_use_color = git_config_colorbool(var, value); + return 0; + } + if (!strncmp(var, "color.branch.", 13)) { + int slot = parse_branch_color_slot(var, 13); + color_parse(value, var, branch_colors[slot]); + return 0; + } + return git_default_config(var, value); +} + +const char *branch_get_color(enum color_branch ix) +{ + if (branch_use_color) + return branch_colors[ix]; + return ""; +} + static int in_merge_bases(const unsigned char *sha1, struct commit *rev1, struct commit *rev2) @@ -183,6 +236,7 @@ static void print_ref_list(int kinds, int verbose, int abbrev) int i; char c; struct ref_list ref_list; + int color; memset(&ref_list, 0, sizeof(ref_list)); ref_list.kinds = kinds; @@ -191,18 +245,38 @@ static void print_ref_list(int kinds, int verbose, int abbrev) qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); for (i = 0; i < ref_list.index; i++) { + switch( ref_list.list[i].kind ) { + case REF_LOCAL_BRANCH: + color = COLOR_BRANCH_LOCAL; + break; + case REF_REMOTE_BRANCH: + color = COLOR_BRANCH_REMOTE; + break; + default: + color = COLOR_BRANCH_PLAIN; + break; + } + c = ' '; if (ref_list.list[i].kind == REF_LOCAL_BRANCH && - !strcmp(ref_list.list[i].name, head)) + !strcmp(ref_list.list[i].name, head)) { c = '*'; + color = COLOR_BRANCH_CURRENT; + } if (verbose) { - printf("%c %-*s", c, ref_list.maxwidth, - ref_list.list[i].name); + printf("%c %s%-*s%s", c, + branch_get_color(color), + ref_list.maxwidth, + ref_list.list[i].name, + branch_get_color(COLOR_BRANCH_RESET)); print_ref_info(ref_list.list[i].sha1, abbrev); } else - printf("%c %s\n", c, ref_list.list[i].name); + printf("%c %s%s%s\n", c, + branch_get_color(color), + ref_list.list[i].name, + branch_get_color(COLOR_BRANCH_RESET)); } free_ref_list(&ref_list); @@ -245,15 +319,47 @@ static void create_branch(const char *name, const char *start, die("Failed to write ref: %s.", strerror(errno)); } +static void rename_branch(const char *oldname, const char *newname, int force) +{ + char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100]; + unsigned char sha1[20]; + + if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref)) + die("Old branchname too long"); + + if (check_ref_format(oldref)) + die("Invalid branch name: %s", oldref); + + if (snprintf(newref, sizeof(newref), "refs/heads/%s", newname) > sizeof(newref)) + die("New branchname too long"); + + if (check_ref_format(newref)) + die("Invalid branch name: %s", newref); + + if (resolve_ref(newref, sha1, 1, NULL) && !force) + die("A branch named '%s' already exists.", newname); + + snprintf(logmsg, sizeof(logmsg), "Branch: renamed %s to %s", + oldref, newref); + + if (rename_ref(oldref, newref, logmsg)) + die("Branch rename failed"); + + if (!strcmp(oldname, head) && create_symref("HEAD", newref)) + die("Branch renamed to %s, but HEAD is not updated!", newname); +} + int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, force_delete = 0, force_create = 0; + int rename = 0, force_rename = 0; int verbose = 0, abbrev = DEFAULT_ABBREV; int reflog = 0; int kinds = REF_LOCAL_BRANCH; int i; - git_config(git_default_config); + setup_ident(); + git_config(git_branch_config); for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -277,6 +383,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) force_create = 1; continue; } + if (!strcmp(arg, "-m")) { + rename = 1; + continue; + } + if (!strcmp(arg, "-M")) { + rename = 1; + force_rename = 1; + continue; + } if (!strcmp(arg, "-r")) { kinds = REF_REMOTE_BRANCH; continue; @@ -297,9 +412,21 @@ int cmd_branch(int argc, const char **argv, const char *prefix) verbose = 1; continue; } + if (!strcmp(arg, "--color")) { + branch_use_color = 1; + continue; + } + if (!strcmp(arg, "--no-color")) { + branch_use_color = 0; + continue; + } usage(builtin_branch_usage); } + if ((delete && rename) || (delete && force_create) || + (rename && force_create)) + usage(builtin_branch_usage); + head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL)); if (!head) die("Failed to resolve HEAD as a valid ref."); @@ -311,6 +438,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) delete_branches(argc - i, argv + i, force_delete); else if (i == argc) print_ref_list(kinds, verbose, abbrev); + else if (rename && (i == argc - 1)) + rename_branch(head, argv[i], force_rename); + else if (rename && (i == argc - 2)) + rename_branch(argv[i], argv[i + 1], force_rename); else if (i == argc - 1) create_branch(argv[i], head, force_create, reflog); else if (i == argc - 2) diff --git a/builtin-merge-file.c b/builtin-merge-file.c new file mode 100644 index 000000000..6c4c3a351 --- /dev/null +++ b/builtin-merge-file.c @@ -0,0 +1,79 @@ +#include "cache.h" +#include "xdiff/xdiff.h" + +static const char merge_file_usage[] = +"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"; + +static int read_file(mmfile_t *ptr, const char *filename) +{ + struct stat st; + FILE *f; + + if (stat(filename, &st)) + return error("Could not stat %s", filename); + if ((f = fopen(filename, "rb")) == NULL) + return error("Could not open %s", filename); + ptr->ptr = xmalloc(st.st_size); + if (fread(ptr->ptr, st.st_size, 1, f) != 1) + return error("Could not read %s", filename); + fclose(f); + ptr->size = st.st_size; + return 0; +} + +int cmd_merge_file(int argc, char **argv, char **envp) +{ + char *names[3]; + mmfile_t mmfs[3]; + mmbuffer_t result = {NULL, 0}; + xpparam_t xpp = {XDF_NEED_MINIMAL}; + int ret = 0, i = 0, to_stdout = 0; + + while (argc > 4) { + if (!strcmp(argv[1], "-L") && i < 3) { + names[i++] = argv[2]; + argc--; + argv++; + } else if (!strcmp(argv[1], "-p") || + !strcmp(argv[1], "--stdout")) + to_stdout = 1; + else if (!strcmp(argv[1], "-q") || + !strcmp(argv[1], "--quiet")) + freopen("/dev/null", "w", stderr); + else + usage(merge_file_usage); + argc--; + argv++; + } + + if (argc != 4) + usage(merge_file_usage); + + for (; i < 3; i++) + names[i] = argv[i + 1]; + + for (i = 0; i < 3; i++) + if (read_file(mmfs + i, argv[i + 1])) + return -1; + + ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], + &xpp, XDL_MERGE_ZEALOUS, &result); + + for (i = 0; i < 3; i++) + free(mmfs[i].ptr); + + if (ret >= 0) { + char *filename = argv[1]; + FILE *f = to_stdout ? stdout : fopen(filename, "wb"); + + if (!f) + ret = error("Could not open %s for writing", filename); + else if (fwrite(result.ptr, result.size, 1, f) != 1) + ret = error("Could not write to %s", filename); + else if (fclose(f)) + ret = error("Could not close %s", filename); + free(result.ptr); + } + + return ret; +} diff --git a/builtin-push.c b/builtin-push.c index d23974e70..b7412e829 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -57,11 +57,36 @@ static void expand_refspecs(void) static void set_refspecs(const char **refs, int nr) { if (nr) { - size_t bytes = nr * sizeof(char *); - - refspec = xrealloc(refspec, bytes); - memcpy(refspec, refs, bytes); - refspec_nr = nr; + int pass; + for (pass = 0; pass < 2; pass++) { + /* pass 0 counts and allocates, pass 1 fills */ + int i, cnt; + for (i = cnt = 0; i < nr; i++) { + if (!strcmp("tag", refs[i])) { + int len; + char *tag; + if (nr <= ++i) + die("tag <tag> shorthand without <tag>"); + if (pass) { + len = strlen(refs[i]) + 11; + tag = xmalloc(len); + strcpy(tag, "refs/tags/"); + strcat(tag, refs[i]); + refspec[cnt] = tag; + } + cnt++; + continue; + } + if (pass) + refspec[cnt] = refs[i]; + cnt++; + } + if (!pass) { + size_t bytes = cnt * sizeof(char *); + refspec_nr = cnt; + refspec = xrealloc(refspec, bytes); + } + } } expand_refspecs(); } diff --git a/builtin-read-tree.c b/builtin-read-tree.c index c1867d2a0..8ba436dba 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -10,6 +10,7 @@ #include "tree-walk.h" #include "cache-tree.h" #include "unpack-trees.h" +#include "dir.h" #include "builtin.h" static struct object_list *trees; @@ -84,7 +85,7 @@ static void prime_cache_tree(void) } -static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])"; +static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <sha1> [<sha2> [<sha3>]])"; static struct lock_file lock_file; @@ -178,6 +179,23 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) continue; } + if (!strncmp(arg, "--exclude-per-directory=", 24)) { + struct dir_struct *dir; + + if (opts.dir) + die("more than one --exclude-per-directory are given."); + + dir = calloc(1, sizeof(*opts.dir)); + dir->show_ignored = 1; + dir->exclude_per_dir = arg + 24; + opts.dir = dir; + /* We do not need to nor want to do read-directory + * here; we are merely interested in reusing the + * per directory ignore stack mechanism. + */ + continue; + } + /* using -u and -i at the same time makes no sense */ if (1 < opts.index_only + opts.update) usage(read_tree_usage); @@ -190,6 +208,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) } if ((opts.update||opts.index_only) && !opts.merge) usage(read_tree_usage); + if ((opts.dir && !opts.update)) + die("--exclude-per-directory is meaningless unless -u"); if (opts.prefix) { int pfxlen = strlen(opts.prefix); @@ -42,6 +42,7 @@ extern int cmd_ls_files(int argc, const char **argv, const char *prefix); extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); +extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); @@ -802,7 +802,10 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; - printf("%d\t%d\t", file->added, file->deleted); + if (file->is_binary) + printf("-\t-\t"); + else + printf("%d\t%d\t", file->added, file->deleted); if (options->line_termination && quote_c_style(file->name, NULL, NULL, 0)) quote_c_style(file->name, NULL, stdout, 0); @@ -156,7 +156,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname) die("cannot use %s as an exclude file", fname); } -static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen) +int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen) { char exclude_file[PATH_MAX]; struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; @@ -170,7 +170,7 @@ static int push_exclude_per_directory(struct dir_struct *dir, const char *base, return current_nr; } -static void pop_exclude_per_directory(struct dir_struct *dir, int stk) +void pop_exclude_per_directory(struct dir_struct *dir, int stk) { struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; @@ -43,6 +43,9 @@ extern int common_prefix(const char **pathspec); extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen); +extern int push_exclude_per_directory(struct dir_struct *, const char *, int); +extern void pop_exclude_per_directory(struct dir_struct *, int); + extern int excluded(struct dir_struct *, const char *); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, @@ -246,6 +246,10 @@ last=`cat "$dotest/last"` this=`cat "$dotest/next"` if test "$skip" = t then + if test -d "$GIT_DIR/rr-cache" + then + git-rerere clear + fi this=`expr "$this" + 1` resume= fi @@ -408,6 +412,10 @@ do stop_here_user_resolve $this fi apply_status=0 + if test -d "$GIT_DIR/rr-cache" + then + git rerere + fi ;; esac diff --git a/git-checkout.sh b/git-checkout.sh index 737abd0c0..4192a99fe 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -161,7 +161,7 @@ then git-read-tree --reset -u $new else git-update-index --refresh >/dev/null - merge_error=$(git-read-tree -m -u $old $new 2>&1) || ( + merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || ( case "$merge" in '') echo >&2 "$merge_error" @@ -172,7 +172,8 @@ else git diff-files --name-only | git update-index --remove --stdin && work=`git write-tree` && git read-tree --reset -u $new && - git read-tree -m -u --aggressive $old $new $work || exit + git read-tree -m -u --aggressive --exclude-per-directory=.gitignore $old $new $work || + exit if result=`git write-tree 2>/dev/null` then diff --git a/git-clone.sh b/git-clone.sh index 0ace989fd..1f5d07a05 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -400,7 +400,9 @@ then rm -f "refs/remotes/$origin/HEAD" git-symbolic-ref "refs/remotes/$origin/HEAD" \ "refs/remotes/$origin/$head_points_at" - esac + esac && + git-repo-config branch."$head_points_at".remote "$origin" && + git-repo-config branch."$head_points_at".merge "refs/heads/$head_points_at" esac case "$no_checkout" in diff --git a/git-commit.sh b/git-commit.sh index 81c3a0cb6..05828bb11 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -350,19 +350,9 @@ t,) refuse_partial "Cannot do a partial commit during a merge." fi TMP_INDEX="$GIT_DIR/tmp-index$$" - if test -z "$initial_commit" - then - # make sure index is clean at the specified paths, or - # they are additions. - dirty_in_index=`git-diff-index --cached --name-status \ - --diff-filter=DMTU HEAD -- "$@"` - test -z "$dirty_in_index" || - refuse_partial "Different in index and the last commit: -$dirty_in_index" - fi commit_only=`git-ls-files --error-unmatch -- "$@"` || exit - # Build the temporary index and update the real index + # Build a temporary index and update the real index # the same way. if test -z "$initial_commit" then @@ -629,4 +619,7 @@ if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0 then "$GIT_DIR"/hooks/post-commit fi + +test "$ret" = 0 && git-diff-tree --summary --root --no-commit-id HEAD + exit "$ret" diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index c9d1d88f2..4863c91fe 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -2,9 +2,8 @@ # Known limitations: # - does not propagate permissions -# - tells "ready for commit" even when things could not be completed -# (not sure this is true anymore, more testing is needed) -# - does not handle whitespace in pathnames at all. +# - error handling has not been extensively tested +# use strict; use Getopt::Std; @@ -115,49 +114,40 @@ if ($opt_a) { } close MSG; -my (@afiles, @dfiles, @mfiles, @dirs); -my %amodes; -my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit); -#print @files; -$? && die "Error in git-diff-tree"; -foreach my $f (@files) { - chomp $f; - my @fields = split(m!\s+!, $f); - if ($fields[4] eq 'A') { - my $path = $fields[5]; - $amodes{$path} = $fields[1]; - push @afiles, $path; - # add any needed parent directories - $path = dirname $path; - while (!-d $path and ! grep { $_ eq $path } @dirs) { - unshift @dirs, $path; - $path = dirname $path; - } - } - if ($fields[4] eq 'M') { - push @mfiles, $fields[5]; - } - if ($fields[4] eq 'D') { - push @dfiles, $fields[5]; - } +`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff"; + +## apply non-binary changes +my $fuzz = $opt_p ? 0 : 2; + +print "Checking if patch will apply\n"; + +my @stat; +open APPLY, "GIT_DIR= git-apply -C$fuzz --binary --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch"; +@stat=<APPLY>; +close APPLY || die "Cannot patch"; +my (@bfiles,@files,@afiles,@dfiles); +chomp @stat; +foreach (@stat) { + push (@bfiles,$1) if m/^-\t-\t(.*)$/; + push (@files, $1) if m/^-\t-\t(.*)$/; + push (@files, $1) if m/^\d+\t\d+\t(.*)$/; + push (@afiles,$1) if m/^ create mode [0-7]+ (.*)$/; + push (@dfiles,$1) if m/^ delete mode [0-7]+ (.*)$/; } -my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles); -@binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit); -map { chomp } @binfiles; -@abfiles = grep s/^Binary files \/dev\/null and b\/(.*) differ$/$1/, @binfiles; -@dbfiles = grep s/^Binary files a\/(.*) and \/dev\/null differ$/$1/, @binfiles; -@mbfiles = grep s/^Binary files a\/(.*) and b\/(.*) differ$/$1/, @binfiles; -push @bfiles, @abfiles; -push @bfiles, @dbfiles; -push @bfiles, @mbfiles; -push @mfiles, @mbfiles; - -$opt_v && print "The commit affects:\n "; -$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n"; -undef @files; # don't need it anymore +map { s/^"(.*)"$/$1/g } @bfiles,@files; +map { s/\\([0-7]{3})/sprintf('%c',oct $1)/eg } @bfiles,@files; # check that the files are clean and up to date according to cvs my $dirty; +my @dirs; +foreach my $p (@afiles) { + my $path = dirname $p; + while (!-d $path and ! grep { $_ eq $path } @dirs) { + unshift @dirs, $path; + $path = dirname $path; + } +} + foreach my $d (@dirs) { if (-e $d) { $dirty = 1; @@ -180,7 +170,8 @@ foreach my $f (@afiles) { } } -foreach my $f (@mfiles, @dfiles) { +foreach my $f (@files) { + next if grep { $_ eq $f } @afiles; # TODO:we need to handle removed in cvs my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); if (@status > 1) { warn 'Strange! cvs status returned more than one line?'}; @@ -197,87 +188,26 @@ if ($dirty) { } } -### -### NOTE: if you are planning to die() past this point -### you MUST call cleanupcvs(@files) before die() -### +print "Applying\n"; +`GIT_DIR= git-apply -C$fuzz --binary --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch"; - -print "Creating new directories\n"; +print "Patch applied successfully. Adding new files and directories to CVS\n"; +my $dirtypatch = 0; foreach my $d (@dirs) { - unless (mkdir $d) { - warn "Could not mkdir $d: $!"; - $dirty = 1; - } - `cvs add $d`; - if ($?) { - $dirty = 1; + if (system('cvs','add',$d)) { + $dirtypatch = 1; warn "Failed to cvs add directory $d -- you may need to do it manually"; } } -print "'Patching' binary files\n"; - -foreach my $f (@bfiles) { - # check that the file in cvs matches the "old" file - # extract the file to $tmpdir and compare with cmp - if (not(grep { $_ eq $f } @afiles)) { - my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}"); - chomp $tree; - my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; - chomp $blob; - `git-cat-file blob $blob > $tmpdir/blob`; - if (system('cmp', '-s', $f, "$tmpdir/blob")) { - warn "Binary file $f in CVS does not match parent.\n"; - if (not $opt_f) { - $dirty = 1; - next; - } - } - } - if (not(grep { $_ eq $f } @dfiles)) { - my $tree = safe_pipe_capture('git-rev-parse', "$commit^{tree}"); - chomp $tree; - my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; - chomp $blob; - # replace with the new file - `git-cat-file blob $blob > $f`; - } - - # TODO: something smart with file modes - -} -if ($dirty) { - cleanupcvs(@files); - die "Exiting: Binary files in CVS do not match parent"; -} - -## apply non-binary changes -my $fuzz = $opt_p ? 0 : 2; - -print "Patching non-binary files\n"; - -if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) { - print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`; -} - -my $dirtypatch = 0; -if (($? >> 8) == 2) { - cleanupcvs(@files); - die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually"; -} elsif (($? >> 8) == 1) { # some hunks failed to apply - $dirtypatch = 1; -} - foreach my $f (@afiles) { - set_new_file_permissions($f, $amodes{$f}); if (grep { $_ eq $f } @bfiles) { system('cvs', 'add','-kb',$f); } else { system('cvs', 'add', $f); } if ($?) { - $dirty = 1; + $dirtypatch = 1; warn "Failed to cvs add $f -- you may need to do it manually"; } } @@ -285,35 +215,40 @@ foreach my $f (@afiles) { foreach my $f (@dfiles) { system('cvs', 'rm', '-f', $f); if ($?) { - $dirty = 1; + $dirtypatch = 1; warn "Failed to cvs rm -f $f -- you may need to do it manually"; } } print "Commit to CVS\n"; -print "Patch: $title\n"; -my $commitfiles = join(' ', @afiles, @mfiles, @dfiles); -my $cmd = "cvs commit -F .msg $commitfiles"; +print "Patch title (first comment line): $title\n"; +my @commitfiles = map { unless (m/\s/) { '\''.$_.'\''; } else { $_; }; } (@files); +my $cmd = "cvs commit -F .msg @commitfiles"; if ($dirtypatch) { print "NOTE: One or more hunks failed to apply cleanly.\n"; - print "Resolve the conflicts and then commit using:\n"; + print "You'll need to apply the patch in .cvsexportcommit.diff manually\n"; + print "using a patch program. After applying the patch and resolving the\n"; + print "problems you may commit using:"; print "\n $cmd\n\n"; exit(1); } - if ($opt_c) { print "Autocommit\n $cmd\n"; - print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @afiles, @mfiles, @dfiles); + print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @files); if ($?) { - cleanupcvs(@files); die "Exiting: The commit did not succeed"; } print "Committed successfully to CVS\n"; } else { print "Ready for you to commit, just run:\n\n $cmd\n"; } + +# clean up +unlink(".cvsexportcommit.diff"); +unlink(".msg"); + sub usage { print STDERR <<END; Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit @@ -321,17 +256,6 @@ END exit(1); } -# ensure cvs is clean before we die -sub cleanupcvs { - my @files = @_; - foreach my $f (@files) { - system('cvs', '-q', 'update', '-C', $f); - if ($?) { - warn "Warning! Failed to cleanup state of $f\n"; - } - } -} - # An alternative to `command` that allows input to be passed as an array # to work around shell problems with weird characters in arguments # if the exec returns non-zero we die @@ -346,12 +270,15 @@ sub safe_pipe_capture { return wantarray ? @output : join('',@output); } -# For any file we want to add to cvs, we must first set its permissions -# properly, *before* the "cvs add ..." command. Otherwise, it is impossible -# to change the permission of the file in the CVS repository using only cvs -# commands. This should be fixed in cvs-1.12.14. -sub set_new_file_permissions { - my ($file, $perm) = @_; - chmod oct($perm), $file - or die "failed to set permissions of \"$file\": $!\n"; +sub safe_pipe_capture_blob { + my $output; + if (my $pid = open my $child, '-|') { + local $/; + undef $/; + $output = (<$child>); + close $child or die join(' ',@_).": $! $?"; + } else { + exec(@_) or die "$! $?"; # exec() can fail the executable can't be found + } + return $output; } diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 197014d9e..2a8447e25 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -946,7 +946,7 @@ sub req_update $log->debug("Temporary directory for merge is $dir"); - my $return = system("merge", $file_local, $file_old, $file_new); + my $return = system("git merge-file", $file_local, $file_old, $file_new); $return >>= 8; if ( $return == 0 ) diff --git a/git-merge.sh b/git-merge.sh index a948878b9..c895a04f6 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -265,7 +265,7 @@ f,*) echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)" git-update-index --refresh 2>/dev/null new_head=$(git-rev-parse --verify "$1^0") && - git-read-tree -u -v -m $head "$new_head" && + git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" && finish "$new_head" "Fast forward" dropsave exit 0 @@ -400,7 +400,14 @@ fi case "$best_strategy" in '') restorestate - echo >&2 "No merge strategy handled the merge." + case "$use_strategies" in + ?*' '?*) + echo >&2 "No merge strategy handled the merge." + ;; + *) + echo >&2 "Merge with strategy $use_strategies failed." + ;; + esac exit 2 ;; "$wt_strategy") diff --git a/git-rebase.sh b/git-rebase.sh index 25530dfdc..2b4f3477f 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -139,6 +139,10 @@ do --skip) if test -d "$dotest" then + if test -d "$GIT_DIR/rr-cache" + then + git-rerere clear + fi prev_head="`cat $dotest/prev_head`" end="`cat $dotest/end`" msgnum="`cat $dotest/msgnum`" @@ -157,6 +161,10 @@ do exit ;; --abort) + if test -d "$GIT_DIR/rr-cache" + then + git-rerere clear + fi if test -d "$dotest" then rm -r "$dotest" diff --git a/git-repack.sh b/git-repack.sh index f150a558c..067898f12 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -67,6 +67,8 @@ name=$(git-pack-objects --non-empty --all $args </dev/null "$PACKTMP") || if [ -z "$name" ]; then echo Nothing new to pack. else + chmod a-w "$PACKTMP-$name.pack" + chmod a-w "$PACKTMP-$name.idx" if test "$quiet" != '-q'; then echo "Pack pack-$name created." fi diff --git a/git-rerere.perl b/git-rerere.perl index d3664ff49..fdd685489 100755 --- a/git-rerere.perl +++ b/git-rerere.perl @@ -154,7 +154,7 @@ sub find_conflict { sub merge { my ($name, $path) = @_; record_preimage($path, "$rr_dir/$name/thisimage"); - unless (system('merge', map { "$rr_dir/$name/${_}image" } + unless (system('git merge-file', map { "$rr_dir/$name/${_}image" } qw(this pre post))) { my $in; open $in, "<$rr_dir/$name/thisimage" or @@ -169,9 +169,66 @@ sub merge { return 0; } +sub garbage_collect_rerere { + # We should allow specifying these from the command line and + # that is why the caller gives @ARGV to us, but I am lazy. + + my $cutoff_noresolve = 15; # two weeks + my $cutoff_resolve = 60; # two months + my @to_remove; + while (<$rr_dir/*/preimage>) { + my ($dir) = /^(.*)\/preimage$/; + my $cutoff = ((-f "$dir/postimage") + ? $cutoff_resolve + : $cutoff_noresolve); + my $age = -M "$_"; + if ($cutoff <= $age) { + push @to_remove, $dir; + } + } + if (@to_remove) { + rmtree(\@to_remove); + } +} + -d "$rr_dir" || exit(0); read_rr(); + +if (@ARGV) { + my $arg = shift @ARGV; + if ($arg eq 'clear') { + for my $path (keys %merge_rr) { + my $name = $merge_rr{$path}; + if (-d "$rr_dir/$name" && + ! -f "$rr_dir/$name/postimage") { + rmtree(["$rr_dir/$name"]); + } + } + unlink $merge_rr; + } + elsif ($arg eq 'status') { + for my $path (keys %merge_rr) { + print $path, "\n"; + } + } + elsif ($arg eq 'diff') { + for my $path (keys %merge_rr) { + my $name = $merge_rr{$path}; + system('diff', ((@ARGV == 0) ? ('-u') : @ARGV), + '-L', "a/$path", '-L', "b/$path", + "$rr_dir/$name/preimage", $path); + } + } + elsif ($arg eq 'gc') { + garbage_collect_rerere(@ARGV); + } + else { + die "$0 unknown command: $arg\n"; + } + exit 0; +} + my %conflict = map { $_ => 1 } find_conflict(); # MERGE_RR records paths with conflicts immediately after merge diff --git a/git-svn.perl b/git-svn.perl index 15254e479..73ab8d873 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -925,19 +925,38 @@ sub cmt_showable { sub log_use_color { return 1 if $_color; - my $dc; - chomp($dc = `git-repo-config --get diff.color`); + my ($dc, $dcvar); + $dcvar = 'color.diff'; + $dc = `git-repo-config --get $dcvar`; + if ($dc eq '') { + # nothing at all; fallback to "diff.color" + $dcvar = 'diff.color'; + $dc = `git-repo-config --get $dcvar`; + } + chomp($dc); if ($dc eq 'auto') { - if (-t *STDOUT || (defined $_pager && - `git-repo-config --bool --get pager.color` !~ /^false/)) { + my $pc; + $pc = `git-repo-config --get color.pager`; + if ($pc eq '') { + # does not have it -- fallback to pager.color + $pc = `git-repo-config --bool --get pager.color`; + } + else { + $pc = `git-repo-config --bool --get color.pager`; + if ($?) { + $pc = 'false'; + } + } + chomp($pc); + if (-t *STDOUT || (defined $_pager && $pc eq 'true')) { return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); } return 0; } return 0 if $dc eq 'never'; return 1 if $dc eq 'always'; - chomp($dc = `git-repo-config --bool --get diff.color`); - $dc eq 'true'; + chomp($dc = `git-repo-config --bool --get $dcvar`); + return ($dc eq 'true'); } sub git_svn_log_cmd { @@ -2981,7 +3000,8 @@ sub libsvn_log_entry { my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or die "Unable to parse date: $date\n"; - if (defined $_authors && ! defined $users{$author}) { + if (defined $author && length $author > 0 && + defined $_authors && ! defined $users{$author}) { die "Author: $author not defined in $_authors file\n"; } $msg = '' if ($rev == 0 && !defined $msg); @@ -247,6 +247,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, + { "merge-file", cmd_merge_file }, { "mv", cmd_mv, RUN_SETUP }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, diff --git a/git.spec.in b/git.spec.in index f2374b733..fb95e3759 100644 --- a/git.spec.in +++ b/git.spec.in @@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages. %package core Summary: Core git tools Group: Development/Tools -Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, expat +Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat %description core This is a stupid (but extremely fast) directory content manager. It doesn't do a whole lot, but what it _does_ do is track directory diff --git a/merge-file.c b/merge-file.c index fc9b14899..69dc1ebbf 100644 --- a/merge-file.c +++ b/merge-file.c @@ -3,52 +3,6 @@ #include "xdiff-interface.h" #include "blob.h" -static void rm_temp_file(const char *filename) -{ - unlink(filename); - free((void *)filename); -} - -static const char *write_temp_file(mmfile_t *f) -{ - int fd; - const char *tmp = getenv("TMPDIR"); - char *filename; - - if (!tmp) - tmp = "/tmp"; - filename = mkpath("%s/%s", tmp, "git-tmp-XXXXXX"); - fd = mkstemp(filename); - if (fd < 0) - return NULL; - filename = xstrdup(filename); - if (f->size != xwrite(fd, f->ptr, f->size)) { - rm_temp_file(filename); - return NULL; - } - close(fd); - return filename; -} - -static void *read_temp_file(const char *filename, unsigned long *size) -{ - struct stat st; - char *buf = NULL; - int fd = open(filename, O_RDONLY); - if (fd < 0) - return NULL; - if (!fstat(fd, &st)) { - *size = st.st_size; - buf = xmalloc(st.st_size); - if (st.st_size != xread(fd, buf, st.st_size)) { - free(buf); - buf = NULL; - } - } - close(fd); - return buf; -} - static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { void *buf; @@ -72,22 +26,19 @@ static void free_mmfile(mmfile_t *f) static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size) { - void *res; - const char *t1, *t2, *t3; - - t1 = write_temp_file(base); - t2 = write_temp_file(our); - t3 = write_temp_file(their); - res = NULL; - if (t1 && t2 && t3) { - int code = run_command("merge", t2, t1, t3, NULL); - if (!code || code == -1) - res = read_temp_file(t2, size); - } - rm_temp_file(t1); - rm_temp_file(t2); - rm_temp_file(t3); - return res; + mmbuffer_t res; + xpparam_t xpp; + int merge_status; + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(base, our, ".our", their, ".their", + &xpp, XDL_MERGE_ZEALOUS, &res); + + if (merge_status < 0) + return NULL; + + *size = res.size; + return res.ptr; } static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf) diff --git a/merge-recursive.c b/merge-recursive.c index 32e186c15..6dd6e2e5a 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -21,6 +21,7 @@ #include "tag.h" #include "unpack-trees.h" #include "path-list.h" +#include "xdiff-interface.h" /* * A virtual commit has @@ -604,24 +605,21 @@ struct merge_file_info merge:1; }; -static char *git_unpack_file(const unsigned char *sha1, char *path) +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) { - void *buf; - char type[20]; unsigned long size; - int fd; + char type[20]; - buf = read_sha1_file(sha1, type, &size); - if (!buf || strcmp(type, blob_type)) - die("unable to read blob object %s", sha1_to_hex(sha1)); + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } - strcpy(path, ".merge_file_XXXXXX"); - fd = mkstemp(path); - if (fd < 0) - die("unable to create temp-file"); - flush_buffer(fd, buf, size); - close(fd); - return path; + mm->ptr = read_sha1_file(sha1, type, &size); + if (!mm->ptr || strcmp(type, blob_type)) + die("unable to read blob object %s", sha1_to_hex(sha1)); + mm->size = size; } static struct merge_file_info merge_file(struct diff_filespec *o, @@ -652,49 +650,41 @@ static struct merge_file_info merge_file(struct diff_filespec *o, else if (sha_eq(b->sha1, o->sha1)) hashcpy(result.sha, a->sha1); else if (S_ISREG(a->mode)) { - int code = 1, fd; - struct stat st; - char orig[PATH_MAX]; - char src1[PATH_MAX]; - char src2[PATH_MAX]; - const char *argv[] = { - "merge", "-L", NULL, "-L", NULL, "-L", NULL, - NULL, NULL, NULL, - NULL - }; - char *la, *lb, *lo; - - git_unpack_file(o->sha1, orig); - git_unpack_file(a->sha1, src1); - git_unpack_file(b->sha1, src2); - - argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path)); - argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path)); - argv[4] = lo = xstrdup(mkpath("orig/%s", o->path)); - argv[7] = src1; - argv[8] = orig; - argv[9] = src2, - - code = run_command_v(10, argv); - - free(la); - free(lb); - free(lo); - if (code && code < -256) { - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?"); - } - fd = open(src1, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0 || - index_fd(result.sha, fd, &st, 1, - "blob")) - die("Unable to add %s to database", src1); - - unlink(orig); - unlink(src1); - unlink(src2); - - result.clean = WEXITSTATUS(code) == 0; + mmfile_t orig, src1, src2; + mmbuffer_t result_buf; + xpparam_t xpp; + char *name1, *name2; + int merge_status; + + name1 = xstrdup(mkpath("%s/%s", branch1, a->path)); + name2 = xstrdup(mkpath("%s/%s", branch2, b->path)); + + fill_mm(o->sha1, &orig); + fill_mm(a->sha1, &src1); + fill_mm(b->sha1, &src2); + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(&orig, + &src1, name1, + &src2, name2, + &xpp, XDL_MERGE_ZEALOUS, + &result_buf); + free(name1); + free(name2); + free(orig.ptr); + free(src1.ptr); + free(src2.ptr); + + if ((merge_status < 0) || !result_buf.ptr) + die("Failed to execute internal merge"); + + if (write_sha1_file(result_buf.ptr, result_buf.size, + blob_type, result.sha)) + die("Unable to add %s to database", + a->path); + + free(result_buf.ptr); + result.clean = (merge_status == 0); } else { if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) die("cannot merge modes?"); @@ -889,7 +879,7 @@ static int process_renames(struct path_list *a_renames, struct diff_filespec src_other, dst_other; int try_merge, stage = a_renames == renames1 ? 3: 2; - remove_file(1, ren1_src, 1); + remove_file(1, ren1_src, index_only); hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); src_other.mode = ren1->src_entry->stages[stage].mode; @@ -1061,38 +1051,17 @@ static int process_entry(const char *path, struct stage_data *entry, output("Adding %s", path); update_file(1, sha, mode, path); } - } else if (!o_sha && a_sha && b_sha) { - /* Case C: Added in both (check for same permissions). */ - if (sha_eq(a_sha, b_sha)) { - if (a_mode != b_mode) { - clean_merge = 0; - output("CONFLICT: File %s added identically in both branches, " - "but permissions conflict %06o->%06o", - path, a_mode, b_mode); - output("CONFLICT: adding with permission: %06o", a_mode); - update_file(0, a_sha, a_mode, path); - } else { - /* This case is handled by git-read-tree */ - assert(0 && "This case must be handled by git-read-tree"); - } - } else { - const char *new_path1, *new_path2; - clean_merge = 0; - new_path1 = unique_path(path, branch1); - new_path2 = unique_path(path, branch2); - output("CONFLICT (add/add): File %s added non-identically " - "in both branches. Adding as %s and %s instead.", - path, new_path1, new_path2); - remove_file(0, path, 0); - update_file(0, a_sha, a_mode, new_path1); - update_file(0, b_sha, b_mode, new_path2); - } - - } else if (o_sha && a_sha && b_sha) { + } else if (a_sha && b_sha) { + /* Case C: Added in both (check for same permissions) and */ /* case D: Modified in both, but differently. */ + const char *reason = "content"; struct merge_file_info mfi; struct diff_filespec o, a, b; + if (!o_sha) { + reason = "add/add"; + o_sha = (unsigned char *)null_sha1; + } output("Auto-merging %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); @@ -1109,7 +1078,8 @@ static int process_entry(const char *path, struct stage_data *entry, update_file(1, mfi.sha, mfi.mode, path); else { clean_merge = 0; - output("CONFLICT (content): Merge conflict in %s", path); + output("CONFLICT (%s): Merge conflict in %s", + reason, path); if (index_only) update_file(0, mfi.sha, mfi.mode, path); @@ -534,7 +534,7 @@ int check_ref_format(const char *ref) level++; if (!ch) { if (level < 2) - return -1; /* at least of form "heads/blah" */ + return -2; /* at least of form "heads/blah" */ return 0; } } @@ -610,6 +610,29 @@ static int remove_empty_directories(char *file) return remove_empty_dir_recursive(path, len); } +static int is_refname_available(const char *ref, const char *oldref, + struct ref_list *list, int quiet) +{ + int namlen = strlen(ref); /* e.g. 'foo/bar' */ + while (list) { + /* list->name could be 'foo' or 'foo/bar/baz' */ + if (!oldref || strcmp(oldref, list->name)) { + int len = strlen(list->name); + int cmplen = (namlen < len) ? namlen : len; + const char *lead = (namlen < len) ? list->name : ref; + if (!strncmp(ref, list->name, cmplen) && + lead[cmplen] == '/') { + if (!quiet) + error("'%s' exists; cannot create '%s'", + list->name, ref); + return 0; + } + } + list = list->next; + } + return 1; +} + static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag) { char *ref_file; @@ -643,29 +666,14 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char orig_ref, strerror(errno)); goto error_return; } - if (is_null_sha1(lock->old_sha1)) { - /* The ref did not exist and we are creating it. - * Make sure there is no existing ref that is packed - * whose name begins with our refname, nor a ref whose - * name is a proper prefix of our refname. - */ - int namlen = strlen(ref); /* e.g. 'foo/bar' */ - struct ref_list *list = get_packed_refs(); - while (list) { - /* list->name could be 'foo' or 'foo/bar/baz' */ - int len = strlen(list->name); - int cmplen = (namlen < len) ? namlen : len; - const char *lead = (namlen < len) ? list->name : ref; - - if (!strncmp(ref, list->name, cmplen) && - lead[cmplen] == '/') { - error("'%s' exists; cannot create '%s'", - list->name, ref); - goto error_return; - } - list = list->next; - } - } + /* When the ref did not exist and we are creating it, + * make sure there is no existing ref that is packed + * whose name begins with our refname, nor a ref whose + * name is a proper prefix of our refname. + */ + if (is_null_sha1(lock->old_sha1) && + !is_refname_available(ref, NULL, get_packed_refs(), 0)) + goto error_return; lock->lk = xcalloc(1, sizeof(struct lock_file)); @@ -776,6 +784,117 @@ int delete_ref(const char *refname, unsigned char *sha1) return ret; } +int rename_ref(const char *oldref, const char *newref, const char *logmsg) +{ + static const char renamed_ref[] = "RENAMED-REF"; + unsigned char sha1[20], orig_sha1[20]; + int flag = 0, logmoved = 0; + struct ref_lock *lock; + struct stat loginfo; + int log = !lstat(git_path("logs/%s", oldref), &loginfo); + + if (S_ISLNK(loginfo.st_mode)) + return error("reflog for %s is a symlink", oldref); + + if (!resolve_ref(oldref, orig_sha1, 1, &flag)) + return error("refname %s not found", oldref); + + if (!is_refname_available(newref, oldref, get_packed_refs(), 0)) + return 1; + + if (!is_refname_available(newref, oldref, get_loose_refs(), 0)) + return 1; + + lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL); + if (!lock) + return error("unable to lock %s", renamed_ref); + lock->force_write = 1; + if (write_ref_sha1(lock, orig_sha1, logmsg)) + return error("unable to save current sha1 in %s", renamed_ref); + + if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log"))) + return error("unable to move logfile logs/%s to tmp-renamed-log: %s", + oldref, strerror(errno)); + + if (delete_ref(oldref, orig_sha1)) { + error("unable to delete old %s", oldref); + goto rollback; + } + + if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) { + if (errno==EISDIR) { + if (remove_empty_directories(git_path("%s", newref))) { + error("Directory not empty: %s", newref); + goto rollback; + } + } else { + error("unable to delete existing %s", newref); + goto rollback; + } + } + + if (log && safe_create_leading_directories(git_path("logs/%s", newref))) { + error("unable to create directory for %s", newref); + goto rollback; + } + + retry: + if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) { + if (errno==EISDIR) { + if (remove_empty_directories(git_path("logs/%s", newref))) { + error("Directory not empty: logs/%s", newref); + goto rollback; + } + goto retry; + } else { + error("unable to move logfile tmp-renamed-log to logs/%s: %s", + newref, strerror(errno)); + goto rollback; + } + } + logmoved = log; + + lock = lock_ref_sha1_basic(newref, NULL, NULL); + if (!lock) { + error("unable to lock %s for update", newref); + goto rollback; + } + + lock->force_write = 1; + hashcpy(lock->old_sha1, orig_sha1); + if (write_ref_sha1(lock, orig_sha1, logmsg)) { + error("unable to write current sha1 into %s", newref); + goto rollback; + } + + return 0; + + rollback: + lock = lock_ref_sha1_basic(oldref, NULL, NULL); + if (!lock) { + error("unable to lock %s for rollback", oldref); + goto rollbacklog; + } + + lock->force_write = 1; + flag = log_all_ref_updates; + log_all_ref_updates = 0; + if (write_ref_sha1(lock, orig_sha1, NULL)) + error("unable to write current sha1 into %s", oldref); + log_all_ref_updates = flag; + + rollbacklog: + if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from %s: %s", + oldref, newref, strerror(errno)); + if (!logmoved && log && + rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from tmp-renamed-log: %s", + oldref, strerror(errno)); + + return 1; +} + void unlock_ref(struct ref_lock *lock) { if (lock->lock_fd >= 0) { @@ -47,4 +47,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); +/** rename ref, return 0 on success **/ +extern int rename_ref(const char *oldref, const char *newref, const char *logmsg); + #endif /* REFS_H */ diff --git a/send-pack.c b/send-pack.c index 328dbbc16..cc884f3b2 100644 --- a/send-pack.c +++ b/send-pack.c @@ -406,6 +406,25 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) return ret; } +static void verify_remote_names(int nr_heads, char **heads) +{ + int i; + + for (i = 0; i < nr_heads; i++) { + const char *remote = strchr(heads[i], ':'); + + remote = remote ? (remote + 1) : heads[i]; + switch (check_ref_format(remote)) { + case 0: /* ok */ + case -2: /* ok but a single level -- that is fine for + * a match pattern. + */ + continue; + } + die("remote part of refspec is not a valid name in %s", + heads[i]); + } +} int main(int argc, char **argv) { @@ -457,6 +476,8 @@ int main(int argc, char **argv) usage(send_pack_usage); if (heads && send_all) usage(send_pack_usage); + verify_remote_names(nr_heads, heads); + pid = git_connect(fd, dest, exec); if (pid < 0) return 1; diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 81f3bedc9..3260d1d7a 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -19,11 +19,7 @@ modification *should* take notice and update the test vectors here. ' ################################################################ -# It appears that people are getting bitten by not installing -# 'merge' (usually part of RCS package in binary distributions). -# Check this and error out before running any tests. Also catch -# the bogosity of trying to run tests without building while we -# are at it. +# It appears that people try to run tests without building... ../git >/dev/null if test $? != 1 @@ -32,14 +28,6 @@ then exit 1 fi -merge >/dev/null 2>/dev/null -if test $? = 127 -then - echo >&2 'You do not seem to have "merge" installed. -Please check INSTALL document.' - exit 1 -fi - . ./test-lib.sh ################################################################ diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index 018fbea45..4f664f6ad 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -8,23 +8,27 @@ test_description='read-tree -m -u checks working tree files' test_expect_success 'two-way setup' ' + mkdir subdir && echo >file1 file one && echo >file2 file two && - git update-index --add file1 file2 && + echo >subdir/file1 file one in subdirectory && + echo >subdir/file2 file two in subdirectory && + git update-index --add file1 file2 subdir/file1 subdir/file2 && git commit -m initial && git branch side && git tag -f branch-point && echo file2 is not tracked on the master anymore && - rm -f file2 && - git update-index --remove file2 && - git commit -a -m "master removes file2" + rm -f file2 subdir/file2 && + git update-index --remove file2 subdir/file2 && + git commit -a -m "master removes file2 and subdir/file2" ' test_expect_success 'two-way not clobbering' ' echo >file2 master creates untracked file2 && + echo >subdir/file2 master creates untracked subdir/file2 && if err=`git read-tree -m -u master side 2>&1` then echo should have complained @@ -34,20 +38,82 @@ test_expect_success 'two-way not clobbering' ' fi ' +echo file2 >.gitignore + +test_expect_success 'two-way with incorrect --exclude-per-directory (1)' ' + + if err=`git read-tree -m --exclude-per-directory=.gitignore master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +test_expect_success 'two-way with incorrect --exclude-per-directory (2)' ' + + if err=`git read-tree -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +test_expect_success 'two-way clobbering a ignored file' ' + + git read-tree -m -u --exclude-per-directory=.gitignore master side +' + +rm -f .gitignore + # three-tree test -test_expect_success 'three-way not complaining' ' +test_expect_success 'three-way not complaining on an untracked path in both' ' - rm -f file2 && + rm -f file2 subdir/file2 && git checkout side && echo >file3 file three && - git update-index --add file3 && - git commit -a -m "side adds file3" && + echo >subdir/file3 file three && + git update-index --add file3 subdir/file3 && + git commit -a -m "side adds file3 and removes file2" && git checkout master && echo >file2 file two is untracked on the master side && + echo >subdir/file2 file two is untracked on the master side && git-read-tree -m -u branch-point master side ' +test_expect_success 'three-way not cloberring a working tree file' ' + + git reset --hard && + rm -f file2 subdir/file2 file3 subdir/file3 && + git checkout master && + echo >file3 file three created in master, untracked && + echo >subdir/file3 file three created in master, untracked && + if err=`git read-tree -m -u branch-point master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +echo >.gitignore file3 + +test_expect_success 'three-way not complaining on an untracked file' ' + + git reset --hard && + rm -f file2 subdir/file2 file3 subdir/file3 && + git checkout master && + echo >file3 file three created in master, untracked && + echo >subdir/file3 file three created in master, untracked && + + git read-tree -m -u --exclude-per-directory=.gitignore branch-point master side +' + test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index acb54b6a0..5782c30b0 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -70,4 +70,45 @@ test_expect_success \ git-branch -d l/m && git-branch l' +test_expect_success \ + 'git branch -m m m/m should work' \ + 'git-branch -l m && + git-branch -m m m/m && + test -f .git/logs/refs/heads/m/m' + +test_expect_success \ + 'git branch -m n/n n should work' \ + 'git-branch -l n/n && + git-branch -m n/n n + test -f .git/logs/refs/heads/n' + +test_expect_failure \ + 'git branch -m o/o o should fail when o/p exists' \ + 'git-branch o/o && + git-branch o/p && + git-branch -m o/o o' + +test_expect_failure \ + 'git branch -m q r/q should fail when r exists' \ + 'git-branch q && + git-branch r && + git-branch -m q r/q' + +test_expect_success \ + 'git branch -m s/s s should work when s/t is deleted' \ + 'git-branch -l s/s && + test -f .git/logs/refs/heads/s/s && + git-branch -l s/t && + test -f .git/logs/refs/heads/s/t && + git-branch -d s/t && + git-branch -m s/s s && + test -f .git/logs/refs/heads/s' + +test_expect_failure \ + 'git-branch -m u v should fail when the reflog for u is a symlink' \ + 'git-branch -l u && + mv .git/logs/refs/heads/u real-u && + ln -s real-u .git/logs/refs/heads/u && + git-branch -m u v' + test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh new file mode 100644 index 000000000..5d9b6f34b --- /dev/null +++ b/t/t6023-merge-file.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +test_description='RCS merge replacement: merge-file' +. ./test-lib.sh + +cat > orig.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new1.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +cat > new2.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new3.txt << EOF +DOMINUS regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new4.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +EOF +echo -n "propter nomen suum." >> new4.txt + +cp new1.txt test.txt +test_expect_success "merge without conflict" \ + "git-merge-file test.txt orig.txt new2.txt" + +cp new1.txt test2.txt +test_expect_success "merge without conflict (missing LF at EOF)" \ + "git-merge-file test2.txt orig.txt new2.txt" + +test_expect_success "merge result added missing LF" \ + "diff -u test.txt test2.txt" + +cp test.txt backup.txt +test_expect_failure "merge with conflicts" \ + "git-merge-file test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< test.txt +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers" "diff -u test.txt expect.txt" + +cp backup.txt test.txt +test_expect_failure "merge with conflicts, using -L" \ + "git-merge-file -L 1 -L 2 test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< 1 +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers, with -L" \ + "diff -u test.txt expect.txt" + +test_done + diff --git a/t/t6023-merge-rename-nocruft.sh b/t/t6023-merge-rename-nocruft.sh new file mode 100755 index 000000000..69c66cf6f --- /dev/null +++ b/t/t6023-merge-rename-nocruft.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +test_description='Merge-recursive merging renames' +. ./test-lib.sh + +test_expect_success setup \ +' +cat >A <<\EOF && +a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +c cccccccccccccccccccccccccccccccccccccccccccccccc +d dddddddddddddddddddddddddddddddddddddddddddddddd +e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +f ffffffffffffffffffffffffffffffffffffffffffffffff +g gggggggggggggggggggggggggggggggggggggggggggggggg +h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh +i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii +j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj +k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk +l llllllllllllllllllllllllllllllllllllllllllllllll +m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm +n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn +o oooooooooooooooooooooooooooooooooooooooooooooooo +EOF + +cat >M <<\EOF && +A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD +E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG +H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII +J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ +K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK +L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO +EOF + +git add A M && +git commit -m "initial has A and M" && +git branch white && +git branch red && + +git checkout white && +sed -e "/^g /s/.*/g : white changes a line/" <A >B && +sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N && +rm -f A M && +git update-index --add --remove A B M N && +git commit -m "white renames A->B, M->N" && + +git checkout red && +echo created by red >R && +git update-index --add R && +git commit -m "red creates R" && + +git checkout master' + +# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae +test_expect_success 'merge white into red (A->B,M->N)' \ +' + git checkout -b red-white red && + git merge white && + git write-tree >/dev/null || { + echo "BAD: merge did not complete" + return 1 + } + + test -f B || { + echo "BAD: B does not exist in working directory" + return 1 + } + test -f N || { + echo "BAD: N does not exist in working directory" + return 1 + } + test -f R || { + echo "BAD: R does not exist in working directory" + return 1 + } + + test -f A && { + echo "BAD: A still exists in working directory" + return 1 + } + test -f M && { + echo "BAD: M still exists in working directory" + return 1 + } + return 0 +' + +test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index 9416c271e..964010e76 100755..100644 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -58,9 +58,19 @@ GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F test_expect_failure "combined merge conflicts" "git merge -m final G" +cat > expect << EOF +<<<<<<< HEAD/a1 +F +======= +G +>>>>>>> 26f86b677eb03d4d956dbe108b29cb77061c1e73/a1 +EOF + +test_expect_success "result contains a conflict" "diff -u expect a1" + git ls-files --stage > out cat > expect << EOF -100644 f70f10e4db19068f79bc43844b49f3eece45c4e8 1 a1 +100644 f16f906ab60483c100d1241dfc39868de9ec9fcb 1 a1 100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2 a1 100644 fd7923529855d0b274795ae3349c5e0438333979 3 a1 EOF diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index c1024790e..ca0513b16 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -89,18 +89,17 @@ test_expect_success \ ! git cvsexportcommit -c $id )' -# Should fail, but only on the git-cvsexportcommit stage -test_expect_success \ - 'Fail to remove binary file more than one generation old' \ - 'git reset --hard HEAD^ && - cat F/newfile6.png >>D/newfile4.png && - git commit -a -m "generation 2 (again)" && - rm -f D/newfile4.png && - git commit -a -m "generation 3" && - id=$(git rev-list --max-count=1 HEAD) && - (cd "$CVSWORK" && - ! git cvsexportcommit -c $id - )' +#test_expect_success \ +# 'Fail to remove binary file more than one generation old' \ +# 'git reset --hard HEAD^ && +# cat F/newfile6.png >>D/newfile4.png && +# git commit -a -m "generation 2 (again)" && +# rm -f D/newfile4.png && +# git commit -a -m "generation 3" && +# id=$(git rev-list --max-count=1 HEAD) && +# (cd "$CVSWORK" && +# ! git cvsexportcommit -c $id +# )' # We reuse the state from two tests back here @@ -108,7 +107,7 @@ test_expect_success \ # fail with gnu patch, so cvsexportcommit must handle that. test_expect_success \ 'Remove only binary files' \ - 'git reset --hard HEAD^^^ && + 'git reset --hard HEAD^^ && rm -f D/newfile4.png && git commit -a -m "test: remove only a binary file" && id=$(git rev-list --max-count=1 HEAD) && @@ -142,20 +141,73 @@ test_expect_success \ diff F/newfile6.png ../F/newfile6.png )' -test_expect_success 'Retain execute bit' ' - mkdir G && - echo executeon >G/on && - chmod +x G/on && - echo executeoff >G/off && - git add G/on && - git add G/off && - git commit -a -m "Execute test" && - ( - cd "$CVSWORK" && - git-cvsexportcommit -c HEAD - test -x G/on && - ! test -x G/off - ) -' +test_expect_success \ + 'New file with spaces in file name' \ + 'mkdir "G g" && + echo ok then >"G g/with spaces.txt" && + git add "G g/with spaces.txt" && \ + cp ../test9200a.png "G g/with spaces.png" && \ + git add "G g/with spaces.png" && + git commit -a -m "With spaces" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git-cvsexportcommit -c $id && + test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.1/-kb with spaces.txt/1.1/" + )' + +test_expect_success \ + 'Update file with spaces in file name' \ + 'echo Ok then >>"G g/with spaces.txt" && + cat ../test9200a.png >>"G g/with spaces.png" && \ + git add "G g/with spaces.png" && + git commit -a -m "Update with spaces" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git-cvsexportcommit -c $id + test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/" + )' + +# This test contains ISO-8859-1 characters +test_expect_success \ + 'File with non-ascii file name' \ + 'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö && + echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt && + git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt && + cp ../test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && + git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && + git commit -a -m "Går det så går det" && \ + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git-cvsexportcommit -v -c $id && + test "$(echo $(sort Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/CVS/Entries|cut -d/ -f2,3,5))" = "gårdetsågårdet.png/1.1/-kb gårdetsågårdet.txt/1.1/" + )' + +test_expect_success \ + 'Mismatching patch should fail' \ + 'date >>"E/newfile5.txt" && + git add "E/newfile5.txt" && + git commit -a -m "Update one" && + date >>"E/newfile5.txt" && + git add "E/newfile5.txt" && + git commit -a -m "Update two" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + ! git-cvsexportcommit -c $id + )' + +test_expect_success \ + 'Retain execute bit' \ + 'mkdir G && + echo executeon >G/on && + chmod +x G/on && + echo executeoff >G/off && + git add G/on && + git add G/off && + git commit -a -m "Execute test" && + (cd "$CVSWORK" && + git-cvsexportcommit -c HEAD + test -x G/on && + ! test -x G/off + )' test_done diff --git a/unpack-trees.c b/unpack-trees.c index 47aa804a8..b8689ebc8 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1,6 +1,7 @@ #include <signal.h> #include <sys/time.h> #include "cache.h" +#include "dir.h" #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" @@ -77,6 +78,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, { int baselen = strlen(base); int src_size = len + 1; + int i_stk = i_stk; + int retval = 0; + + if (o->dir) + i_stk = push_exclude_per_directory(o->dir, base, strlen(base)); + do { int i; const char *first; @@ -143,7 +150,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, } /* No name means we're done */ if (!first) - return 0; + goto leave_directory; pathlen = strlen(first); ce_size = cache_entry_size(baselen + pathlen); @@ -240,13 +247,20 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, newbase[baselen + pathlen] = '/'; newbase[baselen + pathlen + 1] = '\0'; if (unpack_trees_rec(subposns, len, newbase, o, - indpos, df_conflict_list)) - return -1; + indpos, df_conflict_list)) { + retval = -1; + goto leave_directory; + } free(newbase); } free(subposns); free(src); } while (1); + + leave_directory: + if (o->dir) + pop_exclude_per_directory(o->dir, i_stk); + return retval; } /* Unlink the last component and attempt to remove leading @@ -458,7 +472,7 @@ static void invalidate_ce_path(struct cache_entry *ce) /* * We do not want to remove or overwrite a working tree file that - * is not tracked. + * is not tracked, unless it is ignored. */ static void verify_absent(const char *path, const char *action, struct unpack_trees_options *o) @@ -467,7 +481,7 @@ static void verify_absent(const char *path, const char *action, if (o->index_only || o->reset || !o->update) return; - if (!lstat(path, &st)) + if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path))) die("Untracked working tree file '%s' " "would be %s by merge.", path, action); } diff --git a/unpack-trees.h b/unpack-trees.h index c4601621c..191f7442f 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -16,6 +16,7 @@ struct unpack_trees_options { int verbose_update; int aggressive; const char *prefix; + struct dir_struct *dir; merge_fn_t fn; int head_idx; diff --git a/wt-status.c b/wt-status.c index df582a03e..6e9414dbb 100644 --- a/wt-status.c +++ b/wt-status.c @@ -163,7 +163,7 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q, int i; if (q->nr) wt_status_print_header("Changed but not updated", - "use git-update-index to mark for commit"); + "use git-add on files to include for commit"); for (i = 0; i < q->nr; i++) wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]); if (q->nr) diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index c9f817818..fa409d523 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -49,6 +49,9 @@ extern "C" { #define XDL_BDOP_CPY 2 #define XDL_BDOP_INSB 3 +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 typedef struct s_mmfile { char *ptr; @@ -90,6 +93,10 @@ long xdl_mmfile_size(mmfile_t *mmf); int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result); + #ifdef __cplusplus } #endif /* #ifdef __cplusplus */ diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index d76e76a0e..9aeebc473 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -45,7 +45,6 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, xdalgoenv_t *xenv); static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2); -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); @@ -397,7 +396,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; char *rchg = xdf->rchg, *rchgo = xdfo->rchg; xrecord_t **recs = xdf->recs; diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h index d3b72716b..472aeaecf 100644 --- a/xdiff/xdiffi.h +++ b/xdiff/xdiffi.h @@ -50,6 +50,7 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); void xdl_free_script(xdchange_t *xscr); int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c new file mode 100644 index 000000000..352207e51 --- /dev/null +++ b/xdiff/xmerge.c @@ -0,0 +1,419 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + */ + int mode; + long i1, i2; + long chg1, chg2; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i1, long chg1, long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + xrecord_t **recs = xe->xdf2.recs + i; + int size = 0; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest) +{ + const int marker_size = 7; + int marker1_size = (name1 ? strlen(name1) + 1 : 0); + int marker2_size = (name2 ? strlen(name2) + 1 : 0); + int conflict_marker_size = 3 * (marker_size + 1) + + marker1_size + marker2_size; + int size, i1, j; + + for (size = i1 = 0; m; m = m->next) { + if (m->mode == 0) { + size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '<'; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, + marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } else + size += conflict_marker_size; + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '='; + dest[size++] = '\n'; + } + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '>'; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, + marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + } else if (m->mode == 1) + size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0, + dest ? dest + size : NULL); + else if (m->mode == 2) + size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1, + m->i1 + m->chg2 - i1, 0, + dest ? dest + size : NULL); + i1 = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0, + dest ? dest + size : NULL); + return size; +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, it's a bug. */ + xdl_free_env(&xe); + return -2; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, + xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, + int level, xpparam_t const *xpp, mmbuffer_t *result) { + xdmerge_t *changes, *c; + int i1, i2, chg1, chg2; + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level < 1 || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) + i1 -= off; + else + i2 += off; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo > 0) + chg2 += ffo; + else + chg1 -= ffo; + if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 >= i2) + xscr2 = xscr2->next; + if (i2 >= i1) + xscr1 = xscr1->next; + } + while (xscr1) { + if (!changes) + changes = c; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + changes, NULL); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes, + result->ptr); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result) { + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + int status; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 || + xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_env(&xe2); + return -1; + } + status = 0; + if (xscr1 || xscr2) { + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, name1, + &xe2, xscr2, name2, + level, xpp, result); + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + } + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return status; +} |