From 133f081057a99957d6d34aa1c4fec61e5c5d22d8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Feb 2007 17:30:58 -0800 Subject: [PATCH 01/22] t9200: Work around HFS+ issues. We at least know that the test as written has a problem in an environment where "touch '$p'; ls | fgrep '$p'" fails, and have a clear understand why it fails. This tests if the filesystem has that particular issue we know "git add" has a problem with, and skips the test in such an environment. This way, we might catch issues "git add" might have in other environments. Signed-off-by: Junio C Hamano --- t/t9200-git-cvsexportcommit.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index c443f32fce..4efa0c926c 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -169,6 +169,16 @@ test_expect_success \ test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/" )' +# Some filesystems mangle pathnames with UTF-8 characters -- +# check and skip +if 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/å/ä/ö" && + mkdir -p "tst/$p" && + date >"tst/$p/day" && + found=$(find tst -type f -print) && + test "z$found" = "ztst/$p/day" && + rm -fr tst +then + # This test contains UTF-8 characters test_expect_success \ 'File with non-ascii file name' \ @@ -184,6 +194,10 @@ test_expect_success \ 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/" )' +fi + +rm -fr tst + test_expect_success \ 'Mismatching patch should fail' \ 'date >>"E/newfile5.txt" && From 69057cf39f87ecfe7446b14aa7df87ccf19b1151 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Mon, 5 Feb 2007 19:58:47 +0000 Subject: [PATCH 02/22] git-for-each-ref doesn't return "the bit after $GIT_DIR/refs" The documentation for git-for-each-ref said that the refname variable would return "the part after $GIT_DIR/refs/", which isn't true. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index da52eba7b3..f49b0d944c 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -66,7 +66,7 @@ keys. For all objects, the following names can be used: refname:: - The name of the ref (the part after $GIT_DIR/refs/). + The name of the ref (the part after $GIT_DIR/). objecttype:: The type of the object (`blob`, `tree`, `commit`, `tag`). From ea81fcc5762c56b13d1ad04d62b161d782c591f1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:21:06 -0500 Subject: [PATCH 03/22] Show an example of deleting commits with git-rebase. This particular use of git-rebase to remove a single commit or a range of commits from the history of a branch recently came up on the mailing list. Documenting the example should help other users arrive at the same solution on their own. It also was not obvious to the newcomer that git-rebase is able to accept any commit for --onto and . We should at least minimally document this, as much of the language in git-rebase's manpage refers to 'branch' rather than 'committish'. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0cb9e1f10a..977f661b9d 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -114,6 +114,27 @@ would result in: This is useful when topicB does not depend on topicA. +A range of commits could also be removed with rebase. If we have +the following situation: + +------------ + E---F---G---H---I---J topicA +------------ + +then the command + + git-rebase --onto topicA~5 topicA~2 topicA + +would result in the removal of commits F and G: + +------------ + E---H'---I'---J' topicA +------------ + +This is useful if F and G were flawed in some way, or should not be +part of topicA. Note that the argument to --onto and the +parameter can be any valid commit-ish. + In case of conflict, git-rebase will stop at the first problematic commit and leave conflict markers in the tree. You can use git diff to locate the markers (<<<<<<) and make edits to resolve the conflict. For each @@ -141,10 +162,12 @@ OPTIONS :: Starting point at which to create the new commits. If the --onto option is not specified, the starting point is - . + . May be any valid commit, and not just an + existing branch name. :: - Upstream branch to compare against. + Upstream branch to compare against. May be any valid commit, + not just an existing branch name. :: Working branch; defaults to HEAD. From d8a9fea5ea44a4068cc95de9368798ae3cee74ee Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:22 -0500 Subject: [PATCH 04/22] bash: Support completion on git-cherry. I just realized I did not support ref name completion for git-cherry. This tool is just too useful to contributors who submit patches upstream by email; completion support for it is very handy. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 466cc32f4c..b434332bf7 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -415,6 +415,11 @@ _git_checkout () __gitcomp "$(__git_refs)" } +_git_cherry () +{ + __gitcomp "$(__git_refs)" +} + _git_cherry_pick () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -867,6 +872,7 @@ _git () apply) _git_apply ;; branch) _git_branch ;; checkout) _git_checkout ;; + cherry) _git_cherry ;; cherry-pick) _git_cherry_pick ;; commit) _git_commit ;; config) _git_config ;; @@ -909,6 +915,7 @@ complete -o default -o nospace -F _git_am git-am complete -o default -o nospace -F _git_apply git-apply complete -o default -o nospace -F _git_branch git-branch complete -o default -o nospace -F _git_checkout git-checkout +complete -o default -o nospace -F _git_cherry git-cherry complete -o default -o nospace -F _git_cherry_pick git-cherry-pick complete -o default -o nospace -F _git_commit git-commit complete -o default -o nospace -F _git_diff git-diff @@ -939,6 +946,7 @@ complete -o default -o nospace -F _git_add git-add.exe complete -o default -o nospace -F _git_apply git-apply.exe complete -o default -o nospace -F _git git.exe complete -o default -o nospace -F _git_branch git-branch.exe +complete -o default -o nospace -F _git_cherry git-cherry.exe complete -o default -o nospace -F _git_diff git-diff.exe complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe complete -o default -o nospace -F _git_format_patch git-format-patch.exe From 983591c31e2529fbba8dd2d69dce5ab446584921 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:24 -0500 Subject: [PATCH 05/22] bash: Hide diff-stages from completion. Apparently nobody really makes use of git-diff-stages, as nobody has complained that it is not supported by the git-diff frontend. Since its likely this will go away in the future, we should not offer it as a possible subcommand completion. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index b434332bf7..c0dae5484d 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -269,6 +269,7 @@ __git_commands () cvsimport) : import;; cvsserver) : daemon;; daemon) : daemon;; + diff-stages) : nobody uses it;; fsck-objects) : plumbing;; fetch-pack) : plumbing;; fmt-merge-msg) : plumbing;; From b26c87488fa89e444972c486326eecca5b2132c7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:28 -0500 Subject: [PATCH 06/22] bash: Offer --prune completion for git-gc. I'm lazy. I don't want to type out --prune if bash can do it for me with --. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index c0dae5484d..324bfbd8bb 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -508,6 +508,18 @@ _git_format_patch () __git_complete_revlist } +_git_gc () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp "--prune" + return + ;; + esac + COMPREPLY=() +} + _git_ls_remote () { __gitcomp "$(__git_remotes)" @@ -881,6 +893,7 @@ _git () diff-tree) _git_diff_tree ;; fetch) _git_fetch ;; format-patch) _git_format_patch ;; + gc) _git_gc ;; log) _git_log ;; ls-remote) _git_ls_remote ;; ls-tree) _git_ls_tree ;; @@ -923,6 +936,7 @@ complete -o default -o nospace -F _git_diff git-diff complete -o default -o nospace -F _git_diff_tree git-diff-tree complete -o default -o nospace -F _git_fetch git-fetch complete -o default -o nospace -F _git_format_patch git-format-patch +complete -o default -o nospace -F _git_gc git-gc complete -o default -o nospace -F _git_log git-log complete -o default -o nospace -F _git_ls_remote git-ls-remote complete -o default -o nospace -F _git_ls_tree git-ls-tree From e459415c9c3c8e855998dd0247f23d32a0dca4ff Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:30 -0500 Subject: [PATCH 07/22] bash: Hide git-resolve, its deprecated. Don't offer resolve as a possible subcommand completion. If you read the top of the script, there is a big warning about how it will go away soon in the near future. People should not be using it. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 324bfbd8bb..089a7b0571 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -297,6 +297,7 @@ __git_commands () reflog) : plumbing;; repo-config) : plumbing;; rerere) : plumbing;; + resolve) : dead dont use;; rev-list) : plumbing;; rev-parse) : plumbing;; runstatus) : plumbing;; From 1b71eb35ddb20057a266b05258de24d350ed5ba5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:32 -0500 Subject: [PATCH 08/22] bash: Support --add completion to git-config. We've recently added --add as an argument to git-config, but I missed putting it into the earlier round of git-config updates within the bash completion. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 089a7b0571..f9b827a617 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -719,7 +719,7 @@ _git_config () __gitcomp " --global --list --replace-all --get --get-all --get-regexp - --unset --unset-all + --add --unset --unset-all " return ;; From b2e69f6299b5a46840600176679b94843cf63a8d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 5 Feb 2007 15:44:37 -0500 Subject: [PATCH 09/22] bash: Support git-bisect and its subcommands. We now offer completion support for git-bisect's subcommands, as well as ref name completion on the good/bad/reset subcommands. This should make interacting with git-bisect slightly easier on the fingers. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index f9b827a617..430e6367a7 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -407,6 +407,35 @@ _git_add () COMPREPLY=() } +_git_bisect () +{ + local i c=1 command + while [ $c -lt $COMP_CWORD ]; do + i="${COMP_WORDS[c]}" + case "$i" in + start|bad|good|reset|visualize|replay|log) + command="$i" + break + ;; + esac + c=$((++c)) + done + + if [ $c -eq $COMP_CWORD -a -z "$command" ]; then + __gitcomp "start bad good reset visualize replay log" + return + fi + + case "$command" in + bad|good|reset) + __gitcomp "$(__git_refs)" + ;; + *) + COMPREPLY=() + ;; + esac +} + _git_branch () { __gitcomp "$(__git_refs)" @@ -884,6 +913,7 @@ _git () am) _git_am ;; add) _git_add ;; apply) _git_apply ;; + bisect) _git_bisect ;; branch) _git_branch ;; checkout) _git_checkout ;; cherry) _git_cherry ;; @@ -928,6 +958,7 @@ complete -o default -o nospace -F _git git complete -o default -o nospace -F _gitk gitk complete -o default -o nospace -F _git_am git-am complete -o default -o nospace -F _git_apply git-apply +complete -o default -o nospace -F _git_bisect git-bisect complete -o default -o nospace -F _git_branch git-branch complete -o default -o nospace -F _git_checkout git-checkout complete -o default -o nospace -F _git_cherry git-cherry From 041794188ff4f7a0b3ada8058d174a0c114da78b Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI Date: Tue, 30 Jan 2007 19:23:38 +0900 Subject: [PATCH 10/22] gitweb: Convert project name to UTF-8 If the repository directory name is in non-ascii, $project needs to be converted from perl internal to utf-8 because it will be used as title, page path, and snapshot filename. use to_utf8() to do the conversion. Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a2076a680c..d374110070 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1690,7 +1690,7 @@ sub git_header_html { my $title = "$site_name"; if (defined $project) { - $title .= " - $project"; + $title .= " - " . to_utf8($project); if (defined $action) { $title .= "/$action"; if (defined $file_name) { @@ -1963,7 +1963,7 @@ sub git_print_page_path { print "
"; print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), - -title => 'tree root'}, "[$project]"); + -title => 'tree root'}, to_utf8("[$project]"); print " / "; if (defined $name) { my @dirname = split '/', $name; @@ -3610,7 +3610,7 @@ sub git_snapshot { $hash = git_get_head_hash($project); } - my $filename = basename($project) . "-$hash.tar.$suffix"; + my $filename = to_utf8(basename($project)) . "-$hash.tar.$suffix"; print $cgi->header( -type => "application/$ctype", From 3fb624521e059c6c3caef470a9ff03f72b86036c Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Tue, 30 Jan 2007 13:26:50 +0530 Subject: [PATCH 11/22] blameview: Support browsable functionality to blameview. Double clicking on the row execs a new blameview with commit hash as argument. Signed-off-by: Aneesh Kumar K.V Signed-off-by: Junio C Hamano --- contrib/blameview/blameview.perl | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/contrib/blameview/blameview.perl b/contrib/blameview/blameview.perl index 5e9a67c123..807d01fe3d 100755 --- a/contrib/blameview/blameview.perl +++ b/contrib/blameview/blameview.perl @@ -3,7 +3,17 @@ use Gtk2 -init; use Gtk2::SimpleList; -my $fn = shift or die "require filename to blame"; +my $hash; +my $fn; +if ( @ARGV == 1 ) { + $hash = "HEAD"; + $fn = shift; +} elsif ( @ARGV == 2 ) { + $hash = shift; + $fn = shift; +} else { + die "Usage blameview [] "; +} Gtk2::Rc->parse_string(<<'EOS'); style "treeview_style" @@ -27,17 +37,24 @@ $scrolled_window->add($fileview); $fileview->get_column(0)->set_spacing(0); $fileview->set_size_request(1024, 768); $fileview->set_rules_hint(1); +$fileview->signal_connect (row_activated => sub { + my ($sl, $path, $column) = @_; + my $row_ref = $sl->get_row_data_from_path ($path); + system("blameview @$row_ref[0] $fn"); + # $row_ref is now an array ref to the double-clicked row's data. + }); my $fh; -open($fh, '-|', "git cat-file blob HEAD:$fn") +open($fh, '-|', "git cat-file blob $hash:$fn") or die "unable to open $fn: $!"; + while(<$fh>) { chomp; $fileview->{data}->[$.] = ['HEAD', '?', "$fn:$.", $_]; } my $blame; -open($blame, '-|', qw(git blame --incremental --), $fn) +open($blame, '-|', qw(git blame --incremental --), $fn, $hash) or die "cannot start git-blame $fn"; Glib::IO->add_watch(fileno($blame), 'in', \&read_blame_line); From 98d47d4ccf76725e7833c1bbda1da82f7648925f Mon Sep 17 00:00:00 2001 From: Stelian Pop Date: Fri, 12 Jan 2007 22:57:03 +0100 Subject: [PATCH 12/22] Add hg-to-git conversion utility. hg-to-git.py is able to convert a Mercurial repository into a git one, and preserves the branches in the process (unlike tailor) hg-to-git.py can probably be greatly improved (it's a rather crude combination of shell and python) but it does already work quite well for me. Features: - supports incremental conversion (for keeping a git repo in sync with a hg one) - supports hg branches - converts hg tags Signed-off-by: Stelian Pop Signed-off-by: Junio C Hamano --- Documentation/git-tools.txt | 8 ++ contrib/hg-to-git/hg-to-git.py | 233 ++++++++++++++++++++++++++++++++ contrib/hg-to-git/hg-to-git.txt | 21 +++ 3 files changed, 262 insertions(+) create mode 100755 contrib/hg-to-git/hg-to-git.py create mode 100644 contrib/hg-to-git/hg-to-git.txt diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt index 6b407f9adf..10653ff898 100644 --- a/Documentation/git-tools.txt +++ b/Documentation/git-tools.txt @@ -86,6 +86,14 @@ Foreign SCM interface series in git back and forth. + - *hg-to-git* (contrib/) + + hg-to-git converts a Mercurial repository into a git one, and + preserves the full branch history in the process. hg-to-git can + also be used in an incremental way to keep the git repository + in sync with the master Mercurial repository. + + Others ------ diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py new file mode 100755 index 0000000000..37337ff01f --- /dev/null +++ b/contrib/hg-to-git/hg-to-git.py @@ -0,0 +1,233 @@ +#! /usr/bin/python + +""" hg-to-svn.py - A Mercurial to GIT converter + + Copyright (C)2007 Stelian Pop + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os, os.path, sys +import tempfile, popen2, pickle, getopt +import re + +# Maps hg version -> git version +hgvers = {} +# List of children for each hg revision +hgchildren = {} +# Current branch for each hg revision +hgbranch = {} + +#------------------------------------------------------------------------------ + +def usage(): + + print """\ +%s: [OPTIONS] + +options: + -s, --gitstate=FILE: name of the state to be saved/read + for incrementals + +required: + hgprj: name of the HG project to import (directory) +""" % sys.argv[0] + +#------------------------------------------------------------------------------ + +def getgitenv(user, date): + env = '' + elems = re.compile('(.*?)\s+<(.*)>').match(user) + if elems: + env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1) + env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1) + env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2) + env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2) + else: + env += 'export GIT_AUTHOR_NAME="%s" ;' % user + env += 'export GIT_COMMITER_NAME="%s" ;' % user + env += 'export GIT_AUTHOR_EMAIL= ;' + env += 'export GIT_COMMITER_EMAIL= ;' + + env += 'export GIT_AUTHOR_DATE="%s" ;' % date + env += 'export GIT_COMMITTER_DATE="%s" ;' % date + return env + +#------------------------------------------------------------------------------ + +state = '' + +try: + opts, args = getopt.getopt(sys.argv[1:], 's:t:', ['gitstate=', 'tempdir=']) + for o, a in opts: + if o in ('-s', '--gitstate'): + state = a + state = os.path.abspath(state) + + if len(args) != 1: + raise('params') +except: + usage() + sys.exit(1) + +hgprj = args[0] +os.chdir(hgprj) + +if state: + if os.path.exists(state): + print 'State does exist, reading' + f = open(state, 'r') + hgvers = pickle.load(f) + else: + print 'State does not exist, first run' + +tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip() +print 'tip is', tip + +# Calculate the branches +print 'analysing the branches...' +hgchildren["0"] = () +hgbranch["0"] = "master" +for cset in range(1, int(tip) + 1): + hgchildren[str(cset)] = () + prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines() + if len(prnts) > 0: + parent = prnts[0].strip() + else: + parent = str(cset - 1) + hgchildren[parent] += ( str(cset), ) + if len(prnts) > 1: + mparent = prnts[1].strip() + hgchildren[mparent] += ( str(cset), ) + else: + mparent = None + + if mparent: + # For merge changesets, take either one, preferably the 'master' branch + if hgbranch[mparent] == 'master': + hgbranch[str(cset)] = 'master' + else: + hgbranch[str(cset)] = hgbranch[parent] + else: + # Normal changesets + # For first children, take the parent branch, for the others create a new branch + if hgchildren[parent][0] == str(cset): + hgbranch[str(cset)] = hgbranch[parent] + else: + hgbranch[str(cset)] = "branch-" + str(cset) + +if not hgvers.has_key("0"): + print 'creating repository' + os.system('git-init-db') + +# loop through every hg changeset +for cset in range(int(tip) + 1): + + # incremental, already seen + if hgvers.has_key(str(cset)): + continue + + # get info + prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines() + if len(prnts) > 0: + parent = prnts[0].strip() + else: + parent = str(cset - 1) + if len(prnts) > 1: + mparent = prnts[1].strip() + else: + mparent = None + + (fdcomment, filecomment) = tempfile.mkstemp() + csetcomment = os.popen('hg log -r %d -v | grep -v ^changeset: | grep -v ^parent: | grep -v ^user: | grep -v ^date | grep -v ^files: | grep -v ^description: | grep -v ^tag:' % cset).read().strip() + os.write(fdcomment, csetcomment) + os.close(fdcomment) + + date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip() + + tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip() + + user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip() + + print '-----------------------------------------' + print 'cset:', cset + print 'branch:', hgbranch[str(cset)] + print 'user:', user + print 'date:', date + print 'comment:', csetcomment + print 'parent:', parent + if mparent: + print 'mparent:', mparent + if tag: + print 'tag:', tag + print '-----------------------------------------' + + # checkout the parent if necessary + if cset != 0: + if hgbranch[str(cset)] == "branch-" + str(cset): + print 'creating new branch', hgbranch[str(cset)] + os.system('git-checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent])) + else: + print 'checking out branch', hgbranch[str(cset)] + os.system('git-checkout %s' % hgbranch[str(cset)]) + + # merge + if mparent: + if hgbranch[parent] == hgbranch[str(cset)]: + otherbranch = hgbranch[mparent] + else: + otherbranch = hgbranch[parent] + print 'merging', otherbranch, 'into', hgbranch[str(cset)] + os.system(getgitenv(user, date) + 'git-merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch)) + + # remove everything except .git and .hg directories + os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf') + + # repopulate with checkouted files + os.system('hg update -C %d' % cset) + + # add new files + os.system('git-ls-files -x .hg --others | git-update-index --add --stdin') + # delete removed files + os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin') + + # commit + os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment) + os.unlink(filecomment) + + # tag + if tag and tag != 'tip': + os.system(getgitenv(user, date) + 'git-tag %s' % tag) + + # delete branch if not used anymore... + if mparent and len(hgchildren[str(cset)]): + print "Deleting unused branch:", otherbranch + os.system('git-branch -d %s' % otherbranch) + + # retrieve and record the version + vvv = os.popen('git-show | head -1').read() + vvv = vvv[vvv.index(' ') + 1 : ].strip() + print 'record', cset, '->', vvv + hgvers[str(cset)] = vvv + +os.system('git-repack -a -d') + +# write the state for incrementals +if state: + print 'Writing state' + f = open(state, 'w') + pickle.dump(hgvers, f) + +# vim: et ts=8 sw=4 sts=4 diff --git a/contrib/hg-to-git/hg-to-git.txt b/contrib/hg-to-git/hg-to-git.txt new file mode 100644 index 0000000000..91f8fe6410 --- /dev/null +++ b/contrib/hg-to-git/hg-to-git.txt @@ -0,0 +1,21 @@ +hg-to-git.py is able to convert a Mercurial repository into a git one, +and preserves the branches in the process (unlike tailor) + +hg-to-git.py can probably be greatly improved (it's a rather crude +combination of shell and python) but it does already work quite well for +me. Features: + - supports incremental conversion + (for keeping a git repo in sync with a hg one) + - supports hg branches + - converts hg tags + +Note that the git repository will be created 'in place' (at the same +location as the source hg repo). You will have to manually remove the +'.hg' directory after the conversion. + +Also note that the incremental conversion uses 'simple' hg changesets +identifiers (ordinals, as opposed to SHA-1 ids), and since these ids +are not stable across different repositories the hg-to-git.py state file +is forever tied to one hg repository. + +Stelian Pop From 6d9ba67b0fc2f18c1d7a65514edbdcdf86429c1c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Jan 2007 13:30:20 +0100 Subject: [PATCH 13/22] Commands requiring a work tree must not run in GIT_DIR This patch helps when you accidentally run something like git-clean in the git directory instead of the work tree. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-ls-files.c | 10 +++++++++- builtin-rev-parse.c | 5 +++++ git-sh-setup.sh | 3 ++- git.c | 5 +++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 21c2a6e2d9..ac89eb2f77 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -323,7 +323,7 @@ static const char ls_files_usage[] = int cmd_ls_files(int argc, const char **argv, const char *prefix) { int i; - int exc_given = 0; + int exc_given = 0, require_work_tree = 0; struct dir_struct dir; memset(&dir, 0, sizeof(dir)); @@ -363,14 +363,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) { show_modified = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) { show_others = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { dir.show_ignored = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { @@ -379,6 +382,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) { show_killed = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "--directory")) { @@ -447,6 +451,10 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) break; } + if (require_work_tree && + (is_bare_repository() || is_inside_git_dir())) + die("This operation must be run in a work tree"); + pathspec = get_pathspec(prefix, argv + i); /* Verify that the pathspec matches the prefix */ diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 3b716fba13..d53deaa369 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -347,6 +347,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) printf("%s/.git\n", cwd); continue; } + if (!strcmp(arg, "--is-inside-git-dir")) { + printf("%s\n", is_inside_git_dir() ? "true" + : "false"); + continue; + } if (!strncmp(arg, "--since=", 8)) { show_datestring("--max-age=", arg+8); continue; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index b4aa4b2f4e..f24c7f2d23 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -48,7 +48,8 @@ cd_to_toplevel () { } require_work_tree () { - test $(is_bare_repository) = false || + test $(is_bare_repository) = false && + test $(git-rev-parse --is-inside-git-dir) = false || die "fatal: $0 cannot be used without a working tree." } diff --git a/git.c b/git.c index fb03a547de..82a8357272 100644 --- a/git.c +++ b/git.c @@ -299,8 +299,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); - if ((p->option & NOT_BARE) && is_bare_repository()) - die("%s cannot be used in a bare git directory", cmd); + if ((p->option & NOT_BARE) && + (is_bare_repository() || is_inside_git_dir())) + die("%s must be run in a work tree", cmd); trace_argv_printf(argv, argc, "trace: built-in: git"); exit(p->fn(argc, argv, prefix)); From 4c55068683ac2e9765ba6e112d2227e50fefae87 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Feb 2007 14:03:27 -0800 Subject: [PATCH 14/22] bisect: it needs to be done in a working tree. Signed-off-by: Junio C Hamano --- git-bisect.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-bisect.sh b/git-bisect.sh index e8d3418988..24160ec51c 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -11,6 +11,7 @@ git bisect replay replay bisection log git bisect log show bisect log.' . git-sh-setup +require_work_tree sq() { @@PERL@@ -e ' From ca28370a3567c14841c594b89fd62449abc166e6 Mon Sep 17 00:00:00 2001 From: Simon 'corecode' Schubert Date: Thu, 1 Feb 2007 11:43:39 +0100 Subject: [PATCH 15/22] Allow forcing of a parent commit, even if the parent is not a direct one. This can be used to compress multiple changesets into one, for example like git cvsexportcommit -P cvshead mybranch without having to do so in git first. Signed-off-by: Simon 'corecode' Schubert Signed-off-by: Junio C Hamano --- Documentation/git-cvsexportcommit.txt | 5 ++++- git-cvsexportcommit.perl | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 347cbcec35..27d531b888 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -8,7 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout SYNOPSIS -------- -'git-cvsexportcommit' [-h] [-v] [-c] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID +'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID DESCRIPTION @@ -46,6 +46,9 @@ OPTIONS -f:: Force the merge even if the files are not up to date. +-P:: + Force the parent commit, even if it is not a direct parent. + -m:: Prepend the commit message with the provided prefix. Useful for patch series and the like. diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 4863c91fe3..870554eade 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -15,9 +15,9 @@ unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ die "GIT_DIR is not defined or is unreadable"; } -our ($opt_h, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m ); +our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m ); -getopts('hpvcfam:'); +getopts('hPpvcfam:'); $opt_h && usage(); @@ -89,7 +89,7 @@ if ($parent) { last; }; # found it } - die "Did not find $parent in the parents for this commit!" if !$found; + die "Did not find $parent in the parents for this commit!" if !$found and !$opt_P; } else { # we don't have a parent from the cmdline... if (@parents == 1) { # it's safe to get it from the commit $parent = $parents[0]; From 28389d45fb2161d4dcdc1bbfabbcc2fb135914c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20K=C3=A5gedal?= Date: Mon, 5 Feb 2007 14:22:28 -0800 Subject: [PATCH 16/22] git-blame: an Emacs minor mode to view file with git-blame output. Here's another version of git-blame.el that automatically tries to create a sensible list of colors to use for both light and dark backgrounds. Plus a few minor fixes. To use: 1) Load into emacs: M-x load-file RET git-blame.el RET 2) Open a git-controlled file 3) Blame: M-x git-blame-mode Signed-off-by: Junio C Hamano --- contrib/emacs/git-blame.el | 180 +++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 contrib/emacs/git-blame.el diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el new file mode 100644 index 0000000000..62cf24c996 --- /dev/null +++ b/contrib/emacs/git-blame.el @@ -0,0 +1,180 @@ +;;; git-blame.el +;; David Kågedal +;; Message-ID: <87iren2vqx.fsf@morpheus.local> + +(require 'cl) +(defun color-scale (l) + (let* ((colors ()) + r g b) + (setq r l) + (while r + (setq g l) + (while g + (setq b l) + (while b + (push (concat "#" (car r) (car g) (car b)) colors) + (pop b)) + (pop g)) + (pop r)) + colors)) + +(defvar git-blame-dark-colors + (color-scale '("00" "04" "08" "0c" + "10" "14" "18" "1c" + "20" "24" "28" "2c" + "30" "34" "38" "3c"))) + +(defvar git-blame-light-colors + (color-scale '("c0" "c4" "c8" "cc" + "d0" "d4" "d8" "dc" + "e0" "e4" "e8" "ec" + "f0" "f4" "f8" "fc"))) + +(defvar git-blame-ancient-color "dark green") + +(defvar git-blame-overlays nil) +(defvar git-blame-cache nil) + +(defvar git-blame-mode nil) +(make-variable-buffer-local 'git-blame-mode) +(push (list 'git-blame-mode " blame") minor-mode-alist) + +(defun git-blame-mode (&optional arg) + (interactive "P") + (if arg + (setq git-blame-mode (eq arg 1)) + (setq git-blame-mode (not git-blame-mode))) + (make-local-variable 'git-blame-overlays) + (make-local-variable 'git-blame-colors) + (make-local-variable 'git-blame-cache) + (let ((bgmode (cdr (assoc 'background-mode (frame-parameters))))) + (if (eq bgmode 'dark) + (setq git-blame-colors git-blame-dark-colors) + (setq git-blame-colors git-blame-light-colors))) + (if git-blame-mode + (git-blame-run) + (git-blame-cleanup))) + +(defun git-blame-run () + (let* ((display-buf (current-buffer)) + (blame-buf (get-buffer-create + (concat " git blame for " (buffer-name)))) + (proc (start-process "git-blame" blame-buf + "git" "blame" "--incremental" + (file-name-nondirectory buffer-file-name)))) + (mapcar 'delete-overlay git-blame-overlays) + (setq git-blame-overlays nil) + (setq git-blame-cache (make-hash-table :test 'equal)) + (with-current-buffer blame-buf + (erase-buffer) + (make-local-variable 'git-blame-file) + (make-local-variable 'git-blame-current) + (setq git-blame-file display-buf) + (setq git-blame-current nil)) + (set-process-filter proc 'git-blame-filter) + (set-process-sentinel proc 'git-blame-sentinel))) + +(defun git-blame-cleanup () + "Remove all blame properties" + (mapcar 'delete-overlay git-blame-overlays) + (setq git-blame-overlays nil) + (let ((modified (buffer-modified-p))) + (remove-text-properties (point-min) (point-max) '(point-entered nil)) + (set-buffer-modified-p modified))) + +(defun git-blame-sentinel (proc status) + ;;(kill-buffer (process-buffer proc)) + (message "git blame finished")) + +(defvar in-blame-filter nil) + +(defun git-blame-filter (proc str) + (save-excursion + (set-buffer (process-buffer proc)) + (goto-char (process-mark proc)) + (insert-before-markers str) + (goto-char 0) + (unless in-blame-filter + (let ((more t) + (in-blame-filter t)) + (while more + (setq more (git-blame-parse))))))) + +(defun git-blame-parse () + (cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n") + (let ((hash (match-string 1)) + (src-line (string-to-number (match-string 2))) + (res-line (string-to-number (match-string 3))) + (num-lines (string-to-number (match-string 4)))) + (setq git-blame-current + (git-blame-new-commit + hash src-line res-line num-lines))) + (delete-region (point) (match-end 0)) + t) + ((looking-at "filename \\(.+\\)\n") + (let ((filename (match-string 1))) + (git-blame-add-info "filename" filename)) + (delete-region (point) (match-end 0)) + t) + ((looking-at "\\([a-z-]+\\) \\(.+\\)\n") + (let ((key (match-string 1)) + (value (match-string 2))) + (git-blame-add-info key value)) + (delete-region (point) (match-end 0)) + t) + ((looking-at "boundary\n") + (setq git-blame-current nil) + (delete-region (point) (match-end 0)) + t) + (t + nil))) + + +(defun git-blame-new-commit (hash src-line res-line num-lines) + (save-excursion + (set-buffer git-blame-file) + (let ((info (gethash hash git-blame-cache)) + (inhibit-point-motion-hooks t)) + (when (not info) + (let ((color (pop git-blame-colors))) + (unless color + (setq color git-blame-ancient-color)) + (setq info (list hash src-line res-line num-lines + (cons 'color color)))) + (puthash hash info git-blame-cache)) + (goto-line res-line) + (while (> num-lines 0) + (if (get-text-property (point) 'git-blame) + (forward-line) + (let* ((start (point)) + (end (progn (forward-line 1) (point))) + (ovl (make-overlay start end))) + (push ovl git-blame-overlays) + (overlay-put ovl 'git-blame info) + (overlay-put ovl 'help-echo hash) + (overlay-put ovl 'face (list :background + (cdr (assq 'color (cddddr info))))) + ;;(overlay-put ovl 'point-entered + ;; `(lambda (x y) (git-blame-identify ,hash))) + (let ((modified (buffer-modified-p))) + (put-text-property (if (= start 1) start (1- start)) (1- end) + 'point-entered + `(lambda (x y) (git-blame-identify ,hash))) + (set-buffer-modified-p modified)))) + (setq num-lines (1- num-lines)))))) + +(defun git-blame-add-info (key value) + (if git-blame-current + (nconc git-blame-current (list (cons (intern key) value))))) + +(defun git-blame-current-commit () + (let ((info (get-char-property (point) 'git-blame))) + (if info + (car info) + (error "No commit info")))) + +(defun git-blame-identify (&optional hash) + (interactive) + (shell-command + (format "git log -1 --pretty=oneline %s" (or hash + (git-blame-current-commit))))) From 1cfe77333f274c9ba9879c2eb61057a790eb050f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 30 Jan 2007 01:11:08 -0800 Subject: [PATCH 17/22] git-blame: no rev means start from the working tree file. Warning: this changes the semantics. This makes "git blame" without any positive rev to start digging from the working tree copy, which is made into a fake commit whose sole parent is the HEAD. It also adds --contents option to pretend as if the working tree copy has the contents of the named file. You can use '-' to make the command read from the standard input. If you want the command to start annotating from the HEAD commit, you need to explicitly give HEAD parameter. Signed-off-by: Junio C Hamano --- builtin-blame.c | 209 ++++++++++++++++++++++++++++++++++++++++++------ diff-lib.c | 44 +++++++++- diff.h | 1 + 3 files changed, 228 insertions(+), 26 deletions(-) diff --git a/builtin-blame.c b/builtin-blame.c index 1c21204a2c..897323a4b2 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -15,9 +15,10 @@ #include "revision.h" #include "quote.h" #include "xdiff-interface.h" +#include "cache-tree.h" static char blame_usage[] = -"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [commit] [--] file\n" +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [--contents ] [commit] [--] file\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -b Show blank SHA-1 for boundary commits (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" @@ -29,6 +30,7 @@ static char blame_usage[] = " -L n,m Process only line range n,m, counting from 1\n" " -M, -C Find line movements within and across files\n" " --incremental Show blame entries as we find them, incrementally\n" +" --contents file Use 's contents as the final image\n" " -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; static int longest_file; @@ -333,9 +335,13 @@ static struct origin *find_origin(struct scoreboard *sb, diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); + + if (is_null_sha1(origin->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); diffcore_std(&diff_opts); /* It is either one entry that says "modified", or "created", @@ -402,9 +408,13 @@ static struct origin *find_rename(struct scoreboard *sb, diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); + + if (is_null_sha1(origin->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); diffcore_std(&diff_opts); for (i = 0; i < diff_queued_diff.nr; i++) { @@ -1047,9 +1057,12 @@ static int find_copy_in_parent(struct scoreboard *sb, (!porigin || strcmp(target->path, porigin->path))) diff_opts.find_copies_harder = 1; - diff_tree_sha1(parent->tree->object.sha1, - target->commit->tree->object.sha1, - "", &diff_opts); + if (is_null_sha1(target->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + target->commit->tree->object.sha1, + "", &diff_opts); if (!diff_opts.find_copies_harder) diffcore_std(&diff_opts); @@ -1336,9 +1349,9 @@ static void get_commit_info(struct commit *commit, tmp += 2; endp = strchr(tmp, '\n'); if (!endp) - goto error_out; + endp = tmp + strlen(tmp); len = endp - tmp; - if (len >= sizeof(summary_buf)) + if (len >= sizeof(summary_buf) || len == 0) goto error_out; memcpy(summary_buf, tmp, len); summary_buf[len] = 0; @@ -1910,6 +1923,137 @@ static int git_blame_config(const char *var, const char *value) return git_default_config(var, value); } +static struct commit *fake_working_tree_commit(const char *path, const char *contents_from) +{ + struct commit *commit; + struct origin *origin; + unsigned char head_sha1[20]; + char *buf; + const char *ident; + int fd; + time_t now; + unsigned long fin_size; + int size, len; + struct cache_entry *ce; + unsigned mode; + + if (get_sha1("HEAD", head_sha1)) + die("No such ref: HEAD"); + + time(&now); + commit = xcalloc(1, sizeof(*commit)); + commit->parents = xcalloc(1, sizeof(*commit->parents)); + commit->parents->item = lookup_commit_reference(head_sha1); + commit->object.parsed = 1; + commit->date = now; + commit->object.type = OBJ_COMMIT; + + origin = make_origin(commit, path); + + if (!contents_from || strcmp("-", contents_from)) { + struct stat st; + const char *read_from; + + if (contents_from) { + if (stat(contents_from, &st) < 0) + die("Cannot stat %s", contents_from); + read_from = contents_from; + } + else { + if (lstat(path, &st) < 0) + die("Cannot lstat %s", path); + read_from = path; + } + fin_size = st.st_size; + buf = xmalloc(fin_size+1); + mode = canon_mode(st.st_mode); + switch (st.st_mode & S_IFMT) { + case S_IFREG: + fd = open(read_from, O_RDONLY); + if (fd < 0) + die("cannot open %s", read_from); + if (read_in_full(fd, buf, fin_size) != fin_size) + die("cannot read %s", read_from); + break; + case S_IFLNK: + if (readlink(read_from, buf, fin_size+1) != fin_size) + die("cannot readlink %s", read_from); + break; + default: + die("unsupported file type %s", read_from); + } + } + else { + /* Reading from stdin */ + contents_from = "standard input"; + buf = NULL; + fin_size = 0; + mode = 0; + while (1) { + ssize_t cnt = 8192; + buf = xrealloc(buf, fin_size + cnt); + cnt = xread(0, buf + fin_size, cnt); + if (cnt < 0) + die("read error %s from stdin", + strerror(errno)); + if (!cnt) + break; + fin_size += cnt; + } + buf = xrealloc(buf, fin_size + 1); + } + buf[fin_size] = 0; + origin->file.ptr = buf; + origin->file.size = fin_size; + write_sha1_file(buf, fin_size, blob_type, origin->blob_sha1); + commit->util = origin; + + /* + * Read the current index, replace the path entry with + * origin->blob_sha1 without mucking with its mode or type + * bits; we are not going to write this index out -- we just + * want to run "diff-index --cached". + */ + discard_cache(); + read_cache(); + + len = strlen(path); + if (!mode) { + int pos = cache_name_pos(path, len); + if (0 <= pos) + mode = ntohl(active_cache[pos]->ce_mode); + else + /* Let's not bother reading from HEAD tree */ + mode = S_IFREG | 0644; + } + size = cache_entry_size(len); + ce = xcalloc(1, size); + hashcpy(ce->sha1, origin->blob_sha1); + memcpy(ce->name, path, len); + ce->ce_flags = create_ce_flags(len, 0); + ce->ce_mode = create_ce_mode(mode); + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + + /* + * We are not going to write this out, so this does not matter + * right now, but someday we might optimize diff-index --cached + * with cache-tree information. + */ + cache_tree_invalidate_path(active_cache_tree, path); + + commit->buffer = xmalloc(400); + ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); + sprintf(commit->buffer, + "tree 0000000000000000000000000000000000000000\n" + "parent %s\n" + "author %s\n" + "committer %s\n\n" + "Version of %s from %s\n", + sha1_to_hex(head_sha1), + ident, ident, path, contents_from ? contents_from : path); + return commit; +} + int cmd_blame(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1924,6 +2068,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) const char *final_commit_name = NULL; char type[10]; const char *bottomtop = NULL; + const char *contents_from = NULL; git_config(git_blame_config); save_commit_buffer = 0; @@ -1968,6 +2113,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) die("More than one '-L n,m' option given"); bottomtop = arg; } + else if (!strcmp("--contents", arg)) { + if (++i >= argc) + usage(blame_usage); + contents_from = argv[i]; + } else if (!strcmp("--incremental", arg)) incremental = 1; else if (!strcmp("--score-debug", arg)) @@ -2087,7 +2237,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) argv[unk] = NULL; init_revisions(&revs, NULL); - setup_revisions(unk, argv, &revs, "HEAD"); + setup_revisions(unk, argv, &revs, NULL); memset(&sb, 0, sizeof(sb)); /* @@ -2114,16 +2264,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix) if (!sb.final) { /* * "--not A B -- path" without anything positive; - * default to HEAD. + * do not default to HEAD, but use the working tree + * or "--contents". */ - unsigned char head_sha1[20]; - - final_commit_name = "HEAD"; - if (get_sha1(final_commit_name, head_sha1)) - die("No such ref: HEAD"); - sb.final = lookup_commit_reference(head_sha1); - add_pending_object(&revs, &(sb.final->object), "HEAD"); + sb.final = fake_working_tree_commit(path, contents_from); + add_pending_object(&revs, &(sb.final->object), ":"); } + else if (contents_from) + die("Cannot use --contents with final commit object name"); /* * If we have bottom, this will mark the ancestors of the @@ -2132,11 +2280,22 @@ int cmd_blame(int argc, const char **argv, const char *prefix) */ prepare_revision_walk(&revs); - o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) - die("no such path %s in %s", path, final_commit_name); + if (is_null_sha1(sb.final->object.sha1)) { + char *buf; + o = sb.final->util; + buf = xmalloc(o->file.size + 1); + memcpy(buf, o->file.ptr, o->file.size + 1); + sb.final_buf = buf; + sb.final_buf_size = o->file.size; + } + else { + o = get_origin(&sb, sb.final, path); + if (fill_blob_sha1(o)) + die("no such path %s in %s", path, final_commit_name); - sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size); + sb.final_buf = read_sha1_file(o->blob_sha1, type, + &sb.final_buf_size); + } num_read_blob++; lno = prepare_lines(&sb); diff --git a/diff-lib.c b/diff-lib.c index 2c9be60ed9..91cd87742f 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -7,6 +7,7 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "cache-tree.h" /* * diff-files @@ -271,7 +272,7 @@ static int diff_cache(struct rev_info *revs, break; } /* Show difference between old and new */ - show_modified(revs,ac[1], ce, 1, + show_modified(revs, ac[1], ce, 1, cached, match_missing); break; case 1: @@ -372,3 +373,44 @@ int run_diff_index(struct rev_info *revs, int cached) diff_flush(&revs->diffopt); return ret; } + +int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) +{ + struct tree *tree; + struct rev_info revs; + int i; + struct cache_entry **dst; + struct cache_entry *last = NULL; + + /* + * This is used by git-blame to run diff-cache internally; + * it potentially needs to repeatedly run this, so we will + * start by removing the higher order entries the last round + * left behind. + */ + dst = active_cache; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) { + if (last && !strcmp(ce->name, last->name)) + continue; + cache_tree_invalidate_path(active_cache_tree, + ce->name); + last = ce; + ce->ce_mode = 0; + ce->ce_flags &= ~htons(CE_STAGEMASK); + } + *dst++ = ce; + } + active_nr = dst - active_cache; + + init_revisions(&revs, NULL); + revs.prune_data = opt->paths; + tree = parse_tree_indirect(tree_sha1); + if (!tree) + die("bad tree object %s", sha1_to_hex(tree_sha1)); + if (read_tree(tree, 1, opt->paths)) + return error("unable to read tree %s", sha1_to_hex(tree_sha1)); + return diff_cache(&revs, active_cache, active_nr, revs.prune_data, + 1, 0); +} diff --git a/diff.h b/diff.h index 7a347cf77d..eece65ddcc 100644 --- a/diff.h +++ b/diff.h @@ -222,6 +222,7 @@ extern int run_diff_files(struct rev_info *revs, int silent_on_removed); extern int run_diff_index(struct rev_info *revs, int cached); +extern int do_diff_cache(const unsigned char *, struct diff_options *); extern int diff_flush_patch_id(struct diff_options *, unsigned char *); #endif /* DIFF_H */ From d66b37bb19ff7a347ba4ccbcdc11b00772518b57 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Feb 2007 21:42:38 -0800 Subject: [PATCH 18/22] Add pretend_sha1_file() interface. The new interface allows an application to temporarily hash a small number of objects and pretend that they are available in the object store without actually writing them. Signed-off-by: Junio C Hamano --- cache.h | 1 + sha1_file.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/cache.h b/cache.h index 38a9bc02f6..87142571b3 100644 --- a/cache.h +++ b/cache.h @@ -257,6 +257,7 @@ extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, uns extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1); extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); +extern int pretend_sha1_file(void *, unsigned long, const char *, unsigned char *); extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); diff --git a/sha1_file.c b/sha1_file.c index 1526a28095..0d4bf80e74 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1505,10 +1505,67 @@ static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned lo return unpack_entry(e.p, e.offset, type, size); } +/* + * This is meant to hold a *small* number of objects that you would + * want read_sha1_file() to be able to return, but yet you do not want + * to write them into the object store (e.g. a browse-only + * application). + */ +static struct cached_object { + unsigned char sha1[20]; + const char *type; + void *buf; + unsigned long size; +} *cached_objects; +static int cached_object_nr, cached_object_alloc; + +static struct cached_object *find_cached_object(const unsigned char *sha1) +{ + int i; + struct cached_object *co = cached_objects; + + for (i = 0; i < cached_object_nr; i++, co++) { + if (!hashcmp(co->sha1, sha1)) + return co; + } + return NULL; +} + +int pretend_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1) +{ + struct cached_object *co; + + hash_sha1_file(buf, len, type, sha1); + if (has_sha1_file(sha1) || find_cached_object(sha1)) + return 0; + if (cached_object_alloc <= cached_object_nr) { + cached_object_alloc = alloc_nr(cached_object_alloc); + cached_objects = xrealloc(cached_objects, + sizeof(*cached_objects) * + cached_object_alloc); + } + co = &cached_objects[cached_object_nr++]; + co->size = len; + co->type = strdup(type); + hashcpy(co->sha1, sha1); + return 0; +} + void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size) { unsigned long mapsize; void *map, *buf; + struct cached_object *co; + + co = find_cached_object(sha1); + if (co) { + buf = xmalloc(co->size + 1); + memcpy(buf, co->buf, co->size); + ((char*)buf)[co->size] = 0; + strcpy(type, co->type); + *size = co->size; + return buf; + } buf = read_packed_sha1(sha1, type, size); if (buf) From 005f85d9ae95c44d8c6ecf61364642fbcaf49dd2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Feb 2007 21:49:05 -0800 Subject: [PATCH 19/22] Use pretend_sha1_file() in git-blame and git-merge-recursive. git-merge-recursive wants an null tree as the fake merge base while producing the merge result tree. The null tree does not have to be written out in the object store as it won't be part of the result, and it is a prime example for using the new pretend_sha1_file() function. git-blame needs to register an arbitrary data to in-core index while annotating a working tree file (or standard input), but git-blame is a read-only application and the user of it could even lack the privilege to write into the object store; it is another good example for pretend_sha1_file(). Signed-off-by: Junio C Hamano --- builtin-blame.c | 2 +- merge-recursive.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin-blame.c b/builtin-blame.c index 897323a4b2..fb30c49280 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -2005,7 +2005,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con buf[fin_size] = 0; origin->file.ptr = buf; origin->file.size = fin_size; - write_sha1_file(buf, fin_size, blob_type, origin->blob_sha1); + pretend_sha1_file(buf, fin_size, blob_type, origin->blob_sha1); commit->util = origin; /* diff --git a/merge-recursive.c b/merge-recursive.c index a68fcc6f37..58989424d7 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1213,7 +1213,7 @@ static int merge(struct commit *h1, tree->object.parsed = 1; tree->object.type = OBJ_TREE; - write_sha1_file(NULL, 0, tree_type, tree->object.sha1); + pretend_sha1_file(NULL, 0, tree_type, tree->object.sha1); merged_common_ancestors = make_virtual_commit(tree, "ancestor"); } From 06e75a72372d0466e110002a7504fefebe0e9c52 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Feb 2007 15:04:01 -0800 Subject: [PATCH 20/22] blame: document --contents option Signed-off-by: Junio C Hamano --- Documentation/git-blame.txt | 9 ++++++++- builtin-blame.c | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index 0ee887d73c..5c9888d014 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [--incremental] [-L n,m] [-S ] - [-M] [-C] [-C] [--since=] [] [--] + [-M] [-C] [-C] [--since=] [ | --contents ] [--] DESCRIPTION ----------- @@ -67,6 +67,13 @@ OPTIONS Show the result incrementally in a format designed for machine consumption. +--contents :: + When is not specified, the command annotates the + changes starting backwards from the working tree copy. + This flag makes the command pretend as if the working + tree copy has the contents of he named file (specify + `-` to make the command read from the standard input). + -M:: Detect moving lines in the file as well. When a commit moves a block of lines in a file (e.g. the original file diff --git a/builtin-blame.c b/builtin-blame.c index fb30c49280..a18ef81a14 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -18,7 +18,7 @@ #include "cache-tree.h" static char blame_usage[] = -"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [--contents ] [commit] [--] file\n" +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [--contents ] [--incremental] [commit] [--] file\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -b Show blank SHA-1 for boundary commits (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" From b6f5da1e0f4eeb59798b320f97d27f83d19f89df Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 1 Feb 2007 23:30:03 -0800 Subject: [PATCH 21/22] Teach git-remote add to fetch and track This adds three options to 'git-remote add'. * -f (or --fetch) option tells it to also run the initial "git fetch" using the newly created remote shorthand. * -t (or --track) option tells it not to use the default wildcard to track all branches. * -m (or --master) option tells it to make the remote/$name/HEAD point at a remote tracking branch other than master. For example, with this I can say: $ git remote add -f -t master -t quick-start -m master \ jbf-um git://linux-nfs.org/~bfields/git.git/ to (1) create remote.jbf-um.url; (2) track master and quick-start branches (and no other); the two -t options create these two lines: fetch = +refs/heads/master:refs/remotes/jbf-um/master fetch = +refs/heads/quick-start:refs/remotes/jbf-um/quick-start (3) set up remotes/jbf-um/HEAD to point at jbf-um/master so that later I can say "git log jbf-um" Or I could do $ git remote add -t 'ap/*' andy /home/andy/git.git to make Andy's topic branches kept track of under refs/remotes/andy/ap/. Other possible improvements I considered but haven't implemented (hint, hint) are: * reject wildcard letters other than a trailing '*' to the -t parameter; * make -m optional and when the first -t parameter does not have the trailing '*' default to that value (so the above example does not need to say "-m master"); * if -m is not given, and -t parameter ends with '*' (i.e. the above defaulting did not tell us where to point HEAD at), and if we did the fetch with -f, check if 'master' was fetched and make HEAD point at it. Signed-off-by: Junio C Hamano --- git-remote.perl | 56 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/git-remote.perl b/git-remote.perl index f16ff21b8b..c56c5a84a4 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -253,14 +253,30 @@ sub show_remote { } sub add_remote { - my ($name, $url) = @_; + my ($name, $url, $opts) = @_; if (exists $remote->{$name}) { print STDERR "remote $name already exists.\n"; exit(1); } $git->command('config', "remote.$name.url", $url); - $git->command('config', "remote.$name.fetch", - "+refs/heads/*:refs/remotes/$name/*"); + my $track = $opts->{'track'} || ["*"]; + + for (@$track) { + $git->command('config', '--add', "remote.$name.fetch", + "+refs/heads/$_:refs/remotes/$name/$_"); + } + if ($opts->{'fetch'}) { + $git->command('fetch', $name); + } + if (exists $opts->{'master'}) { + $git->command('symbolic-ref', "refs/remotes/$name/HEAD", + "refs/remotes/$name/$opts->{'master'}"); + } +} + +sub add_usage { + print STDERR "Usage: git remote add [-f] [-t track]* [-m master] \n"; + exit(1); } if (!@ARGV) { @@ -307,11 +323,37 @@ elsif ($ARGV[0] eq 'prune') { } } elsif ($ARGV[0] eq 'add') { - if (@ARGV != 3) { - print STDERR "Usage: git remote add \n"; - exit(1); + my %opts = (); + while (1 < @ARGV && $ARGV[1] =~ /^-/) { + my $opt = $ARGV[1]; + shift @ARGV; + if ($opt eq '-f' || $opt eq '--fetch') { + $opts{'fetch'} = 1; + next; + } + if ($opt eq '-t' || $opt eq '--track') { + if (@ARGV < 1) { + add_usage(); + } + $opts{'track'} ||= []; + push @{$opts{'track'}}, $ARGV[1]; + shift @ARGV; + next; + } + if ($opt eq '-m' || $opt eq '--master') { + if ((@ARGV < 1) || exists $opts{'master'}) { + add_usage(); + } + $opts{'master'} = $ARGV[1]; + shift @ARGV; + next; + } + add_usage(); } - add_remote($ARGV[1], $ARGV[2]); + if (@ARGV != 3) { + add_usage(); + } + add_remote($ARGV[1], $ARGV[2], \%opts); } else { print STDERR "Usage: git remote\n"; From 35ce862279f68a4798889adcdd90a1698a2c102f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 24 Jan 2007 11:21:10 -0800 Subject: [PATCH 22/22] pager: Work around window resizing bug in 'less' If you resize the terminal while less is waiting for input, less will exit entirely without even showing the output. This is very noticeable if you do something like "git diff" on a big and cold-cache tree and git takes a few seconds to think, and then you resize the window while it's preparing. Boom. No output AT ALL. The way to reproduce the problem is to do some pager operation that takes a while in git, and resizing the window while git is thinking about the output. Try git diff --stat v2.6.12.. in the kernel tree to do something where it takes a while for git to start outputting information. Signed-off-by: Junio C Hamano --- pager.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pager.c b/pager.c index 4587fbbdb5..5f280ab527 100644 --- a/pager.c +++ b/pager.c @@ -1,5 +1,7 @@ #include "cache.h" +#include + /* * This is split up from the rest of git so that we might do * something different on Windows, for example. @@ -7,6 +9,16 @@ static void run_pager(const char *pager) { + /* + * Work around bug in "less" by not starting it until we + * have real input + */ + fd_set in; + + FD_ZERO(&in); + FD_SET(0, &in); + select(1, &in, NULL, &in, NULL); + execlp(pager, pager, NULL); execl("/bin/sh", "sh", "-c", pager, NULL); }