{"thread":{"id":"730","subject":"resolving merge conflicts?","startedAt":"2005-05-27T03:48:48Z","lastAt":"2005-05-27T09:44:40Z","messageCount":4,"participants":["Jeff Garzik","Junio C Hamano","Linus Torvalds","Thomas Glanzmann"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"4027","messageId":"429698A0.1020008@pobox.com","threadId":"730","inReplyTo":null,"subject":"resolving merge conflicts?","fromName":"Jeff Garzik","fromEmail":"jgarzik@pobox.com","sentAt":"2005-05-27T03:48:48Z","receivedAt":"2005-05-27T03:48:48Z","isPatch":false,"sender":{"key":"jgarzik@pobox.com","avatar":null},"body":"\nSo is there a doc or something that describes how to resolve merge \nconflicts?\n\nPresumably I look at .merge_file_*, and make sure that all necessary \nchanges make it into the copy of the file in the working directory... \nthen what?  How to continue the merge?\n\n\tJeff\n\n\n\n> [...]\n> Adding drivers/net/bnx2.c with perm -x.\n> Adding drivers/net/bnx2.h with perm -x.\n> Adding drivers/net/bnx2_fw.h with perm -x.\n> Auto-merging drivers/net/ixgb/ixgb_main.c.\n> merge: warning: conflicts during merge\n> ERROR: Leaving conflict merge in .merge_file_fidvEd.\n> fatal: merge program failed\n> drivers/net/ixgb/ixgb_main.c: unmerged (7d26623d859278bd1ad6369648de9ed41a112493)\n> drivers/net/ixgb/ixgb_main.c: unmerged (a6af9d9e340865278cfe4b607efed9e56c087538)\n> drivers/net/ixgb/ixgb_main.c: unmerged (35f6a7c271a2be3a1f749716cbe27b1bffc66b98)\n> drivers/net/skfp/lnkstat.c: unmerged (00a248044f8683d104d55918fe5d003a3261fa41)\n> drivers/net/skfp/lnkstat.c: unmerged (00a248044f8683d104d55918fe5d003a3261fa41)\n> drivers/net/smc-mca.h: unmerged (ac50117a7e847dcdd12591267e04f67fb05fbbd4)\n> drivers/net/smc-mca.h: unmerged (ac50117a7e847dcdd12591267e04f67fb05fbbd4)\n> drivers/s390/net/ctcmain.h: unmerged (ba3605f16335dba918a656bb1b94589fb8008490)\n> drivers/s390/net/qeth_tso.c: unmerged (c91976274e7b007b78269e40fd8b354a4e888b86)\n> drivers/s390/net/qeth_tso.c: unmerged (c91976274e7b007b78269e40fd8b354a4e888b86)\n> ...\n> fatal: git-write-tree: not able to write tree\n"},{"id":"4028","messageId":"7vekbt2uhm.fsf@assigned-by-dhcp.cox.net","threadId":"730","inReplyTo":"429698A0.1020008@pobox.com","subject":"Re: resolving merge conflicts?","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-05-27T03:55:17Z","receivedAt":"2005-05-27T03:55:17Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":">>>>> \"JG\" == Jeff Garzik <jgarzik@pobox.com> writes:\n\nJG> So is there a doc or something that describes how to resolve merge\nJG> conflicts?\n\nJG> Presumably I look at .merge_file_*, and make sure that all necessary\nJG> changes make it into the copy of the file in the working\nJG> directory... then what?  How to continue the merge?\n\nThen you would copy that .merge_* into the target file, and say\n\n    git-update-cache $that_path\n\nIf your merge decision says to remove that file, then making\nsure you do not have that file, you say\n\n    git-update-cache --remove $that_path\n\n\n"},{"id":"4033","messageId":"Pine.LNX.4.58.0505262202050.17402@ppc970.osdl.org","threadId":"730","inReplyTo":"429698A0.1020008@pobox.com","subject":"Re: resolving merge conflicts?","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2005-05-27T05:15:43Z","receivedAt":"2005-05-27T05:15:43Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Thu, 26 May 2005, Jeff Garzik wrote:\n> \n> So is there a doc or something that describes how to resolve merge \n> conflicts?\n\nDang. Not really. I've always done them just by hand.\n\n> Presumably I look at .merge_file_*, and make sure that all necessary \n> changes make it into the copy of the file in the working directory... \n> then what?  How to continue the merge?\n\nStart from that, and when you're happy with it, just do \"git-update-cache\"  \nwhen the file is to your liking, and re-do\n\n\tgit-merge-cache git-merge-one-file-script -a\n\nif you forget where you were and what other files you need to do by hand\n(which I always do, not that I've seen a lot of merge problems).\n\nAlso, I assume that this is something cogito does better. But even the \ngit-merge-one-script could probably be nicer. \n\nQuite frankly, a more friendly git-resolve-script would probably do\n\n\tgit-merge-cache -o git-merge-one-file-script -a\n\n(the \"-o\" flag causes it to merge as much as it can automatically, instead \nof exiting at the first one), and git-merge-one-file-script should \nprobably on failure do\n\n\techo \"ERROR: Leaving conflict merge in $4.\"\n\tmv \"$src2\" \"$4\"\n\texit 1\n\ninstead of leaving it in the temporary file. The temp-file is good for a\ngraphical thing (which was kind of what I was hoping for), though, so it\nprobably depends on how you continue with the merge.\n\n(I also like the temp-file just because _if_ you have dirty data in your \ntree, overwriting it with the failed merge is pretty damn impolite, but of \ncourse, you shouldn't try to merge with dirty data, and you may prefer the \none that is _practically_ more friendly even if it's a bit unsafe. It's a \ntrade-off)\n\n\t\tLinus\n"},{"id":"4041","messageId":"20050527094440.GS24351@cip.informatik.uni-erlangen.de","threadId":"730","inReplyTo":"429698A0.1020008@pobox.com","subject":"Re: resolving merge conflicts?","fromName":"Thomas Glanzmann","fromEmail":"sithglan@stud.uni-erlangen.de","sentAt":"2005-05-27T09:44:40Z","receivedAt":"2005-05-27T09:44:40Z","isPatch":false,"sender":{"key":"sithglan@stud.uni-erlangen.de","avatar":null},"body":"Hello,\n\n> So is there a doc or something that describes how to resolve merge \n> conflicts?\n\n> Presumably I look at .merge_file_*, and make sure that all necessary \n> changes make it into the copy of the file in the working directory... \n> then what?  How to continue the merge?\n\nI attached a perlscript which tries to make it easy for the user. Maybe\nyou can 'pull out the' logic and put it in a shell script or so.\n\ngit pull\n... Conflicts ... run git resolve\ngit resolve\n...\n\n\tThomas\n\n\n#!/usr/bin/env perl\n\nuse strict;\nuse warnings;\nuse IO::Handle;\nuse File::Temp qw/ tempfile tempdir /;\nuse File::Copy;\nuse Cwd;\nuse Getopt::Long;\n\nSTDOUT->autoflush(1);\n\nmy $hostname = gitcmdout('hostname');\nchomp($hostname);\n\nmy $DIFF  = undef;\nmy $PATCH = undef;\n\nif (-x '/opt/csw/bin/gdiff') {\n\t$DIFF='/opt/csw/bin/gdiff';\n\n} else {\n\t$DIFF='diff';\n}\n\nif (-x '/usr/bin/gpatch') {\n\t$PATCH='/usr/bin/gpatch';\n\n} else {\n\t$PATCH='patch';\n}\n\nmy %commands = (\n\t\"add\"        => \\&add,\n\t\"checkout\"   => \\&checkout,\n\t\"ci\"         => \\&ci,\n\t\"clone\"      => \\&clone,\n\t\"commit\"     => \\&commit,\n\t\"diff\"       => \\&diff,\n\t\"treediff\"   => \\&treediff,\n\t\"dirty\"      => \\&dirty,\n\t\"help\"       => \\&usage,\n\t\"init-db\"    => \\&init_db,\n\t\"log\"        => \\&log,\n\t\"orphan\"     => \\&orphan,\n\t\"parent\"     => \\&parent,\n\t\"patch\"      => \\&patch,\n\t\"pull\"       => \\&pull,\n\t\"push\"       => \\&_push,\n\t\"refresh\"    => \\&refresh,\n\t\"revert\"     => \\&revert,\n\t\"rm\"         => \\&rm,\n\t\"setup\"      => \\&setup,\n\t\"status\"     => \\&status,\n\t\"undo\"       => \\&undo,\n\t\"changed\"    => \\&changed,\n\t\"resolve\"    => \\&resolve,\n\t\"changes\"    => \\&changes,\n);\n\nmy %touched = ();\n\nsub\nusage\n{\n\tprint STDERR <<\"__EOF__\";\nUsage: $0 COMMAND [ARG]...\n\nAvailable commands:\n\n__EOF__\n\n\tprint \"\\t\" . join(\"\\n\\t\", sort(keys(%commands))) . \"\\n\\n\";\n\n\treturn 1;\n}\n\nsub\nrefresh\n{\n\t`git-update-cache --refresh`;\n}\n\nsub\nprocess_git_diff_output\n{\n\tmy $str = shift || return (());\n\n\tmy @in  = split(\"\\0\", $str);\n\tmy @out = ();\n\n\twhile (@in) {\n\t\tmy @tmp = split(' ', shift(@in));\n\t\t$tmp[0] =~ s/^://g;\n\t\tpush(@tmp, shift(@in));\n\t\tpush(@out, [@tmp]);\n\t}\n\n\treturn(@out);\n}\n\nsub\ndirty_files\n{\n\trefresh();\n\n\tmy @dirty = ();\n\tmy $str = gitcmdout('git-diff-files', '-z', '-r');\n\n\tforeach (process_git_diff_output($str)) {\n\t\tif ((@{$_})[1] ne '000000') {\n\t\t\tpush(@dirty, @{$_}[5]);\n\t\t}\n\t\t#print \"<\" . join(\"> <\", @{$_}) . \">\\n\";\n\t}\n\n\treturn @dirty;\n}\n\nsub\norphan_files\n{\n\tmy @orphan = gitcmdout('git-ls-files', '--others');\n\tchomp(@orphan);\n\n\tmy $regexp = '^\\.';\n\tif (-f '.git/ignore') {\n\t\tmy @ignore = ();\n\t\tchomp (@ignore = _read_file('.git/ignore'));\n\t\t$regexp = join('|', '^\\.', @ignore);\n\t}\n\n\t@orphan = grep(! /$regexp/, @orphan);\n\n\treturn @orphan;\n}\n\nsub\norphan\n{\n\tforeach (orphan_files()) {\n\t\tprint $_ . \"\\n\";\n\t}\n}\n\nsub\ndirty\n{\n\tforeach (dirty_files()) {\n\t\tprint $_ . \"\\n\";\n\t}\n}\n\nsub\nchanged\n{\n\tforeach my $rev (gitcmdout('git-rev-list', 'HEAD')) {\n\t\tchomp($rev);\n\t\tmy %hash = commit_hash($rev);\n\t\t\n\t\tmy $str = gitcmdout('git-diff-tree', '--root', '-r', '-z', $rev);\n\t\tforeach (process_git_diff_output($str)) {\n\t\t\tmy ($time, $rest) = split(/\\s/, $hash{committer_date});\n\t\t\tpush(@{$touched{@{$_}[5]}}, $time);\n\t\t}\n\t}\n}\n\nsub\nstatus\n{\n\tmy %hash = ();\n\t\n\tforeach (orphan_files()) {\n\t\t$hash{$_} = '?';\n\t}\n\n\tforeach (dirty_files()) {\n\t\t$hash{$_} = 'D';\n\t}\n\n\tmy $str = gitcmdout('git-diff-cache', '-r', '--cached', '-z', 'HEAD');\n\tforeach (process_git_diff_output($str)) {\n\t\tif      (@{$_}[0] eq '000000') {\n\t\t\t$hash{@{$_}[5]} .= '+';\n\n\t\t} elsif (@{$_}[1] eq '000000') {\n\t\t\t$hash{@{$_}[5]} .= '-';\n\n\t\t} else {\n\t\t\t$hash{@{$_}[5]} .= '*';\n\t\t}\n\t}\n\n\tforeach (sort(keys(%hash))) {\n\t\tif ($hash{$_} eq \"D\") {\n\t\t\t$hash{$_} = \"D \";\n\t\t}\n\t\tprintf(\"% 2s %s\\n\", $hash{$_}, $_);\n\t}\n}\n\nsub\nwrite_tree\n{\n\tchomp (my $tree = `git-write-tree`);\n\tif ($?) {\n\t\treturn undef;\n\t} else {\n\t\treturn $tree;\n\t}\n}\n\nsub\nretrieve_unmerged\n{\n\tmy %hash = ();\n\t\n\tforeach my $line (gitcmdout('git-ls-files', '--unmerged')) {\n\t\tchomp($line);\n# 100644 cde27275fad8103084d7ed2d08d246ba4ce6eb9c 1 Makefile\n# 100644 d311a35e5e8a09629ea9e6051a43710c76fa8f6d 2 Makefile\n# 100644 ec2b76bf90fb105b2aaf00a66f44b135046d3002 3 Makefile\n\t\tif ($line =~ /^(\\d{6})\\s([a-z0-9]{40})\\s(\\d)\\s(.+)$/) {\n\t\t\tpush(@{$hash{$4}}, $line);\n\t\t} else {\n\t\t\tdie(\"Can't match: <$line>\\n\");\n\t\t}\n\t}\n\n\treturn %hash;\n}\n\nsub\nprocess_unmerged_file\n{\n\tmy $line;\n\tmy $file;\n\tmy @files = ();\n\twhile($line = shift) {\n\t\tif ($line =~ /^(\\d{6})\\s([a-z0-9]{40})\\s(\\d)\\s(.+)$/) {\n\t\t\tif ($3 eq '1') { $file = \"$4.GCA\"; }\n\t\t\tif ($3 eq '2') { $file = \"$4.LOCAL\"; }\n\t\t\tif ($3 eq '3') { $file = \"$4.REMOTE\"; }\n\n\t\t\tmy $mode = substr($1, 2);\n\n\t\t\tif (-f $file) {\n\t\t\t\tdie(\"Please get rid of $file\\n\");\n\t\t\t}\n\n\t\t\tprint STDERR \"Checking out: $file with permissions $mode\\n\";\n\n\t\t\tsystem(\"git-cat-file blob $2 > $file\");\n\t\t\tchmod oct($mode), $file;\n\t\t\tpush(@files, $file);\n\t\t}\n\t}\n\n\tif (@files == 3) {\n\t\tmy $filename = (@files)[0];\n\t\t$filename =~ s#\\.(GCA|LOCAL|REMOTE)$##;\n\n\t\tprint STDERR <<\"EOF\";\n\nYou got GCA, LOCAL and REMOTE, so I run merge for you and leaving the merges in\n${filename} .\\n\n\nEOF\n\n\t\tunlink($filename);\n\t\tsystem('cp', \"${filename}.LOCAL\", \"${filename}\");\n\n\t\tsystem('merge', \"${filename}\", \"${filename}.GCA\", \"${filename}.REMOTE\");\n\t}\n\n\tprint STDERR <<\"EOF\";\n\nPlease resolve the conflict and add the file using the command 'git ci file'.\nDroping you to a login shell now. Please exit the shell as soon as you resolved\nthe conflict.\n\nEOF\n\n\tsystem($ENV{'SHELL'}, '--login');\n\n\tforeach (@files) {\n\t\tunlink($_);\n\t}\n}\n\nsub\nresolve\n{\n\tif (! -f '.git/RESOLVE') {\n\t\tdie(\"Nothing to resolve\");\n\t}\n\tif ( ! -f '.git/HEAD' && ! -l '.git/HEAD') {\n\t\tdie(\"How the hell I am supposed to resolve without a head?\");\n\t}\n\n\tmy $fh;\n\tmy $head = head();\n\tmy $merge_tree = undef;\n\tmy $pwd        = getcwd;\n\tchomp (my ($remote_head, $url) = _read_file('.git/RESOLVE'));\n\n\tprint \"remote_head => $remote_head\\nurl => $url\\n\";\n\n\tmy %unmerged = retrieve_unmerged();\n\n\tforeach my $file (keys(%unmerged)) {\n\t\tprocess_unmerged_file(@{$unmerged{$file}});\n\t}\n\n\tif (! defined($merge_tree = write_tree())) {\n\t\tdie(\"Still unresolved conflicts. Run git resolve again.\");\n\t}\n\n\topen($fh, \"+>\", undef);\n\tprint $fh \"Manual Merge $url => ${hostname}:${pwd}\\n\";\n\tseek($fh, 0, 0);\n\tchomp($head = gitcmdinout($fh, 'git-commit-tree', $merge_tree, '-p', $head, '-p', $remote_head));\n\tclose $fh;\n\n\thead($head);\n\tunlink('.git/RESOLVE');\n\tcheckout('-f');\n\n\tprint STDERR \"All issues resolved manual: Commited as ${head}.\\n\";\n        print STDERR \"Have a pleasant day.\\n\";\n\treturn;\n}\n\nsub\nmerge\n{\n\tmy $remote_head = shift || die(\"need remote head\");\n\tmy $url         = shift || die(\"need remote url\");\n\tmy $merge_tree  = undef;\n\tmy $message     = \"\";\n\tmy $fh;\n\tmy $pwd         = `pwd`;\n\tchomp($pwd);\n\n\tif ( ! -f '.git/HEAD' && ! -l '.git/HEAD') {\n\t\thead($remote_head);\n\t\treturn;\n\t}\n\tmy $head = head();\n\tchomp (my $merge_base  = gitcmdout('git-merge-base', $head, $remote_head) || die (\"no merge-base\"));\n\n\tprint \"head => $head\\nremote => $remote_head\\nbase => $merge_base\\n\";\n\t\n\tif ($merge_base eq $remote_head) {\n\t\tprint STDERR \"Nothing to pull.\\n\";\n\t\treturn;\n\t}\n\n\tif ($merge_base eq $head) {\n\t\tprint STDERR \"Just bringing head ahead.\\n\";\n\t\thead($remote_head);\n\t\treturn;\n\t}\n\n\tgitcmd('git-read-tree', '-m', tree_id($merge_base), tree_id($head), tree_id($remote_head));\n\tif (! defined($merge_tree = write_tree())) {\n\t\tsystem('git-merge-cache', '-o', 'git-merge-one-file-script', '-a');\n\t\tif (! defined($merge_tree = write_tree())) {\n\t\t\t_write_file('.git/RESOLVE', \"$remote_head\", \"$url\\n\");\n\t\t\tdie(\"Couldn't merge automatically: Call 'git resolve'\");\n\t\t}\n\t\tprint STDERR \"Threewaydiff invloved.\\n\";\n\t\t$message = \"Threeway Merge $url => ${hostname}:${pwd}\\n\";\n\t} else {\n\t\tprint STDERR \"Automatic Merge.\\n\";\n\t\t$message = \"Automatic Merge $url => ${hostname}:${pwd}\\n\";\n\t}\n\n\topen($fh, \"+>\", undef);\n\tprint $fh $message;\n\tseek($fh, 0, 0);\n\tchomp($head = gitcmdinout($fh, 'git-commit-tree', $merge_tree, '-p', $head, '-p', $remote_head));\n\tclose $fh;\n\n\thead($head);\n\treturn;\n}\n\nsub\npatch\n{\n\tmy @files = ();\n\tmy ($fh, $patch);\n\tmy $file = shift || die(\"Need at least on argument.\\n\");\n\tmy $head  = undef;\n\tmy $dir   = undef;\n\n\tif (head()\n\t&& (    dirty_files()\n            || `git-diff-cache -r --cached HEAD`)) {\n\t\tprint STDERR \"Get rid of dirty files / uncommited deltas first.\\n\";\n\t\texit 1;\n\t}\n\n\t($fh, $patch) = tempfile(CLEANUP => 1);\n\n\t`filterdiff -x '*/.*' $file > $patch`;\n\n\t@files = `lsdiff --strip 1 $patch`;\n\n\tchomp(@files);\n\n\t$dir = tempdir(CLEANUP => 1);\n\t$ENV{GIT_INDEX_FILE} = \"$dir/.index\";\n\n\tif ($head = head()) {\n\t\tgitcmd('git-read-tree', $head);\n\t\tforeach my $file (@files) {\n\t\t\tgitcmd('git-checkout-cache', '-q', \"--prefix=$dir/\", $file);\n\t\t}\n\t}\n\n\tmy $pwd = getcwd;\n\n\tsymlink(\"$pwd/.git\", \"$dir/.git\");\n\n\tchdir($dir);\n\n\t# FIXME call in batch modus and check return value --tg 08:30 05-05-21\n\tsystem(\"${PATCH} -p1 < $patch\");\n\n\tforeach my $file (@files) {\n\t\tgitcmd('git-update-cache', '--add', '--remove', $file);\n\t}\n\n\tcommit();\n\n\tchdir($pwd);\n\tdelete($ENV{GIT_INDEX_FILE});\n\tcheckout('-f');\n}\n\nsub\ncheckout\n{\n\tmy $head = head();\n\tgitcmd('git-read-tree', '-m', $head);\n\n\t# FIXME if this fails call it without -m --tg 20:25 05-05-09\n\n\tif (defined($_[0]) && $_[0] eq '-f') {\n\t\tgitcmd('git-checkout-cache', '-u', '-f', '-a');\n\n\t} else {\n\t\tgitcmd('git-checkout-cache', '-u', '-q', '-a');\n\t}\n\n#\tchanged();\n#\n#\tforeach my $file (gitcmdout('git-ls-files')) {\n#\t\tchomp($file);\n#\t\tmy $time = (sort {$b <=> $a} @{$touched{$file}})[0];\n#\t\tutime $time, $time, $file;\n#\t}\n}\n\nsub\ngenerate_url\n{\n\tmy $url = shift;\n\n\tif (! defined($url)) {\n\t\tif ( -f '.git/PARENT' || -l '.git/PARENT') {\n\t\t\tchomp ($url = _read_file('.git/PARENT'));\n\t\t} else {\n\t\t\tdie(\"$0: No URL specified and no parent found: Where to pull from?\\n\");\n\t\t}\n\t} else {\n\t\t$url =~ s#\\/$##;\n\n\t\tif (-d $url &&\n\t\t    ! ($url =~ /\\.git$/)) {\n\t\t\t\t$url .= \"/.git\";\n\t\t}\n\n\t\tif (-d $url &&\n\t\t    ! ($url =~ /^\\//)) {\n\t\t\t# FIXME: use Cwd; my $pwd = getcwd; --tg 21:32 05-05-03\n\t\t\tchomp(my $pwd = `pwd`);\n\t\t\t$url = \"${pwd}/${url}\";\n\t\t}\n\t}\n\n\treturn $url;\n}\n\nsub\npull\n{\n\tmy %options;\n\tlocal @ARGV = @_;\n\tGetOptions(\\%options, 'o', 'l');\n\t@_ = @ARGV;\n\n\tmy $url = generate_url(shift);\n\tmy $remote_head = undef;\n\n\n\tif (head()\n\t&& (! defined($options{'o'}))\n\t&& (    dirty_files()\n            || `git-diff-cache -r --cached HEAD`)) {\n\t\tprint STDERR \"Get rid of dirty files / uncommited deltas first.\\n\";\n\t\texit 1;\n\t}\n\n\tif (defined($options{'l'}) && -d $url) {\n\t\t$ENV{'GIT_ALTERNATE_OBJECT_DIRECTORIES'} = \"${url}/objects\";\n\n\t} else {\n\t\tgitcmd('rsync', '-qa', '--ignore-existing', \"$url/objects/.\", \".git/objects/.\");\n\t}\n\tgitcmd('rsync', '-qL', \"$url/HEAD\", '.git/REMOTE_HEAD');\n\tchomp($remote_head = _read_file('.git/REMOTE_HEAD'));\n\tif (! defined($options{'o'})) {\n\t\tmerge($remote_head, $url);\n\t\tcheckout('-f');\n\t}\n}\n\nsub\nchanges\n{\n\tmy %options;\n\tlocal @ARGV = @_;\n\tGetOptions(\\%options, \"L\", \"R\", \"d\", \"m\", \"n\", \"t=s\");\n\t@_ = @ARGV;\n\n\tmy $git_diff_tree_options = '-s';\n\tif (defined ($options{'d'})) {\n\t\t$git_diff_tree_options = '-p';\n\t}\n\tif (defined ($options{'m'})) {\n\t\t$git_diff_tree_options .= ' -m';\n\t}\n\n\tif (defined ($options{'L'})) {\n\t\tif (! defined ($options{'n'})) {\n\t\t\tpull('-o', '-l', shift);\n\t\t}\n\t\tsystem(\"git-rev-tree HEAD '^REMOTE_HEAD' | sed -e 's/^[0-9]* //' | git-diff-tree --stdin -v $git_diff_tree_options\");\n\n\t} elsif (defined ($options{'R'})) {\n\t\tif (! defined ($options{'n'})) {\n\t\t\tpull('-o', '-l', shift);\n\t\t}\n\t\tsystem(\"git-rev-tree REMOTE_HEAD '^HEAD' | sed -e 's/^[0-9]* //' | git-diff-tree --stdin -v $git_diff_tree_options\");\n\n\t} elsif (defined ($options{'t'})) {\n\t\tsystem(\"git-rev-tree HEAD '^$options{'t'}' | sed -e 's/^[0-9]* //' | git-diff-tree --stdin -v $git_diff_tree_options\");\n\t\n\t} else {\n\t\tsystem(\"git-rev-list HEAD | git-diff-tree --stdin -v $git_diff_tree_options\");\n\t}\n}\n\nsub\n_push\n{\n\tmy $url = generate_url(shift);\n\n\tgitcmd('rsync', '-qL', \"$url/HEAD\", '.git/REMOTE_HEAD');\n\tchomp (my $remote_head = _read_file('.git/REMOTE_HEAD'));\n\tmy $head        = head() || die(\"No local HEAD\\n\");\n\tif ($head eq $remote_head) {\n\t\tprint STDERR \"Nothing to push.\\n\";\n\t\treturn;\n\t}\n\tprint \"head => $head\\nremote => $remote_head\\n\";\n\tmy @revlist     = gitcmdout('git-rev-list', $head);\n\tif (! grep(/^${remote_head}$/, @revlist)) {\n\t\tprint STDERR \"Remote is ahead or unrelated: Need to pull first?\\n\";\n\t\texit 1;\n\t}\n\tgitcmd('rsync', '-qa', '--ignore-existing', \".git/objects/.\", \"$url/objects/.\");\n\tgitcmd('rsync', '-q', '.git/HEAD', \"$url/HEAD\");\n}\n\nsub\nclone\n{\n\tmy %options;\n\n\tGetOptions(\\%options, \"l\");\n\n\tmy $url     = shift(@ARGV);\n\tmy $project = shift(@ARGV);\n\n\tif (! defined($url) || ! defined($project)) {\n\t\tdie(\"Usage: $0 clone <url> <project>\\n\");\n\t}\n\n\t$url = generate_url($url);\n\n\tif (defined($options{l})) {\n\t\tif (-d $url) {\n\t\t\t-d \".git\"              && die(\"$0 clone $project: Don\\'t create repository in a repository.\");\n\t\t\tmkdir($project, 0755)  || die(\"$0 clone $project: mkdir: $!\");\n\t\t\tchdir($project)        || die(\"$0 clone $project: chdir: $!\");\n\t\t\tmkdir('.git', 0755)    || die(\"$0 clone $project: mkdir: $!\");\n\t\t\tsymlink(\"$url/objects\", '.git/objects') || die(\"Can\\'t symlink object database\");\n\n\t\t} else {\n\t\t\tdie(\"Can't symlink from network repositories\\n\");\n\t\t}\n\t} else {\n\t\tsetup($project);\n\t}\n\tparent($url);\n\tpull();\n}\n\nsub\nparent\n{\n\tmy $parent = $_[0];\n\tif (defined($parent)) {\n\t\tif ($parent eq \"-c\") {\n\t\t\tif ( -f \".git/PARENT\" || -l \".git/PARENT\") {\n\t\t\t\tunlink(\".git/PARENT\") || die(\"can't delete .git/PARENT: $!\");\n\t\t\t}\n\t\t\tprint STDERR \"$0 parent: Parent removed\\n\";\n\n\t\t} else {\n\t\t\t$parent = generate_url($parent);\n\n\t\t\t_write_file('.git/PARENT', \"$parent\\n\");\n\t\t}\n\n\t} else {\n\t\tif ( -f \".git/PARENT\" || -l \".git/PARENT\") {\n\t\t\t\n\t\t\tchomp($parent = _read_file('.git/PARENT'));\n\t\t\tprint \"${parent}\\n\";\n\n\t\t} else {\n\t\t\tprint STDERR \"$0 parent: No parent specified\\n\";\n\t\t}\n\t}\n}\n\n# FIXME: Include, Exclude --tg 12:07 05-05-06\n\nsub\ndiff\n{\n\tmy @deltas=();\n\n\tmy %options;\n\tlocal @ARGV = @_;\n\tGetOptions(\\%options, 'r=s', 'f', 'c', 'v');\n\t@_ = @ARGV;\n\n\tmy $dir = tempdir(CLEANUP => 1);\n\n\trefresh();\n\n\tif (defined($options{'r'})) {\n\t\tif($options{'r'} =~ /:/) {\n\t\t\tmy ($first, $second) = split(/:/, $options{'r'});\n\t\t\tsystem(\"git-diff-tree -r -z $first $second | git-diff-helper -z\");\n\n\t\t} else {\n\t\t\tif (defined ($options{'v'})) {\n\t\t\t\tsystem(\"git-diff-tree -s -v $options{'r'}\");\n\t\t\t\tprint \"\\n\";\n\t\t\t}\n\t\t\tsystem(\"git-diff-tree -r -z $options{'r'} | git-diff-helper -z\");\n\t\t}\n\n\t} elsif (defined($options{\"f\"})) {\n\t\tsystem('git-diff-files -r -z | git-diff-helper -z');\n\n\t} elsif (defined($options{\"c\"})) {\n\t\tsystem('git-diff-cache --cached -r -z HEAD | git-diff-helper -z');\n\n\t} else {\n\t\tsystem('git-diff-cache -r -z HEAD | git-diff-helper -z');\n\t}\n\n\tchomp(@deltas);\n}\n\nsub\ntreediff\n{\n\tpull('-o', '-l', shift);\n\tdiff('-r', 'REMOTE_HEAD:HEAD');\n}\n\nsub\ninit_db\n{\n\tgitcmd( 'init-db', @_ );\n}\n\nsub\nsetup\n{\n\tmy $project = shift    || die (\"usage: $0 setup project\");\n\t-d \".git\"              && die (\"$0 setup $project: Don\\'t create repository in a repository.\");\n\tmkdir($project, 0755)  || die (\"$0 setup $project: mkdir: $!\");\n\tchdir($project)        || die (\"$0 setup $project: chdir: $!\");\n\tgitcmd('git-init-db');\n}\n\nsub\nrevert\n{\n\t# TODO handle revert of add/remove --tg 02:32 05-05-06\n\tmy @in    = ();\n\tmy $fh;\n\n\tif (! defined($_[0])) {\n\t\treturn;\n\t}\n\n\tif ($_[0] eq \"-\") {\n\t\t@in = <STDIN>;\n\n\t} else {\n\t\t@in = @_;\n\t}\n\n\tmy $head = head();\n\t($fh, $ENV{GIT_INDEX_FILE}) = tempfile(CLEANUP => 1);\n\tgitcmd('git-read-tree', $head);\n\n\tforeach (@in) {\n\t\tchomp($_);\n\t\t$_ =~ s#^\\.\\/##;\n\n\t\tif (/^\\./) {\n\t\t\tprint STDERR \"Warning: Skipping $_\\n\";\n\t\t\tnext;\n\t\t}\n\n\t\tif (-d $_) {\n\t\t\tnext;\n\t\t}\n\n\t\tgitcmd('git-checkout-cache', '-f', $_);\n\t}\n\n\tdelete($ENV{GIT_INDEX_FILE});\n\n\tforeach (@in) {\n\t\tchomp($_);\n\t\t$_ =~ s#^\\.\\/##;\n\n\t\tif (/^\\./) {\n\t\t\tnext;\n\t\t}\n\n\t\tif (! -f $_) {\n\t\t\tnext;\n\t\t}\n\n\t\tgitcmd('git-update-cache',  $_);\n\t}\n}\n\nsub\nadd\n{\n\tmy @in    = ();\n\n\tif (defined($_[0]) && $_[0] eq \"-\") {\n\t\t@in = <STDIN>;\n\n\t} else {\n\t\t@in = @_;\n\t}\n\n\tforeach (@in) {\n\t\tchomp($_);\n\t\t$_ =~ s#^\\.\\/##;\n\n\t\tif (/^\\./) {\n\t\t\tprint STDERR \"Warning: Skipping $_\\n\";\n\t\t\tnext;\n\t\t}\n\n\t\tif (-d $_) {\n\t\t\tnext;\n\t\t}\n\n\t\tif (! -f $_) {\n\t\t\tprint STDERR \"Warning: Skipping nonexisting file: $_\\n\";\n\t\t\tnext;\n\t\t}\n\n\t\tgitcmd( 'git-update-cache', '--add', '--', $_ );\n\t}\n\n}\n\nsub\nci\n{\n\tmy @in    = ();\n\n\tif (defined($_[0]) && $_[0] eq \"-\") {\n\t\t@in = <STDIN>;\n\n\t} else {\n\t\t@in = @_;\n\t}\n\n\tforeach (@in) {\n\t\tchomp($_);\n\t\t$_ =~ s#^\\.\\/##;\n\n\t\tif (/^\\./) {\n\t\t\tprint STDERR \"Warning: Skipping $_\\n\";\n\t\t\tnext;\n\t\t}\n\n\t\tif (-d $_) {\n\t\t\tnext;\n\t\t}\n\n\t\tif (! -f $_) {\n\t\t\tprint STDERR \"Warning: Skipping nonexisting file: $_\\n\";\n\t\t\tnext;\n\t\t}\n\n\t\tgitcmd( 'git-update-cache', '--', $_ );\n\t}\n\n}\n\nsub\nrm\n{\n\tmy @in    = ();\n\n\tif (defined($_[0]) && $_[0] eq \"-\") {\n\t\t@in = <STDIN>;\n\n\t} else {\n\t\t@in = @_;\n\t}\n\n\tforeach (@in) {\n\t\tchomp($_);\n\t\t$_ =~ s#^\\.\\/##;\n\n\t\tif (/^\\./) {\n\t\t\tprint STDERR \"Warning: Skipping $_\\n\";\n\t\t\tnext;\n\t\t}\n\n\t\tif (-d $_) {\n\t\t\tnext;\n\t\t}\n\n\t\tif (-f $_) {\n\t\t\tunlink($_) || die (\"$0 rm $_: $!\");\n\t\t}\n\n\t\tgitcmd( 'git-update-cache', '--remove', '--' , $_ );\n\t}\n\n}\n\nsub\nhead\n{\n\tmy $head = $_[0];\n\tif (defined($head)) {\n\t\tif ($head eq \"\") {\n\t\t\tif ( -f \".git/HEAD\" || -l \".git/HEAD\") {\n\t\t\t\tunlink(\".git/HEAD\") || die \"failed to delete .git/HEAD: $!\\n\";\n\t\t\t}\n\t\t} else {\n\t\t\tchomp($head);\n\t\t\t_write_file( '.git/HEAD', \"$head\\n\" );\n\t\t}\n\n\t} else {\n\t\tif (-f '.git/HEAD') {\n\t\t\tchomp( $head = _read_file( '.git/HEAD' ) );\n\t\t} else {\n\t\t\t$head = undef;\n\t\t}\n\t}\n\n\treturn $head;\n}\n\nsub\nprint_commit\n{\n\tmy $commit = shift || die(\"need commit\");\n\n\tprint \"commit $commit\\n\";\n\tforeach (gitcmdout('git-cat-file', 'commit', $commit)) {\n\t\tif (/^(author|committer)(.+>\\s)(\\d+)\\s([+-]?\\d{4})$/) {\n\t\t\t# local $ENV{TZ} = $4;\n\t\t\tprint \"$1$2\" . localtime($3) . \"\\n\";\n\t\t} else {\n\t\t\tprint \"$_\\n\";\n\t\t}\n\t}\n\tprint \"\\n\";\n}\n\nsub\ncommit\n{\n\tmy $head = head();\n\tchomp (my $tree = gitcmdout('git-write-tree'));\n\n\tif (dirty_files()) {\n\t\tdie \"$0 commit: Get rid of dirty files first.\\n\";\n\t}\n\n\tif (defined($head)) {\n\t\tmy %hash = commit_hash($head);\n\n\t\tif ($hash{tree} eq $tree) {\n\t\t\tdie \"$0 commit: The commit wouldn't commit anything different.\\n\";\n\t\t}\n\n\t\t$head = gitcmdout('git-commit-tree', $tree, '-p', $head);\n\t} else {\n\t\t$head = gitcmdout('git-commit-tree', $tree);\n\t}\n\n\thead($head);\n}\n\nsub\nlog\n{\n\tmy $head = head() || return ;\n\n\tmy $pid;\n\tif ( ! -p STDIN && ! -p STDOUT ) {\n\t\tmy ( $r, $w );\n\t\tpipe( $r, $w ) || die \"Failed to pipe: $!\";\n\t\tdefined( $pid = fork ) || die \"Failed to fork: $!\";\n\t\tif ( $pid ) { # Parent\n\t\t\t$SIG{INT} = $SIG{TERM} = $SIG{HUP} = sub {\n\t\t\t\tkill 15, $pid;\n\t\t\t\texit 1;\n\t\t\t};\n\t\t\tclose $r;\n\t\t\tclose STDOUT;\n\t\t\topen STDOUT, '>&', $w || die \"Failed to redirect STDOUT: $!\";\n\t\t} else { # pager child\n\t\t\tclose $w;\n\t\t\tclose STDIN;\n\t\t\topen STDIN, '<&', $r || die \"Failed to redirect STDIN: $!\";\n\t\t\tif ( $ENV{PAGER} ) {\n\t\t\t\texec( $ENV{PAGER} );\n\t\t\t} else {\n\t\t\t\texec( 'less', '-r', '-' );\n\t\t\t}\n\t\t}\n\t}\n\n\tforeach (gitcmdout('git-rev-list', $head)) {\n\t\tprint_commit($_);\n\t}\n\n\tif ( $pid ) {\n\t\tclose STDOUT;\n\t\twaitpid($pid, 0);\n\t}\n}\n\nsub\ncommit_hash\n{\n\tmy %hash    = ();\n\tmy $id      = shift || die (\"$0: commit_hash: expect one argument\");\n\tmy $comment = 0;\n\n \tmy @lines = gitcmdout('git-cat-file', 'commit', $id);\n\n\tforeach (@lines) {\n\t\tchomp;\n\n\t\tif ($comment) {\n\t\t\tpush(@{$hash{comment}}, $_);\n\t\t\tnext;\n\t\t}\n\n\t\tif (/^tree\\s(\\w{40})$/) {\n\t\t\t$hash{tree} = $1;\n\t\t\tnext;\n\t\t}\n\n\t\tif (/^parent\\s(\\w{40})$/) {\n\t\t\tpush(@{$hash{parent}}, $1);\n\t\t\tnext;\n\t\t}\n\n\t\tif (/^author\\s(.*)\\s<(.*)>\\s(.*)$/) {\n\t\t\t$hash{author_name}  = $1;\n\t\t\t$hash{author_eMail} = $2;\n\t\t\t$hash{author_date}  = $3;\n\t\t\tnext;\n\t\t}\n\n\t\tif (/^committer\\s(.*)\\s<(.*)>\\s(.*)$/) {\n\t\t\t$hash{committer_name}  = $1;\n\t\t\t$hash{committer_eMail} = $2;\n\t\t\t$hash{committer_date}  = $3;\n\t\t\tnext;\n\t\t}\n\n\t\tif (/^$/) {\n\t\t\t$comment = 1;\n\t\t}\n\t}\n\n\treturn(%hash);\n}\n\nsub\nfirst_parent\n{\n\tmy $this = shift || die (\"$0: parent called without object\");\n\tmy %hash = commit_hash($this);\n\n\tif (! defined(@{$hash{parent}}[0])) {\n\t\treturn \"\";\n\t}\n\n\treturn(@{$hash{parent}}[0]);\n}\n\nsub\nundo\n{\n\tmy $head = head();\n\n\tif (! defined($head)) {\n\t\treturn;\n\t}\n\n\tif (    dirty_files()\n            || `git-diff-cache -r --cached HEAD`) {\n\t\tprint STDERR \"Get rid of dirty files / uncommited deltas first.\\n\";\n\t\texit 1;\n\t}\n\n\tmy $newhead = first_parent($head);\n\n\tprint \"oldhead $head\\nnewhead $newhead\\n\";\n\n\thead($newhead);\n\tcheckout('-f');\n}\n\nsub\nobject_type\n{\n\tmy $id = shift;\n\tdefined $id && $id =~ /^[A-Za-z0-9]{40}$/ ||\n\t\tdie \"Invalid sha1 id '$id'\";\n\tmy $type = gitcmdout('git-cat-file', '-t', $id );\n\tchomp $type;\n\treturn $type;\n}\n\nsub\ntree_id\n{\n\tmy $id = shift;\n\t( $id ) = grep { defined }\n\t\tmap { /^tree ([A-Za-z0-9]{40})$/ ? $1 : undef }\n\t\t\tgitcmdout( 'git-cat-file', 'commit', $id ) or\n\t\tdie \"Unable to find tree id for commit id $id\";\n\tobject_type( $id ) eq 'tree' ||\n\t\tdie \"tree id from commit is not a tree object!\";\n\treturn $id;\n}\n\nsub\nparent_id\n{\n\tmy $id = shift;\n\tmy ( $pid ) = grep { defined }\n\t\tmap { /^parent ([A-Za-z0-9]{40})$/ ? $1 : undef }\n\t\t\tgitcmdout( 'git-cat-file', 'commit', $id ) or\n\t\tdie \"Unable to determine parent commit of commit $id\";\n\treturn $pid;\n}\n\n# int\n# main(int argc, char **argv)\n# {\n\nif (defined($ARGV[0]) && defined($commands{$ARGV[0]})) {\n\tmy $string = shift;\n\tif ( ! -d \".git\"\n\t&&   ! ($string eq \"setup\"\n             || $string eq \"init-db\"\n             || $string eq \"clone\")) {\n\t\tdie(\"$0 $string: Not in a git BASE directory\");\n\t}\n\t$commands{$string}->(@ARGV);\n\n} else {\n\tif (defined($ARGV[0])) {\n\t\tprint STDERR \"No such command: $ARGV[0]\\n\\n\";\n\t}\t\n\tusage();\n}\n\n# }\n\n{\n\tmy %gitcmd;\n\n\tsub\n\tgitcmdpath\n\t{\n\t\tmy $cmd = shift;\n\t\tunless ( defined $gitcmd{$cmd} ) {\n\t\t\tlocal $/ = \"\\n\";\n\t\t\tchomp( $gitcmd{$cmd} = `which $cmd` );\n\t\t\treturn undef if $gitcmd{$cmd} eq '';\n\t\t}\n\t\treturn $gitcmd{$cmd};\n\t}\n\n\tsub\n\tgitcmd\n\t{\n\t\tmy $cmd = shift;\n\t\tgitcmdpath( $cmd ) || die \"command '$cmd' not found\";\n\t\tmy $r = system( $gitcmd{$cmd}, @_ );\n\t\tdie \"$cmd failed: \" . _gitcmderrmsg( $cmd )\n\t\t\tif $r != 0;\n\t\treturn 1;\n\t}\n\n\tsub\n\tgitcmdinout\n\t{\n\t\tmy $infh = shift;\n\t\tmy $cmd  = shift;\n\t\tgitcmdpath( $cmd ) || die \"command '$cmd' not found\";\n\t\tmy ( $r, $w );\n\t\tpipe( $r, $w ) || die \"Failed to pipe: $!\";\n\t\tmy $pid = fork();\n\t\tdie \"Failed to fork: $!\" unless defined $pid;\n\t\tif ( $pid ) {\n\t\t\tclose $w;\n\t\t\tlocal $/;\n\t\t\tlocal $_ = <$r>;\n\t\t\tclose $r;\n\t\t\tmy $kid = waitpid( $pid, 0 );\n\t\t\tdie \"Hmm, auto reaping in place?\" if $kid == -1;\n\t\t\tdie \"$cmd failed: \" . _gitcmderrmsg( $cmd )\n\t\t\t\tif $? & 127 || $? >> 8 != 0;\n\t\t\tif ( wantarray ) {\n\t\t\t\treturn split( \"\\n\", $_ );\n\t\t\t} else {\n\t\t\t\treturn $_;\n\t\t\t}\n\t\t} else {\n\t\t\tclose $r;\n\t\t\tclose STDOUT;\n\t\t\tclose STDIN;\n\t\t\topen STDIN, '<&', $infh || die \"Failed to rediret STDIN\";\n\t\t\topen STDOUT, '>&', $w || die \"Failed to redirect STDOUT\";\n\t\t\texec( $gitcmd{$cmd}, @_ );\n\t\t}\n\t}\n\n\tsub\n\tgitcmdout\n\t{\n\t\tmy $cmd = shift;\n\t\tgitcmdpath( $cmd ) || die \"command '$cmd' not found\";\n\t\tmy ( $r, $w );\n\t\tpipe( $r, $w ) || die \"Failed to pipe: $!\";\n\t\tmy $pid = fork();\n\t\tdie \"Failed to fork: $!\" unless defined $pid;\n\t\tif ( $pid ) {\n\t\t\tclose $w;\n\t\t\tlocal $/;\n\t\t\tmy $ret = <$r>;\n\t\t\tclose $r;\n\n\t\t\tmy $kid = waitpid( $pid, 0 );\n\t\t\tdie \"Hmm, auto reaping in place?\" if $kid == -1;\n\t\t\tdie \"$cmd failed: \" . _gitcmderrmsg( $cmd )\n\t\t\t\tif $? & 127 || $? >> 8 != 0;\n\n\t\t\tif (wantarray) {\n\t\t\t\treturn split(\"\\n\", $ret);\n\t\t\t} else {\n\t\t\t\treturn $ret;\n\t\t\t}\n\t\t} else {\n\t\t\tclose $r;\n\t\t\tclose STDOUT;\n\t\t\topen STDOUT, '>&', $w || die \"Failed to redirect STDOUT\";\n\t\t\texec( $gitcmd{$cmd}, @_ );\n\t\t}\n\t}\n\n\tsub\n\t_gitcmderrmsg\n\t{\n\t\tmy $cmd = shift;\n\t\tmy $e;\n\t\tif ( $? == -1 ) {\n\t\t\t$e = \"failed to execute $gitcmd{$cmd}: $!\";\n\t\t} elsif ( $? & 127 ) {\n\t\t\t$e = sprintf( 'child die from signal %d', ( $? & 127 ) );\n\t\t\t$e .= ' (with coredump)' if $? & 128;\n\t\t} else {\n\t\t\t$e = sprintf( 'child exit value: %d', $? >> 8 );\n\t\t}\n\t\treturn $e;\n\t}\n}\n\nsub\n_recur_mkdir\n{\n\tmy $dir = shift;\n\tmy @dir = split( /\\//, $dir );\n\tmy $path = '';\n\twhile ( @dir ) {\n\t\t$path .= '/' . shift @dir;\n\t\t( -d $path ) || mkdir( $path ) ||\n\t\t\tdie \"Failed to mkdir $path: $!\";\n\t}\n}\n\nsub\n_read_file\n{\n\tmy $file = shift;\n\tmy $fh;\n\topen $fh, '<', $file || die \"failed to read $file: $!\\n\";\n\tif ( wantarray ) {\n\t\tmy @r = <$fh>;\n\t\tclose $fh || die \"failed to close $file: $!\\n\";\n\t\treturn @r;\n\t} else {\n\t\tlocal $/;\n\t\tmy $r = <$fh>;\n\t\tclose $fh || die \"failed to close $file: $!\\n\";\n\t\treturn $r;\n\t}\n}\n\nsub\n_write_file\n{\n\tmy $file = shift;\n\tmy $fh;\n\topen $fh, '>', $file || die \"failed to write $file: $!\\n\";\n\tif ( @_ ) {\n\t\tprint $fh join( $/, @_ );\n\t}\n\tclose $fh            || die \"failed to close $file: $!\\n\";\n\treturn 1;\n}\n\n# vim:set noexpandtab:\n"}]}