##// END OF EJS Templates
Cleanup of tabs and trailing spaces.
Thomas Arendsen Hein -
r1308:2073e5a7 default
parent child Browse files
Show More
@@ -1,100 +1,100 b''
1 MERCURIAL QUICK-START
1 MERCURIAL QUICK-START
2
2
3 Setting up Mercurial:
3 Setting up Mercurial:
4
4
5 Note: some distributions fails to include bits of distutils by
5 Note: some distributions fails to include bits of distutils by
6 default, you'll need python-dev to install. You'll also need a C
6 default, you'll need python-dev to install. You'll also need a C
7 compiler and a 3-way merge tool like merge, tkdiff, or kdiff3.
7 compiler and a 3-way merge tool like merge, tkdiff, or kdiff3.
8
8
9 First, unpack the source:
9 First, unpack the source:
10
10
11 $ tar xvzf mercurial-<ver>.tar.gz
11 $ tar xvzf mercurial-<ver>.tar.gz
12 $ cd mercurial-<ver>
12 $ cd mercurial-<ver>
13
13
14 To install system-wide:
14 To install system-wide:
15
15
16 $ python setup.py install # change python to python2.3 if 2.2 is default
16 $ python setup.py install # change python to python2.3 if 2.2 is default
17
17
18 To install in your home directory (~/bin and ~/lib, actually), run:
18 To install in your home directory (~/bin and ~/lib, actually), run:
19
19
20 $ python2.3 setup.py install --home=~
20 $ python2.3 setup.py install --home=~
21 $ export PYTHONPATH=${HOME}/lib/python # (or lib64/ on some systems)
21 $ export PYTHONPATH=${HOME}/lib/python # (or lib64/ on some systems)
22 $ export PATH=${HOME}/bin:$PATH # add these to your .bashrc
22 $ export PATH=${HOME}/bin:$PATH # add these to your .bashrc
23
23
24 And finally:
24 And finally:
25
25
26 $ hg # test installation, show help
26 $ hg # test installation, show help
27
27
28 If you get complaints about missing modules, you probably haven't set
28 If you get complaints about missing modules, you probably haven't set
29 PYTHONPATH correctly.
29 PYTHONPATH correctly.
30
30
31 Setting up a Mercurial project:
31 Setting up a Mercurial project:
32
32
33 $ cd project/
33 $ cd project/
34 $ hg init # creates .hg
34 $ hg init # creates .hg
35 $ hg addremove # add all unknown files and remove all missing files
35 $ hg addremove # add all unknown files and remove all missing files
36 $ hg commit # commit all changes, edit changelog entry
36 $ hg commit # commit all changes, edit changelog entry
37
37
38 Mercurial will look for a file named .hgignore in the root of your
38 Mercurial will look for a file named .hgignore in the root of your
39 repository which contains a set of regular expressions to ignore in
39 repository which contains a set of regular expressions to ignore in
40 file paths.
40 file paths.
41
41
42 Branching and merging:
42 Branching and merging:
43
43
44 $ hg clone linux linux-work # create a new branch
44 $ hg clone linux linux-work # create a new branch
45 $ cd linux-work
45 $ cd linux-work
46 $ <make changes>
46 $ <make changes>
47 $ hg commit
47 $ hg commit
48 $ cd ../linux
48 $ cd ../linux
49 $ hg pull ../linux-work # pull changesets from linux-work
49 $ hg pull ../linux-work # pull changesets from linux-work
50 $ hg update -m # merge the new tip from linux-work into
50 $ hg update -m # merge the new tip from linux-work into
51 # our working directory
51 # our working directory
52 $ hg commit # commit the result of the merge
52 $ hg commit # commit the result of the merge
53
53
54 Importing patches:
54 Importing patches:
55
55
56 Fast:
56 Fast:
57 $ patch < ../p/foo.patch
57 $ patch < ../p/foo.patch
58 $ hg addremove
58 $ hg addremove
59 $ hg commit
59 $ hg commit
60
60
61 Faster:
61 Faster:
62 $ patch < ../p/foo.patch
62 $ patch < ../p/foo.patch
63 $ hg commit `lsdiff -p1 ../p/foo.patch`
63 $ hg commit `lsdiff -p1 ../p/foo.patch`
64
64
65 Fastest:
65 Fastest:
66 $ cat ../p/patchlist | xargs hg import -p1 -b ../p
66 $ cat ../p/patchlist | xargs hg import -p1 -b ../p
67
67
68 Exporting a patch:
68 Exporting a patch:
69
69
70 (make changes)
70 (make changes)
71 $ hg commit
71 $ hg commit
72 $ hg tip
72 $ hg tip
73 28237:747a537bd090880c29eae861df4d81b245aa0190
73 28237:747a537bd090880c29eae861df4d81b245aa0190
74 $ hg export 28237 > foo.patch # export changeset 28237
74 $ hg export 28237 > foo.patch # export changeset 28237
75
75
76 Network support:
76 Network support:
77
77
78 # pull from the primary Mercurial repo
78 # pull from the primary Mercurial repo
79 foo$ hg clone http://selenic.com/hg/
79 foo$ hg clone http://selenic.com/hg/
80 foo$ cd hg
80 foo$ cd hg
81
81
82 # export your current repo via HTTP with browsable interface
82 # export your current repo via HTTP with browsable interface
83 foo$ hg serve -n "My repo" -p 80
83 foo$ hg serve -n "My repo" -p 80
84
84
85 # pushing changes to a remote repo with SSH
85 # pushing changes to a remote repo with SSH
86 foo$ hg push ssh://user@example.com/~/hg/
86 foo$ hg push ssh://user@example.com/~/hg/
87
87
88 # merge changes from a remote machine
88 # merge changes from a remote machine
89 bar$ hg pull http://foo/
89 bar$ hg pull http://foo/
90 bar$ hg update -m # merge changes into your working directory
90 bar$ hg update -m # merge changes into your working directory
91
91
92 # Set up a CGI server on your webserver
92 # Set up a CGI server on your webserver
93 foo$ cp hgweb.cgi ~/public_html/hg/index.cgi
93 foo$ cp hgweb.cgi ~/public_html/hg/index.cgi
94 foo$ emacs ~/public_html/hg/index.cgi # adjust the defaults
94 foo$ emacs ~/public_html/hg/index.cgi # adjust the defaults
95
95
96 For more info:
96 For more info:
97
97
98 Documentation in doc/
98 Documentation in doc/
99 Mercurial website at http://selenic.com/mercurial
99 Mercurial website at http://selenic.com/mercurial
100 Mercurial wiki at http://selenic.com/mercurial/wiki
100 Mercurial wiki at http://selenic.com/mercurial/wiki
@@ -1,31 +1,31 b''
1 Mercurial git BK (*)
1 Mercurial git BK (*)
2 storage revlog delta compressed revisions SCCS weave
2 storage revlog delta compressed revisions SCCS weave
3 storage naming by filename by revision hash by filename
3 storage naming by filename by revision hash by filename
4 merge file DAGs changeset DAG file DAGs?
4 merge file DAGs changeset DAG file DAGs?
5 consistency SHA1 SHA1 CRC
5 consistency SHA1 SHA1 CRC
6 signable? yes yes no
6 signable? yes yes no
7
7
8 retrieve file tip O(1) O(1) O(revs)
8 retrieve file tip O(1) O(1) O(revs)
9 add rev O(1) O(1) O(revs)
9 add rev O(1) O(1) O(revs)
10 find prev file rev O(1) O(changesets) O(revs)
10 find prev file rev O(1) O(changesets) O(revs)
11 annotate file O(revs) O(changesets) O(revs)
11 annotate file O(revs) O(changesets) O(revs)
12 find file changeset O(1) O(changesets) ?
12 find file changeset O(1) O(changesets) ?
13
13
14 checkout O(files) O(files) O(revs)?
14 checkout O(files) O(files) O(revs)?
15 commit O(changes) O(changes) ?
15 commit O(changes) O(changes) ?
16 6 patches/s 6 patches/s slow
16 6 patches/s 6 patches/s slow
17 diff working dir O(changes) O(changes) ?
17 diff working dir O(changes) O(changes) ?
18 < 1s < 1s ?
18 < 1s < 1s ?
19 tree diff revs O(changes) O(changes) ?
19 tree diff revs O(changes) O(changes) ?
20 < 1s < 1s ?
20 < 1s < 1s ?
21 hardlink clone O(files) O(revisions) O(files)
21 hardlink clone O(files) O(revisions) O(files)
22
22
23 find remote csets O(log new) rsync: O(revisions) ?
23 find remote csets O(log new) rsync: O(revisions) ?
24 git-http: O(changesets)
24 git-http: O(changesets)
25 pull remote csets O(patch) O(modified files) O(patch)
25 pull remote csets O(patch) O(modified files) O(patch)
26
26
27 repo growth O(patch) O(revisions) O(patch)
27 repo growth O(patch) O(revisions) O(patch)
28 kernel history 300M 3.5G? 250M?
28 kernel history 300M 3.5G? 250M?
29 lines of code 2500 6500 (+ cogito) ??
29 lines of code 2500 6500 (+ cogito) ??
30
30
31 * I've never used BK so this is just guesses
31 * I've never used BK so this is just guesses
@@ -1,171 +1,171 b''
1 #!/bin/bash
1 #!/bin/bash
2
2
3 _hg_commands()
3 _hg_commands()
4 {
4 {
5 local commands="$(hg -v help | sed -e '1,/^list of commands:/d' \
5 local commands="$(hg -v help | sed -e '1,/^list of commands:/d' \
6 -e '/^global options:/,$d' \
6 -e '/^global options:/,$d' \
7 -e '/^ [^ ]/!d; s/[,:]//g;')"
7 -e '/^ [^ ]/!d; s/[,:]//g;')"
8
8
9 # hide debug commands from users, but complete them if
9 # hide debug commands from users, but complete them if
10 # specifically asked for
10 # specifically asked for
11 if [[ "$cur" == de* ]]; then
11 if [[ "$cur" == de* ]]; then
12 commands="$commands debugcheckstate debugstate debugindex"
12 commands="$commands debugcheckstate debugstate debugindex"
13 commands="$commands debugindexdot debugwalk debugdata"
13 commands="$commands debugindexdot debugwalk debugdata"
14 commands="$commands debugancestor debugconfig debugrename"
14 commands="$commands debugancestor debugconfig debugrename"
15 fi
15 fi
16 COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$commands" -- "$cur") )
16 COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$commands" -- "$cur") )
17 }
17 }
18
18
19 _hg_paths()
19 _hg_paths()
20 {
20 {
21 local paths="$(hg paths | sed -e 's/ = .*$//')"
21 local paths="$(hg paths | sed -e 's/ = .*$//')"
22 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W "$paths" -- "$cur" ))
22 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W "$paths" -- "$cur" ))
23 }
23 }
24
24
25 _hg_status()
25 _hg_status()
26 {
26 {
27 local files="$( hg status -$1 | cut -b 3- )"
27 local files="$( hg status -$1 | cut -b 3- )"
28 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W "$files" -- "$cur" ))
28 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W "$files" -- "$cur" ))
29 }
29 }
30
30
31 _hg_tags()
31 _hg_tags()
32 {
32 {
33 local tags="$(hg tags | sed -e 's/[0-9]*:[a-f0-9]\{40\}$//; s/ *$//')"
33 local tags="$(hg tags | sed -e 's/[0-9]*:[a-f0-9]\{40\}$//; s/ *$//')"
34 COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$tags" -- "$cur") )
34 COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$tags" -- "$cur") )
35 }
35 }
36
36
37 # this is "kind of" ugly...
37 # this is "kind of" ugly...
38 _hg_count_non_option()
38 _hg_count_non_option()
39 {
39 {
40 local i count=0
40 local i count=0
41 local filters="$1"
41 local filters="$1"
42
42
43 for (( i=1; $i<=$COMP_CWORD; i++ )); do
43 for (( i=1; $i<=$COMP_CWORD; i++ )); do
44 if [[ "${COMP_WORDS[i]}" != -* ]]; then
44 if [[ "${COMP_WORDS[i]}" != -* ]]; then
45 if [[ ${COMP_WORDS[i-1]} == @($filters|$global_args) ]]; then
45 if [[ ${COMP_WORDS[i-1]} == @($filters|$global_args) ]]; then
46 continue
46 continue
47 fi
47 fi
48 count=$(($count + 1))
48 count=$(($count + 1))
49 fi
49 fi
50 done
50 done
51
51
52 echo $(($count - 1))
52 echo $(($count - 1))
53 }
53 }
54
54
55 _hg()
55 _hg()
56 {
56 {
57 local cur prev cmd opts i
57 local cur prev cmd opts i
58 # global options that receive an argument
58 # global options that receive an argument
59 local global_args='--cwd|-R|--repository'
59 local global_args='--cwd|-R|--repository'
60
60
61 COMPREPLY=()
61 COMPREPLY=()
62 cur="$2"
62 cur="$2"
63 prev="$3"
63 prev="$3"
64
64
65 # searching for the command
65 # searching for the command
66 # (first non-option argument that doesn't follow a global option that
66 # (first non-option argument that doesn't follow a global option that
67 # receives an argument)
67 # receives an argument)
68 for (( i=1; $i<=$COMP_CWORD; i++ )); do
68 for (( i=1; $i<=$COMP_CWORD; i++ )); do
69 if [[ ${COMP_WORDS[i]} != -* ]]; then
69 if [[ ${COMP_WORDS[i]} != -* ]]; then
70 if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
70 if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
71 cmd="${COMP_WORDS[i]}"
71 cmd="${COMP_WORDS[i]}"
72 break
72 break
73 fi
73 fi
74 fi
74 fi
75 done
75 done
76
76
77 if [[ "$cur" == -* ]]; then
77 if [[ "$cur" == -* ]]; then
78 # this assumes that there are no commands with spaces in the name
78 # this assumes that there are no commands with spaces in the name
79 opts=$(hg -v help $cmd | sed -e '/^ *-/!d; s/ [^- ].*//')
79 opts=$(hg -v help $cmd | sed -e '/^ *-/!d; s/ [^- ].*//')
80
80
81 COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$opts" -- "$cur") )
81 COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$opts" -- "$cur") )
82 return
82 return
83 fi
83 fi
84
84
85 # global options
85 # global options
86 case "$prev" in
86 case "$prev" in
87 -R|--repository)
87 -R|--repository)
88 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -d -- "$cur" ))
88 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -d -- "$cur" ))
89 return
89 return
90 ;;
90 ;;
91 --cwd)
91 --cwd)
92 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -d -- "$cur" ))
92 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -d -- "$cur" ))
93 return
93 return
94 ;;
94 ;;
95 esac
95 esac
96
96
97 if [ -z "$cmd" ] || [ $COMP_CWORD -eq $i ]; then
97 if [ -z "$cmd" ] || [ $COMP_CWORD -eq $i ]; then
98 _hg_commands
98 _hg_commands
99 return
99 return
100 fi
100 fi
101
101
102 # canonicalize command name
102 # canonicalize command name
103 cmd=$(hg -q help "$cmd" | sed -e 's/^hg //; s/ .*//; 1q')
103 cmd=$(hg -q help "$cmd" | sed -e 's/^hg //; s/ .*//; 1q')
104
104
105 if [ "$cmd" != status ] && [ "$prev" = -r ] || [ "$prev" = --rev ]; then
105 if [ "$cmd" != status ] && [ "$prev" = -r ] || [ "$prev" = --rev ]; then
106 _hg_tags
106 _hg_tags
107 return
107 return
108 fi
108 fi
109
109
110 case "$cmd" in
110 case "$cmd" in
111 help)
111 help)
112 _hg_commands
112 _hg_commands
113 ;;
113 ;;
114 export|manifest|update)
114 export|manifest|update)
115 _hg_tags
115 _hg_tags
116 ;;
116 ;;
117 pull|push|outgoing|incoming)
117 pull|push|outgoing|incoming)
118 _hg_paths
118 _hg_paths
119 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -d -- "$cur" ))
119 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -d -- "$cur" ))
120 ;;
120 ;;
121 paths)
121 paths)
122 _hg_paths
122 _hg_paths
123 ;;
123 ;;
124 add)
124 add)
125 _hg_status "u"
125 _hg_status "u"
126 ;;
126 ;;
127 commit)
127 commit)
128 _hg_status "mra"
128 _hg_status "mra"
129 ;;
129 ;;
130 remove)
130 remove)
131 _hg_status "r"
131 _hg_status "r"
132 ;;
132 ;;
133 forget)
133 forget)
134 _hg_status "a"
134 _hg_status "a"
135 ;;
135 ;;
136 diff)
136 diff)
137 _hg_status "mra"
137 _hg_status "mra"
138 ;;
138 ;;
139 revert)
139 revert)
140 _hg_status "mra"
140 _hg_status "mra"
141 ;;
141 ;;
142 clone)
142 clone)
143 local count=$(_hg_count_non_option)
143 local count=$(_hg_count_non_option)
144 if [ $count = 1 ]; then
144 if [ $count = 1 ]; then
145 _hg_paths
145 _hg_paths
146 fi
146 fi
147 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -d -- "$cur" ))
147 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -d -- "$cur" ))
148 ;;
148 ;;
149 debugindex|debugindexdot)
149 debugindex|debugindexdot)
150 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -X "!*.i" -- "$cur" ))
150 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -X "!*.i" -- "$cur" ))
151 ;;
151 ;;
152 debugdata)
152 debugdata)
153 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -X "!*.d" -- "$cur" ))
153 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -X "!*.d" -- "$cur" ))
154 ;;
154 ;;
155 cat)
155 cat)
156 local count=$(_hg_count_non_option '-o|--output')
156 local count=$(_hg_count_non_option '-o|--output')
157 if [ $count = 2 ]; then
157 if [ $count = 2 ]; then
158 _hg_tags
158 _hg_tags
159 else
159 else
160 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" ))
160 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" ))
161 fi
161 fi
162 ;;
162 ;;
163 *)
163 *)
164 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" ))
164 COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" ))
165 ;;
165 ;;
166 esac
166 esac
167
167
168 }
168 }
169
169
170 complete -o bashdefault -o default -F _hg hg 2> /dev/null \
170 complete -o bashdefault -o default -F _hg hg 2> /dev/null \
171 || complete -o default -F _hg hg
171 || complete -o default -F _hg hg
@@ -1,3651 +1,3651 b''
1 #!/bin/sh
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "${1+$@}"
3 exec wish "$0" -- "${1+$@}"
4
4
5 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
5 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
8 # either version 2, or (at your option) any later version.
9
9
10 proc gitdir {} {
10 proc gitdir {} {
11 global env
11 global env
12 if {[info exists env(GIT_DIR)]} {
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
13 return $env(GIT_DIR)
14 } else {
14 } else {
15 return ".hg"
15 return ".hg"
16 }
16 }
17 }
17 }
18
18
19 proc getcommits {rargs} {
19 proc getcommits {rargs} {
20 global commits commfd phase canv mainfont env
20 global commits commfd phase canv mainfont env
21 global startmsecs nextupdate ncmupdate
21 global startmsecs nextupdate ncmupdate
22 global ctext maincursor textcursor leftover
22 global ctext maincursor textcursor leftover
23
23
24 # check that we can find a .git directory somewhere...
24 # check that we can find a .git directory somewhere...
25 set gitdir [gitdir]
25 set gitdir [gitdir]
26 if {![file isdirectory $gitdir]} {
26 if {![file isdirectory $gitdir]} {
27 error_popup "Cannot find the git directory \"$gitdir\"."
27 error_popup "Cannot find the git directory \"$gitdir\"."
28 exit 1
28 exit 1
29 }
29 }
30 set commits {}
30 set commits {}
31 set phase getcommits
31 set phase getcommits
32 set startmsecs [clock clicks -milliseconds]
32 set startmsecs [clock clicks -milliseconds]
33 set nextupdate [expr $startmsecs + 100]
33 set nextupdate [expr $startmsecs + 100]
34 set ncmupdate 1
34 set ncmupdate 1
35 if [catch {
35 if [catch {
36 set parse_args [concat --default HEAD $rargs]
36 set parse_args [concat --default HEAD $rargs]
37 set parsed_args [split [eval exec hg debug-rev-parse $parse_args] "\n"]
37 set parsed_args [split [eval exec hg debug-rev-parse $parse_args] "\n"]
38 }] {
38 }] {
39 # if git-rev-parse failed for some reason...
39 # if git-rev-parse failed for some reason...
40 if {$rargs == {}} {
40 if {$rargs == {}} {
41 set rargs HEAD
41 set rargs HEAD
42 }
42 }
43 set parsed_args $rargs
43 set parsed_args $rargs
44 }
44 }
45 if [catch {
45 if [catch {
46 set commfd [open "|hg debug-rev-list --header --topo-order --parents $parsed_args" r]
46 set commfd [open "|hg debug-rev-list --header --topo-order --parents $parsed_args" r]
47 } err] {
47 } err] {
48 puts stderr "Error executing hg debug-rev-list: $err"
48 puts stderr "Error executing hg debug-rev-list: $err"
49 exit 1
49 exit 1
50 }
50 }
51 set leftover {}
51 set leftover {}
52 fconfigure $commfd -blocking 0 -translation lf
52 fconfigure $commfd -blocking 0 -translation lf
53 fileevent $commfd readable [list getcommitlines $commfd]
53 fileevent $commfd readable [list getcommitlines $commfd]
54 $canv delete all
54 $canv delete all
55 $canv create text 3 3 -anchor nw -text "Reading commits..." \
55 $canv create text 3 3 -anchor nw -text "Reading commits..." \
56 -font $mainfont -tags textitems
56 -font $mainfont -tags textitems
57 . config -cursor watch
57 . config -cursor watch
58 settextcursor watch
58 settextcursor watch
59 }
59 }
60
60
61 proc getcommitlines {commfd} {
61 proc getcommitlines {commfd} {
62 global commits parents cdate children
62 global commits parents cdate children
63 global commitlisted phase commitinfo nextupdate
63 global commitlisted phase commitinfo nextupdate
64 global stopped redisplaying leftover
64 global stopped redisplaying leftover
65
65
66 set stuff [read $commfd]
66 set stuff [read $commfd]
67 if {$stuff == {}} {
67 if {$stuff == {}} {
68 if {![eof $commfd]} return
68 if {![eof $commfd]} return
69 # set it blocking so we wait for the process to terminate
69 # set it blocking so we wait for the process to terminate
70 fconfigure $commfd -blocking 1
70 fconfigure $commfd -blocking 1
71 if {![catch {close $commfd} err]} {
71 if {![catch {close $commfd} err]} {
72 after idle finishcommits
72 after idle finishcommits
73 return
73 return
74 }
74 }
75 if {[string range $err 0 4] == "usage"} {
75 if {[string range $err 0 4] == "usage"} {
76 set err \
76 set err \
77 {Gitk: error reading commits: bad arguments to git-rev-list.
77 {Gitk: error reading commits: bad arguments to git-rev-list.
78 (Note: arguments to gitk are passed to git-rev-list
78 (Note: arguments to gitk are passed to git-rev-list
79 to allow selection of commits to be displayed.)}
79 to allow selection of commits to be displayed.)}
80 } else {
80 } else {
81 set err "Error reading commits: $err"
81 set err "Error reading commits: $err"
82 }
82 }
83 error_popup $err
83 error_popup $err
84 exit 1
84 exit 1
85 }
85 }
86 set start 0
86 set start 0
87 while 1 {
87 while 1 {
88 set i [string first "\0" $stuff $start]
88 set i [string first "\0" $stuff $start]
89 if {$i < 0} {
89 if {$i < 0} {
90 append leftover [string range $stuff $start end]
90 append leftover [string range $stuff $start end]
91 return
91 return
92 }
92 }
93 set cmit [string range $stuff $start [expr {$i - 1}]]
93 set cmit [string range $stuff $start [expr {$i - 1}]]
94 if {$start == 0} {
94 if {$start == 0} {
95 set cmit "$leftover$cmit"
95 set cmit "$leftover$cmit"
96 set leftover {}
96 set leftover {}
97 }
97 }
98 set start [expr {$i + 1}]
98 set start [expr {$i + 1}]
99 set j [string first "\n" $cmit]
99 set j [string first "\n" $cmit]
100 set ok 0
100 set ok 0
101 if {$j >= 0} {
101 if {$j >= 0} {
102 set ids [string range $cmit 0 [expr {$j - 1}]]
102 set ids [string range $cmit 0 [expr {$j - 1}]]
103 set ok 1
103 set ok 1
104 foreach id $ids {
104 foreach id $ids {
105 if {![regexp {^[0-9a-f]{40}$} $id]} {
105 if {![regexp {^[0-9a-f]{40}$} $id]} {
106 set ok 0
106 set ok 0
107 break
107 break
108 }
108 }
109 }
109 }
110 }
110 }
111 if {!$ok} {
111 if {!$ok} {
112 set shortcmit $cmit
112 set shortcmit $cmit
113 if {[string length $shortcmit] > 80} {
113 if {[string length $shortcmit] > 80} {
114 set shortcmit "[string range $shortcmit 0 80]..."
114 set shortcmit "[string range $shortcmit 0 80]..."
115 }
115 }
116 error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
116 error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
117 exit 1
117 exit 1
118 }
118 }
119 set id [lindex $ids 0]
119 set id [lindex $ids 0]
120 set olds [lrange $ids 1 end]
120 set olds [lrange $ids 1 end]
121 set cmit [string range $cmit [expr {$j + 1}] end]
121 set cmit [string range $cmit [expr {$j + 1}] end]
122 lappend commits $id
122 lappend commits $id
123 set commitlisted($id) 1
123 set commitlisted($id) 1
124 parsecommit $id $cmit 1 [lrange $ids 1 end]
124 parsecommit $id $cmit 1 [lrange $ids 1 end]
125 drawcommit $id
125 drawcommit $id
126 if {[clock clicks -milliseconds] >= $nextupdate} {
126 if {[clock clicks -milliseconds] >= $nextupdate} {
127 doupdate 1
127 doupdate 1
128 }
128 }
129 while {$redisplaying} {
129 while {$redisplaying} {
130 set redisplaying 0
130 set redisplaying 0
131 if {$stopped == 1} {
131 if {$stopped == 1} {
132 set stopped 0
132 set stopped 0
133 set phase "getcommits"
133 set phase "getcommits"
134 foreach id $commits {
134 foreach id $commits {
135 drawcommit $id
135 drawcommit $id
136 if {$stopped} break
136 if {$stopped} break
137 if {[clock clicks -milliseconds] >= $nextupdate} {
137 if {[clock clicks -milliseconds] >= $nextupdate} {
138 doupdate 1
138 doupdate 1
139 }
139 }
140 }
140 }
141 }
141 }
142 }
142 }
143 }
143 }
144 }
144 }
145
145
146 proc doupdate {reading} {
146 proc doupdate {reading} {
147 global commfd nextupdate numcommits ncmupdate
147 global commfd nextupdate numcommits ncmupdate
148
148
149 if {$reading} {
149 if {$reading} {
150 fileevent $commfd readable {}
150 fileevent $commfd readable {}
151 }
151 }
152 update
152 update
153 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
153 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
154 if {$numcommits < 100} {
154 if {$numcommits < 100} {
155 set ncmupdate [expr {$numcommits + 1}]
155 set ncmupdate [expr {$numcommits + 1}]
156 } elseif {$numcommits < 10000} {
156 } elseif {$numcommits < 10000} {
157 set ncmupdate [expr {$numcommits + 10}]
157 set ncmupdate [expr {$numcommits + 10}]
158 } else {
158 } else {
159 set ncmupdate [expr {$numcommits + 100}]
159 set ncmupdate [expr {$numcommits + 100}]
160 }
160 }
161 if {$reading} {
161 if {$reading} {
162 fileevent $commfd readable [list getcommitlines $commfd]
162 fileevent $commfd readable [list getcommitlines $commfd]
163 }
163 }
164 }
164 }
165
165
166 proc readcommit {id} {
166 proc readcommit {id} {
167 if [catch {set contents [exec hg debug-cat-file commit $id]}] return
167 if [catch {set contents [exec hg debug-cat-file commit $id]}] return
168 parsecommit $id $contents 0 {}
168 parsecommit $id $contents 0 {}
169 }
169 }
170
170
171 proc parsecommit {id contents listed olds} {
171 proc parsecommit {id contents listed olds} {
172 global commitinfo children nchildren parents nparents cdate ncleft
172 global commitinfo children nchildren parents nparents cdate ncleft
173
173
174 set inhdr 1
174 set inhdr 1
175 set comment {}
175 set comment {}
176 set headline {}
176 set headline {}
177 set auname {}
177 set auname {}
178 set audate {}
178 set audate {}
179 set comname {}
179 set comname {}
180 set comdate {}
180 set comdate {}
181 if {![info exists nchildren($id)]} {
181 if {![info exists nchildren($id)]} {
182 set children($id) {}
182 set children($id) {}
183 set nchildren($id) 0
183 set nchildren($id) 0
184 set ncleft($id) 0
184 set ncleft($id) 0
185 }
185 }
186 set parents($id) $olds
186 set parents($id) $olds
187 set nparents($id) [llength $olds]
187 set nparents($id) [llength $olds]
188 foreach p $olds {
188 foreach p $olds {
189 if {![info exists nchildren($p)]} {
189 if {![info exists nchildren($p)]} {
190 set children($p) [list $id]
190 set children($p) [list $id]
191 set nchildren($p) 1
191 set nchildren($p) 1
192 set ncleft($p) 1
192 set ncleft($p) 1
193 } elseif {[lsearch -exact $children($p) $id] < 0} {
193 } elseif {[lsearch -exact $children($p) $id] < 0} {
194 lappend children($p) $id
194 lappend children($p) $id
195 incr nchildren($p)
195 incr nchildren($p)
196 incr ncleft($p)
196 incr ncleft($p)
197 }
197 }
198 }
198 }
199 foreach line [split $contents "\n"] {
199 foreach line [split $contents "\n"] {
200 if {$inhdr} {
200 if {$inhdr} {
201 if {$line == {}} {
201 if {$line == {}} {
202 set inhdr 0
202 set inhdr 0
203 } else {
203 } else {
204 set tag [lindex $line 0]
204 set tag [lindex $line 0]
205 if {$tag == "author"} {
205 if {$tag == "author"} {
206 set x [expr {[llength $line] - 2}]
206 set x [expr {[llength $line] - 2}]
207 set audate [lindex $line $x]
207 set audate [lindex $line $x]
208 set auname [lrange $line 1 [expr {$x - 1}]]
208 set auname [lrange $line 1 [expr {$x - 1}]]
209 } elseif {$tag == "committer"} {
209 } elseif {$tag == "committer"} {
210 set x [expr {[llength $line] - 2}]
210 set x [expr {[llength $line] - 2}]
211 set comdate [lindex $line $x]
211 set comdate [lindex $line $x]
212 set comname [lrange $line 1 [expr {$x - 1}]]
212 set comname [lrange $line 1 [expr {$x - 1}]]
213 }
213 }
214 }
214 }
215 } else {
215 } else {
216 if {$comment == {}} {
216 if {$comment == {}} {
217 set headline [string trim $line]
217 set headline [string trim $line]
218 } else {
218 } else {
219 append comment "\n"
219 append comment "\n"
220 }
220 }
221 if {!$listed} {
221 if {!$listed} {
222 # git-rev-list indents the comment by 4 spaces;
222 # git-rev-list indents the comment by 4 spaces;
223 # if we got this via git-cat-file, add the indentation
223 # if we got this via git-cat-file, add the indentation
224 append comment " "
224 append comment " "
225 }
225 }
226 append comment $line
226 append comment $line
227 }
227 }
228 }
228 }
229 if {$audate != {}} {
229 if {$audate != {}} {
230 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
230 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
231 }
231 }
232 if {$comdate != {}} {
232 if {$comdate != {}} {
233 set cdate($id) $comdate
233 set cdate($id) $comdate
234 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
234 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
235 }
235 }
236 set commitinfo($id) [list $headline $auname $audate \
236 set commitinfo($id) [list $headline $auname $audate \
237 $comname $comdate $comment]
237 $comname $comdate $comment]
238 }
238 }
239
239
240 proc readrefs {} {
240 proc readrefs {} {
241 global tagids idtags headids idheads tagcontents
241 global tagids idtags headids idheads tagcontents
242
242
243 set tags [exec hg tags]
243 set tags [exec hg tags]
244 set lines [split $tags '\n']
244 set lines [split $tags '\n']
245 foreach f $lines {
245 foreach f $lines {
246 set f [regexp -all -inline {\S+} $f]
246 set f [regexp -all -inline {\S+} $f]
247 set direct [lindex $f 0]
247 set direct [lindex $f 0]
248 set full [lindex $f 1]
248 set full [lindex $f 1]
249 set sha [split $full ':']
249 set sha [split $full ':']
250 set tag [lindex $sha 1]
250 set tag [lindex $sha 1]
251 lappend tagids($direct) $tag
251 lappend tagids($direct) $tag
252 lappend idtags($tag) $direct
252 lappend idtags($tag) $direct
253 }
253 }
254 }
254 }
255
255
256 proc readotherrefs {base dname excl} {
256 proc readotherrefs {base dname excl} {
257 global otherrefids idotherrefs
257 global otherrefids idotherrefs
258
258
259 set git [gitdir]
259 set git [gitdir]
260 set files [glob -nocomplain -types f [file join $git $base *]]
260 set files [glob -nocomplain -types f [file join $git $base *]]
261 foreach f $files {
261 foreach f $files {
262 catch {
262 catch {
263 set fd [open $f r]
263 set fd [open $f r]
264 set line [read $fd 40]
264 set line [read $fd 40]
265 if {[regexp {^[0-9a-f]{40}} $line id]} {
265 if {[regexp {^[0-9a-f]{40}} $line id]} {
266 set name "$dname[file tail $f]"
266 set name "$dname[file tail $f]"
267 set otherrefids($name) $id
267 set otherrefids($name) $id
268 lappend idotherrefs($id) $name
268 lappend idotherrefs($id) $name
269 }
269 }
270 close $fd
270 close $fd
271 }
271 }
272 }
272 }
273 set dirs [glob -nocomplain -types d [file join $git $base *]]
273 set dirs [glob -nocomplain -types d [file join $git $base *]]
274 foreach d $dirs {
274 foreach d $dirs {
275 set dir [file tail $d]
275 set dir [file tail $d]
276 if {[lsearch -exact $excl $dir] >= 0} continue
276 if {[lsearch -exact $excl $dir] >= 0} continue
277 readotherrefs [file join $base $dir] "$dname$dir/" {}
277 readotherrefs [file join $base $dir] "$dname$dir/" {}
278 }
278 }
279 }
279 }
280
280
281 proc error_popup msg {
281 proc error_popup msg {
282 set w .error
282 set w .error
283 toplevel $w
283 toplevel $w
284 wm transient $w .
284 wm transient $w .
285 message $w.m -text $msg -justify center -aspect 400
285 message $w.m -text $msg -justify center -aspect 400
286 pack $w.m -side top -fill x -padx 20 -pady 20
286 pack $w.m -side top -fill x -padx 20 -pady 20
287 button $w.ok -text OK -command "destroy $w"
287 button $w.ok -text OK -command "destroy $w"
288 pack $w.ok -side bottom -fill x
288 pack $w.ok -side bottom -fill x
289 bind $w <Visibility> "grab $w; focus $w"
289 bind $w <Visibility> "grab $w; focus $w"
290 tkwait window $w
290 tkwait window $w
291 }
291 }
292
292
293 proc makewindow {} {
293 proc makewindow {} {
294 global canv canv2 canv3 linespc charspc ctext cflist textfont
294 global canv canv2 canv3 linespc charspc ctext cflist textfont
295 global findtype findtypemenu findloc findstring fstring geometry
295 global findtype findtypemenu findloc findstring fstring geometry
296 global entries sha1entry sha1string sha1but
296 global entries sha1entry sha1string sha1but
297 global maincursor textcursor curtextcursor
297 global maincursor textcursor curtextcursor
298 global rowctxmenu gaudydiff mergemax
298 global rowctxmenu gaudydiff mergemax
299
299
300 menu .bar
300 menu .bar
301 .bar add cascade -label "File" -menu .bar.file
301 .bar add cascade -label "File" -menu .bar.file
302 menu .bar.file
302 menu .bar.file
303 .bar.file add command -label "Reread references" -command rereadrefs
303 .bar.file add command -label "Reread references" -command rereadrefs
304 .bar.file add command -label "Quit" -command doquit
304 .bar.file add command -label "Quit" -command doquit
305 menu .bar.help
305 menu .bar.help
306 .bar add cascade -label "Help" -menu .bar.help
306 .bar add cascade -label "Help" -menu .bar.help
307 .bar.help add command -label "About gitk" -command about
307 .bar.help add command -label "About gitk" -command about
308 . configure -menu .bar
308 . configure -menu .bar
309
309
310 if {![info exists geometry(canv1)]} {
310 if {![info exists geometry(canv1)]} {
311 set geometry(canv1) [expr 45 * $charspc]
311 set geometry(canv1) [expr 45 * $charspc]
312 set geometry(canv2) [expr 30 * $charspc]
312 set geometry(canv2) [expr 30 * $charspc]
313 set geometry(canv3) [expr 15 * $charspc]
313 set geometry(canv3) [expr 15 * $charspc]
314 set geometry(canvh) [expr 25 * $linespc + 4]
314 set geometry(canvh) [expr 25 * $linespc + 4]
315 set geometry(ctextw) 80
315 set geometry(ctextw) 80
316 set geometry(ctexth) 30
316 set geometry(ctexth) 30
317 set geometry(cflistw) 30
317 set geometry(cflistw) 30
318 }
318 }
319 panedwindow .ctop -orient vertical
319 panedwindow .ctop -orient vertical
320 if {[info exists geometry(width)]} {
320 if {[info exists geometry(width)]} {
321 .ctop conf -width $geometry(width) -height $geometry(height)
321 .ctop conf -width $geometry(width) -height $geometry(height)
322 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
322 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
323 set geometry(ctexth) [expr {($texth - 8) /
323 set geometry(ctexth) [expr {($texth - 8) /
324 [font metrics $textfont -linespace]}]
324 [font metrics $textfont -linespace]}]
325 }
325 }
326 frame .ctop.top
326 frame .ctop.top
327 frame .ctop.top.bar
327 frame .ctop.top.bar
328 pack .ctop.top.bar -side bottom -fill x
328 pack .ctop.top.bar -side bottom -fill x
329 set cscroll .ctop.top.csb
329 set cscroll .ctop.top.csb
330 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
330 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
331 pack $cscroll -side right -fill y
331 pack $cscroll -side right -fill y
332 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
332 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
333 pack .ctop.top.clist -side top -fill both -expand 1
333 pack .ctop.top.clist -side top -fill both -expand 1
334 .ctop add .ctop.top
334 .ctop add .ctop.top
335 set canv .ctop.top.clist.canv
335 set canv .ctop.top.clist.canv
336 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
336 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
337 -bg white -bd 0 \
337 -bg white -bd 0 \
338 -yscrollincr $linespc -yscrollcommand "$cscroll set"
338 -yscrollincr $linespc -yscrollcommand "$cscroll set"
339 .ctop.top.clist add $canv
339 .ctop.top.clist add $canv
340 set canv2 .ctop.top.clist.canv2
340 set canv2 .ctop.top.clist.canv2
341 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
341 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
342 -bg white -bd 0 -yscrollincr $linespc
342 -bg white -bd 0 -yscrollincr $linespc
343 .ctop.top.clist add $canv2
343 .ctop.top.clist add $canv2
344 set canv3 .ctop.top.clist.canv3
344 set canv3 .ctop.top.clist.canv3
345 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
345 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
346 -bg white -bd 0 -yscrollincr $linespc
346 -bg white -bd 0 -yscrollincr $linespc
347 .ctop.top.clist add $canv3
347 .ctop.top.clist add $canv3
348 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
348 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
349
349
350 set sha1entry .ctop.top.bar.sha1
350 set sha1entry .ctop.top.bar.sha1
351 set entries $sha1entry
351 set entries $sha1entry
352 set sha1but .ctop.top.bar.sha1label
352 set sha1but .ctop.top.bar.sha1label
353 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
353 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
354 -command gotocommit -width 8
354 -command gotocommit -width 8
355 $sha1but conf -disabledforeground [$sha1but cget -foreground]
355 $sha1but conf -disabledforeground [$sha1but cget -foreground]
356 pack .ctop.top.bar.sha1label -side left
356 pack .ctop.top.bar.sha1label -side left
357 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
357 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
358 trace add variable sha1string write sha1change
358 trace add variable sha1string write sha1change
359 pack $sha1entry -side left -pady 2
359 pack $sha1entry -side left -pady 2
360
360
361 image create bitmap bm-left -data {
361 image create bitmap bm-left -data {
362 #define left_width 16
362 #define left_width 16
363 #define left_height 16
363 #define left_height 16
364 static unsigned char left_bits[] = {
364 static unsigned char left_bits[] = {
365 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
365 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
366 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
366 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
367 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
367 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
368 }
368 }
369 image create bitmap bm-right -data {
369 image create bitmap bm-right -data {
370 #define right_width 16
370 #define right_width 16
371 #define right_height 16
371 #define right_height 16
372 static unsigned char right_bits[] = {
372 static unsigned char right_bits[] = {
373 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
373 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
374 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
374 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
375 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
375 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
376 }
376 }
377 button .ctop.top.bar.leftbut -image bm-left -command goback \
377 button .ctop.top.bar.leftbut -image bm-left -command goback \
378 -state disabled -width 26
378 -state disabled -width 26
379 pack .ctop.top.bar.leftbut -side left -fill y
379 pack .ctop.top.bar.leftbut -side left -fill y
380 button .ctop.top.bar.rightbut -image bm-right -command goforw \
380 button .ctop.top.bar.rightbut -image bm-right -command goforw \
381 -state disabled -width 26
381 -state disabled -width 26
382 pack .ctop.top.bar.rightbut -side left -fill y
382 pack .ctop.top.bar.rightbut -side left -fill y
383
383
384 button .ctop.top.bar.findbut -text "Find" -command dofind
384 button .ctop.top.bar.findbut -text "Find" -command dofind
385 pack .ctop.top.bar.findbut -side left
385 pack .ctop.top.bar.findbut -side left
386 set findstring {}
386 set findstring {}
387 set fstring .ctop.top.bar.findstring
387 set fstring .ctop.top.bar.findstring
388 lappend entries $fstring
388 lappend entries $fstring
389 entry $fstring -width 30 -font $textfont -textvariable findstring
389 entry $fstring -width 30 -font $textfont -textvariable findstring
390 pack $fstring -side left -expand 1 -fill x
390 pack $fstring -side left -expand 1 -fill x
391 set findtype Exact
391 set findtype Exact
392 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
392 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
393 findtype Exact IgnCase Regexp]
393 findtype Exact IgnCase Regexp]
394 set findloc "All fields"
394 set findloc "All fields"
395 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
395 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
396 Comments Author Committer Files Pickaxe
396 Comments Author Committer Files Pickaxe
397 pack .ctop.top.bar.findloc -side right
397 pack .ctop.top.bar.findloc -side right
398 pack .ctop.top.bar.findtype -side right
398 pack .ctop.top.bar.findtype -side right
399 # for making sure type==Exact whenever loc==Pickaxe
399 # for making sure type==Exact whenever loc==Pickaxe
400 trace add variable findloc write findlocchange
400 trace add variable findloc write findlocchange
401
401
402 panedwindow .ctop.cdet -orient horizontal
402 panedwindow .ctop.cdet -orient horizontal
403 .ctop add .ctop.cdet
403 .ctop add .ctop.cdet
404 frame .ctop.cdet.left
404 frame .ctop.cdet.left
405 set ctext .ctop.cdet.left.ctext
405 set ctext .ctop.cdet.left.ctext
406 text $ctext -bg white -state disabled -font $textfont \
406 text $ctext -bg white -state disabled -font $textfont \
407 -width $geometry(ctextw) -height $geometry(ctexth) \
407 -width $geometry(ctextw) -height $geometry(ctexth) \
408 -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
408 -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
409 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
409 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
410 pack .ctop.cdet.left.sb -side right -fill y
410 pack .ctop.cdet.left.sb -side right -fill y
411 pack $ctext -side left -fill both -expand 1
411 pack $ctext -side left -fill both -expand 1
412 .ctop.cdet add .ctop.cdet.left
412 .ctop.cdet add .ctop.cdet.left
413
413
414 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
414 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
415 if {$gaudydiff} {
415 if {$gaudydiff} {
416 $ctext tag conf hunksep -back blue -fore white
416 $ctext tag conf hunksep -back blue -fore white
417 $ctext tag conf d0 -back "#ff8080"
417 $ctext tag conf d0 -back "#ff8080"
418 $ctext tag conf d1 -back green
418 $ctext tag conf d1 -back green
419 } else {
419 } else {
420 $ctext tag conf hunksep -fore blue
420 $ctext tag conf hunksep -fore blue
421 $ctext tag conf d0 -fore red
421 $ctext tag conf d0 -fore red
422 $ctext tag conf d1 -fore "#00a000"
422 $ctext tag conf d1 -fore "#00a000"
423 $ctext tag conf m0 -fore red
423 $ctext tag conf m0 -fore red
424 $ctext tag conf m1 -fore blue
424 $ctext tag conf m1 -fore blue
425 $ctext tag conf m2 -fore green
425 $ctext tag conf m2 -fore green
426 $ctext tag conf m3 -fore purple
426 $ctext tag conf m3 -fore purple
427 $ctext tag conf m4 -fore brown
427 $ctext tag conf m4 -fore brown
428 $ctext tag conf mmax -fore darkgrey
428 $ctext tag conf mmax -fore darkgrey
429 set mergemax 5
429 set mergemax 5
430 $ctext tag conf mresult -font [concat $textfont bold]
430 $ctext tag conf mresult -font [concat $textfont bold]
431 $ctext tag conf msep -font [concat $textfont bold]
431 $ctext tag conf msep -font [concat $textfont bold]
432 $ctext tag conf found -back yellow
432 $ctext tag conf found -back yellow
433 }
433 }
434
434
435 frame .ctop.cdet.right
435 frame .ctop.cdet.right
436 set cflist .ctop.cdet.right.cfiles
436 set cflist .ctop.cdet.right.cfiles
437 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
437 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
438 -yscrollcommand ".ctop.cdet.right.sb set"
438 -yscrollcommand ".ctop.cdet.right.sb set"
439 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
439 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
440 pack .ctop.cdet.right.sb -side right -fill y
440 pack .ctop.cdet.right.sb -side right -fill y
441 pack $cflist -side left -fill both -expand 1
441 pack $cflist -side left -fill both -expand 1
442 .ctop.cdet add .ctop.cdet.right
442 .ctop.cdet add .ctop.cdet.right
443 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
443 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
444
444
445 pack .ctop -side top -fill both -expand 1
445 pack .ctop -side top -fill both -expand 1
446
446
447 bindall <1> {selcanvline %W %x %y}
447 bindall <1> {selcanvline %W %x %y}
448 #bindall <B1-Motion> {selcanvline %W %x %y}
448 #bindall <B1-Motion> {selcanvline %W %x %y}
449 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
449 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
450 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
450 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
451 bindall <2> "allcanvs scan mark 0 %y"
451 bindall <2> "allcanvs scan mark 0 %y"
452 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
452 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
453 bind . <Key-Up> "selnextline -1"
453 bind . <Key-Up> "selnextline -1"
454 bind . <Key-Down> "selnextline 1"
454 bind . <Key-Down> "selnextline 1"
455 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
455 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
456 bind . <Key-Next> "allcanvs yview scroll 1 pages"
456 bind . <Key-Next> "allcanvs yview scroll 1 pages"
457 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
457 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
458 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
458 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
459 bindkey <Key-space> "$ctext yview scroll 1 pages"
459 bindkey <Key-space> "$ctext yview scroll 1 pages"
460 bindkey p "selnextline -1"
460 bindkey p "selnextline -1"
461 bindkey n "selnextline 1"
461 bindkey n "selnextline 1"
462 bindkey b "$ctext yview scroll -1 pages"
462 bindkey b "$ctext yview scroll -1 pages"
463 bindkey d "$ctext yview scroll 18 units"
463 bindkey d "$ctext yview scroll 18 units"
464 bindkey u "$ctext yview scroll -18 units"
464 bindkey u "$ctext yview scroll -18 units"
465 bindkey / {findnext 1}
465 bindkey / {findnext 1}
466 bindkey <Key-Return> {findnext 0}
466 bindkey <Key-Return> {findnext 0}
467 bindkey ? findprev
467 bindkey ? findprev
468 bindkey f nextfile
468 bindkey f nextfile
469 bind . <Control-q> doquit
469 bind . <Control-q> doquit
470 bind . <Control-f> dofind
470 bind . <Control-f> dofind
471 bind . <Control-g> {findnext 0}
471 bind . <Control-g> {findnext 0}
472 bind . <Control-r> findprev
472 bind . <Control-r> findprev
473 bind . <Control-equal> {incrfont 1}
473 bind . <Control-equal> {incrfont 1}
474 bind . <Control-KP_Add> {incrfont 1}
474 bind . <Control-KP_Add> {incrfont 1}
475 bind . <Control-minus> {incrfont -1}
475 bind . <Control-minus> {incrfont -1}
476 bind . <Control-KP_Subtract> {incrfont -1}
476 bind . <Control-KP_Subtract> {incrfont -1}
477 bind $cflist <<ListboxSelect>> listboxsel
477 bind $cflist <<ListboxSelect>> listboxsel
478 bind . <Destroy> {savestuff %W}
478 bind . <Destroy> {savestuff %W}
479 bind . <Button-1> "click %W"
479 bind . <Button-1> "click %W"
480 bind $fstring <Key-Return> dofind
480 bind $fstring <Key-Return> dofind
481 bind $sha1entry <Key-Return> gotocommit
481 bind $sha1entry <Key-Return> gotocommit
482 bind $sha1entry <<PasteSelection>> clearsha1
482 bind $sha1entry <<PasteSelection>> clearsha1
483
483
484 set maincursor [. cget -cursor]
484 set maincursor [. cget -cursor]
485 set textcursor [$ctext cget -cursor]
485 set textcursor [$ctext cget -cursor]
486 set curtextcursor $textcursor
486 set curtextcursor $textcursor
487
487
488 set rowctxmenu .rowctxmenu
488 set rowctxmenu .rowctxmenu
489 menu $rowctxmenu -tearoff 0
489 menu $rowctxmenu -tearoff 0
490 $rowctxmenu add command -label "Diff this -> selected" \
490 $rowctxmenu add command -label "Diff this -> selected" \
491 -command {diffvssel 0}
491 -command {diffvssel 0}
492 $rowctxmenu add command -label "Diff selected -> this" \
492 $rowctxmenu add command -label "Diff selected -> this" \
493 -command {diffvssel 1}
493 -command {diffvssel 1}
494 $rowctxmenu add command -label "Make patch" -command mkpatch
494 $rowctxmenu add command -label "Make patch" -command mkpatch
495 $rowctxmenu add command -label "Create tag" -command mktag
495 $rowctxmenu add command -label "Create tag" -command mktag
496 $rowctxmenu add command -label "Write commit to file" -command writecommit
496 $rowctxmenu add command -label "Write commit to file" -command writecommit
497 }
497 }
498
498
499 # when we make a key binding for the toplevel, make sure
499 # when we make a key binding for the toplevel, make sure
500 # it doesn't get triggered when that key is pressed in the
500 # it doesn't get triggered when that key is pressed in the
501 # find string entry widget.
501 # find string entry widget.
502 proc bindkey {ev script} {
502 proc bindkey {ev script} {
503 global entries
503 global entries
504 bind . $ev $script
504 bind . $ev $script
505 set escript [bind Entry $ev]
505 set escript [bind Entry $ev]
506 if {$escript == {}} {
506 if {$escript == {}} {
507 set escript [bind Entry <Key>]
507 set escript [bind Entry <Key>]
508 }
508 }
509 foreach e $entries {
509 foreach e $entries {
510 bind $e $ev "$escript; break"
510 bind $e $ev "$escript; break"
511 }
511 }
512 }
512 }
513
513
514 # set the focus back to the toplevel for any click outside
514 # set the focus back to the toplevel for any click outside
515 # the entry widgets
515 # the entry widgets
516 proc click {w} {
516 proc click {w} {
517 global entries
517 global entries
518 foreach e $entries {
518 foreach e $entries {
519 if {$w == $e} return
519 if {$w == $e} return
520 }
520 }
521 focus .
521 focus .
522 }
522 }
523
523
524 proc savestuff {w} {
524 proc savestuff {w} {
525 global canv canv2 canv3 ctext cflist mainfont textfont
525 global canv canv2 canv3 ctext cflist mainfont textfont
526 global stuffsaved findmergefiles gaudydiff maxgraphpct
526 global stuffsaved findmergefiles gaudydiff maxgraphpct
527 global maxwidth
527 global maxwidth
528
528
529 if {$stuffsaved} return
529 if {$stuffsaved} return
530 if {![winfo viewable .]} return
530 if {![winfo viewable .]} return
531 catch {
531 catch {
532 set f [open "~/.gitk-new" w]
532 set f [open "~/.gitk-new" w]
533 puts $f [list set mainfont $mainfont]
533 puts $f [list set mainfont $mainfont]
534 puts $f [list set textfont $textfont]
534 puts $f [list set textfont $textfont]
535 puts $f [list set findmergefiles $findmergefiles]
535 puts $f [list set findmergefiles $findmergefiles]
536 puts $f [list set gaudydiff $gaudydiff]
536 puts $f [list set gaudydiff $gaudydiff]
537 puts $f [list set maxgraphpct $maxgraphpct]
537 puts $f [list set maxgraphpct $maxgraphpct]
538 puts $f [list set maxwidth $maxwidth]
538 puts $f [list set maxwidth $maxwidth]
539 puts $f "set geometry(width) [winfo width .ctop]"
539 puts $f "set geometry(width) [winfo width .ctop]"
540 puts $f "set geometry(height) [winfo height .ctop]"
540 puts $f "set geometry(height) [winfo height .ctop]"
541 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
541 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
542 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
542 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
543 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
543 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
544 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
544 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
545 set wid [expr {([winfo width $ctext] - 8) \
545 set wid [expr {([winfo width $ctext] - 8) \
546 / [font measure $textfont "0"]}]
546 / [font measure $textfont "0"]}]
547 puts $f "set geometry(ctextw) $wid"
547 puts $f "set geometry(ctextw) $wid"
548 set wid [expr {([winfo width $cflist] - 11) \
548 set wid [expr {([winfo width $cflist] - 11) \
549 / [font measure [$cflist cget -font] "0"]}]
549 / [font measure [$cflist cget -font] "0"]}]
550 puts $f "set geometry(cflistw) $wid"
550 puts $f "set geometry(cflistw) $wid"
551 close $f
551 close $f
552 file rename -force "~/.gitk-new" "~/.gitk"
552 file rename -force "~/.gitk-new" "~/.gitk"
553 }
553 }
554 set stuffsaved 1
554 set stuffsaved 1
555 }
555 }
556
556
557 proc resizeclistpanes {win w} {
557 proc resizeclistpanes {win w} {
558 global oldwidth
558 global oldwidth
559 if [info exists oldwidth($win)] {
559 if [info exists oldwidth($win)] {
560 set s0 [$win sash coord 0]
560 set s0 [$win sash coord 0]
561 set s1 [$win sash coord 1]
561 set s1 [$win sash coord 1]
562 if {$w < 60} {
562 if {$w < 60} {
563 set sash0 [expr {int($w/2 - 2)}]
563 set sash0 [expr {int($w/2 - 2)}]
564 set sash1 [expr {int($w*5/6 - 2)}]
564 set sash1 [expr {int($w*5/6 - 2)}]
565 } else {
565 } else {
566 set factor [expr {1.0 * $w / $oldwidth($win)}]
566 set factor [expr {1.0 * $w / $oldwidth($win)}]
567 set sash0 [expr {int($factor * [lindex $s0 0])}]
567 set sash0 [expr {int($factor * [lindex $s0 0])}]
568 set sash1 [expr {int($factor * [lindex $s1 0])}]
568 set sash1 [expr {int($factor * [lindex $s1 0])}]
569 if {$sash0 < 30} {
569 if {$sash0 < 30} {
570 set sash0 30
570 set sash0 30
571 }
571 }
572 if {$sash1 < $sash0 + 20} {
572 if {$sash1 < $sash0 + 20} {
573 set sash1 [expr $sash0 + 20]
573 set sash1 [expr $sash0 + 20]
574 }
574 }
575 if {$sash1 > $w - 10} {
575 if {$sash1 > $w - 10} {
576 set sash1 [expr $w - 10]
576 set sash1 [expr $w - 10]
577 if {$sash0 > $sash1 - 20} {
577 if {$sash0 > $sash1 - 20} {
578 set sash0 [expr $sash1 - 20]
578 set sash0 [expr $sash1 - 20]
579 }
579 }
580 }
580 }
581 }
581 }
582 $win sash place 0 $sash0 [lindex $s0 1]
582 $win sash place 0 $sash0 [lindex $s0 1]
583 $win sash place 1 $sash1 [lindex $s1 1]
583 $win sash place 1 $sash1 [lindex $s1 1]
584 }
584 }
585 set oldwidth($win) $w
585 set oldwidth($win) $w
586 }
586 }
587
587
588 proc resizecdetpanes {win w} {
588 proc resizecdetpanes {win w} {
589 global oldwidth
589 global oldwidth
590 if [info exists oldwidth($win)] {
590 if [info exists oldwidth($win)] {
591 set s0 [$win sash coord 0]
591 set s0 [$win sash coord 0]
592 if {$w < 60} {
592 if {$w < 60} {
593 set sash0 [expr {int($w*3/4 - 2)}]
593 set sash0 [expr {int($w*3/4 - 2)}]
594 } else {
594 } else {
595 set factor [expr {1.0 * $w / $oldwidth($win)}]
595 set factor [expr {1.0 * $w / $oldwidth($win)}]
596 set sash0 [expr {int($factor * [lindex $s0 0])}]
596 set sash0 [expr {int($factor * [lindex $s0 0])}]
597 if {$sash0 < 45} {
597 if {$sash0 < 45} {
598 set sash0 45
598 set sash0 45
599 }
599 }
600 if {$sash0 > $w - 15} {
600 if {$sash0 > $w - 15} {
601 set sash0 [expr $w - 15]
601 set sash0 [expr $w - 15]
602 }
602 }
603 }
603 }
604 $win sash place 0 $sash0 [lindex $s0 1]
604 $win sash place 0 $sash0 [lindex $s0 1]
605 }
605 }
606 set oldwidth($win) $w
606 set oldwidth($win) $w
607 }
607 }
608
608
609 proc allcanvs args {
609 proc allcanvs args {
610 global canv canv2 canv3
610 global canv canv2 canv3
611 eval $canv $args
611 eval $canv $args
612 eval $canv2 $args
612 eval $canv2 $args
613 eval $canv3 $args
613 eval $canv3 $args
614 }
614 }
615
615
616 proc bindall {event action} {
616 proc bindall {event action} {
617 global canv canv2 canv3
617 global canv canv2 canv3
618 bind $canv $event $action
618 bind $canv $event $action
619 bind $canv2 $event $action
619 bind $canv2 $event $action
620 bind $canv3 $event $action
620 bind $canv3 $event $action
621 }
621 }
622
622
623 proc about {} {
623 proc about {} {
624 set w .about
624 set w .about
625 if {[winfo exists $w]} {
625 if {[winfo exists $w]} {
626 raise $w
626 raise $w
627 return
627 return
628 }
628 }
629 toplevel $w
629 toplevel $w
630 wm title $w "About gitk"
630 wm title $w "About gitk"
631 message $w.m -text {
631 message $w.m -text {
632 Gitk version 1.2
632 Gitk version 1.2
633
633
634 Copyright οΏ½ 2005 Paul Mackerras
634 Copyright οΏ½ 2005 Paul Mackerras
635
635
636 Use and redistribute under the terms of the GNU General Public License} \
636 Use and redistribute under the terms of the GNU General Public License} \
637 -justify center -aspect 400
637 -justify center -aspect 400
638 pack $w.m -side top -fill x -padx 20 -pady 20
638 pack $w.m -side top -fill x -padx 20 -pady 20
639 button $w.ok -text Close -command "destroy $w"
639 button $w.ok -text Close -command "destroy $w"
640 pack $w.ok -side bottom
640 pack $w.ok -side bottom
641 }
641 }
642
642
643 proc assigncolor {id} {
643 proc assigncolor {id} {
644 global commitinfo colormap commcolors colors nextcolor
644 global commitinfo colormap commcolors colors nextcolor
645 global parents nparents children nchildren
645 global parents nparents children nchildren
646 global cornercrossings crossings
646 global cornercrossings crossings
647
647
648 if [info exists colormap($id)] return
648 if [info exists colormap($id)] return
649 set ncolors [llength $colors]
649 set ncolors [llength $colors]
650 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
650 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
651 set child [lindex $children($id) 0]
651 set child [lindex $children($id) 0]
652 if {[info exists colormap($child)]
652 if {[info exists colormap($child)]
653 && $nparents($child) == 1} {
653 && $nparents($child) == 1} {
654 set colormap($id) $colormap($child)
654 set colormap($id) $colormap($child)
655 return
655 return
656 }
656 }
657 }
657 }
658 set badcolors {}
658 set badcolors {}
659 if {[info exists cornercrossings($id)]} {
659 if {[info exists cornercrossings($id)]} {
660 foreach x $cornercrossings($id) {
660 foreach x $cornercrossings($id) {
661 if {[info exists colormap($x)]
661 if {[info exists colormap($x)]
662 && [lsearch -exact $badcolors $colormap($x)] < 0} {
662 && [lsearch -exact $badcolors $colormap($x)] < 0} {
663 lappend badcolors $colormap($x)
663 lappend badcolors $colormap($x)
664 }
664 }
665 }
665 }
666 if {[llength $badcolors] >= $ncolors} {
666 if {[llength $badcolors] >= $ncolors} {
667 set badcolors {}
667 set badcolors {}
668 }
668 }
669 }
669 }
670 set origbad $badcolors
670 set origbad $badcolors
671 if {[llength $badcolors] < $ncolors - 1} {
671 if {[llength $badcolors] < $ncolors - 1} {
672 if {[info exists crossings($id)]} {
672 if {[info exists crossings($id)]} {
673 foreach x $crossings($id) {
673 foreach x $crossings($id) {
674 if {[info exists colormap($x)]
674 if {[info exists colormap($x)]
675 && [lsearch -exact $badcolors $colormap($x)] < 0} {
675 && [lsearch -exact $badcolors $colormap($x)] < 0} {
676 lappend badcolors $colormap($x)
676 lappend badcolors $colormap($x)
677 }
677 }
678 }
678 }
679 if {[llength $badcolors] >= $ncolors} {
679 if {[llength $badcolors] >= $ncolors} {
680 set badcolors $origbad
680 set badcolors $origbad
681 }
681 }
682 }
682 }
683 set origbad $badcolors
683 set origbad $badcolors
684 }
684 }
685 if {[llength $badcolors] < $ncolors - 1} {
685 if {[llength $badcolors] < $ncolors - 1} {
686 foreach child $children($id) {
686 foreach child $children($id) {
687 if {[info exists colormap($child)]
687 if {[info exists colormap($child)]
688 && [lsearch -exact $badcolors $colormap($child)] < 0} {
688 && [lsearch -exact $badcolors $colormap($child)] < 0} {
689 lappend badcolors $colormap($child)
689 lappend badcolors $colormap($child)
690 }
690 }
691 if {[info exists parents($child)]} {
691 if {[info exists parents($child)]} {
692 foreach p $parents($child) {
692 foreach p $parents($child) {
693 if {[info exists colormap($p)]
693 if {[info exists colormap($p)]
694 && [lsearch -exact $badcolors $colormap($p)] < 0} {
694 && [lsearch -exact $badcolors $colormap($p)] < 0} {
695 lappend badcolors $colormap($p)
695 lappend badcolors $colormap($p)
696 }
696 }
697 }
697 }
698 }
698 }
699 }
699 }
700 if {[llength $badcolors] >= $ncolors} {
700 if {[llength $badcolors] >= $ncolors} {
701 set badcolors $origbad
701 set badcolors $origbad
702 }
702 }
703 }
703 }
704 for {set i 0} {$i <= $ncolors} {incr i} {
704 for {set i 0} {$i <= $ncolors} {incr i} {
705 set c [lindex $colors $nextcolor]
705 set c [lindex $colors $nextcolor]
706 if {[incr nextcolor] >= $ncolors} {
706 if {[incr nextcolor] >= $ncolors} {
707 set nextcolor 0
707 set nextcolor 0
708 }
708 }
709 if {[lsearch -exact $badcolors $c]} break
709 if {[lsearch -exact $badcolors $c]} break
710 }
710 }
711 set colormap($id) $c
711 set colormap($id) $c
712 }
712 }
713
713
714 proc initgraph {} {
714 proc initgraph {} {
715 global canvy canvy0 lineno numcommits nextcolor linespc
715 global canvy canvy0 lineno numcommits nextcolor linespc
716 global mainline mainlinearrow sidelines
716 global mainline mainlinearrow sidelines
717 global nchildren ncleft
717 global nchildren ncleft
718 global displist nhyperspace
718 global displist nhyperspace
719
719
720 allcanvs delete all
720 allcanvs delete all
721 set nextcolor 0
721 set nextcolor 0
722 set canvy $canvy0
722 set canvy $canvy0
723 set lineno -1
723 set lineno -1
724 set numcommits 0
724 set numcommits 0
725 catch {unset mainline}
725 catch {unset mainline}
726 catch {unset mainlinearrow}
726 catch {unset mainlinearrow}
727 catch {unset sidelines}
727 catch {unset sidelines}
728 foreach id [array names nchildren] {
728 foreach id [array names nchildren] {
729 set ncleft($id) $nchildren($id)
729 set ncleft($id) $nchildren($id)
730 }
730 }
731 set displist {}
731 set displist {}
732 set nhyperspace 0
732 set nhyperspace 0
733 }
733 }
734
734
735 proc bindline {t id} {
735 proc bindline {t id} {
736 global canv
736 global canv
737
737
738 $canv bind $t <Enter> "lineenter %x %y $id"
738 $canv bind $t <Enter> "lineenter %x %y $id"
739 $canv bind $t <Motion> "linemotion %x %y $id"
739 $canv bind $t <Motion> "linemotion %x %y $id"
740 $canv bind $t <Leave> "lineleave $id"
740 $canv bind $t <Leave> "lineleave $id"
741 $canv bind $t <Button-1> "lineclick %x %y $id 1"
741 $canv bind $t <Button-1> "lineclick %x %y $id 1"
742 }
742 }
743
743
744 proc drawlines {id xtra} {
744 proc drawlines {id xtra} {
745 global mainline mainlinearrow sidelines lthickness colormap canv
745 global mainline mainlinearrow sidelines lthickness colormap canv
746
746
747 $canv delete lines.$id
747 $canv delete lines.$id
748 if {[info exists mainline($id)]} {
748 if {[info exists mainline($id)]} {
749 set t [$canv create line $mainline($id) \
749 set t [$canv create line $mainline($id) \
750 -width [expr {($xtra + 1) * $lthickness}] \
750 -width [expr {($xtra + 1) * $lthickness}] \
751 -fill $colormap($id) -tags lines.$id \
751 -fill $colormap($id) -tags lines.$id \
752 -arrow $mainlinearrow($id)]
752 -arrow $mainlinearrow($id)]
753 $canv lower $t
753 $canv lower $t
754 bindline $t $id
754 bindline $t $id
755 }
755 }
756 if {[info exists sidelines($id)]} {
756 if {[info exists sidelines($id)]} {
757 foreach ls $sidelines($id) {
757 foreach ls $sidelines($id) {
758 set coords [lindex $ls 0]
758 set coords [lindex $ls 0]
759 set thick [lindex $ls 1]
759 set thick [lindex $ls 1]
760 set arrow [lindex $ls 2]
760 set arrow [lindex $ls 2]
761 set t [$canv create line $coords -fill $colormap($id) \
761 set t [$canv create line $coords -fill $colormap($id) \
762 -width [expr {($thick + $xtra) * $lthickness}] \
762 -width [expr {($thick + $xtra) * $lthickness}] \
763 -arrow $arrow -tags lines.$id]
763 -arrow $arrow -tags lines.$id]
764 $canv lower $t
764 $canv lower $t
765 bindline $t $id
765 bindline $t $id
766 }
766 }
767 }
767 }
768 }
768 }
769
769
770 # level here is an index in displist
770 # level here is an index in displist
771 proc drawcommitline {level} {
771 proc drawcommitline {level} {
772 global parents children nparents displist
772 global parents children nparents displist
773 global canv canv2 canv3 mainfont namefont canvy linespc
773 global canv canv2 canv3 mainfont namefont canvy linespc
774 global lineid linehtag linentag linedtag commitinfo
774 global lineid linehtag linentag linedtag commitinfo
775 global colormap numcommits currentparents dupparents
775 global colormap numcommits currentparents dupparents
776 global idtags idline idheads idotherrefs
776 global idtags idline idheads idotherrefs
777 global lineno lthickness mainline mainlinearrow sidelines
777 global lineno lthickness mainline mainlinearrow sidelines
778 global commitlisted rowtextx idpos lastuse displist
778 global commitlisted rowtextx idpos lastuse displist
779 global oldnlines olddlevel olddisplist
779 global oldnlines olddlevel olddisplist
780
780
781 incr numcommits
781 incr numcommits
782 incr lineno
782 incr lineno
783 set id [lindex $displist $level]
783 set id [lindex $displist $level]
784 set lastuse($id) $lineno
784 set lastuse($id) $lineno
785 set lineid($lineno) $id
785 set lineid($lineno) $id
786 set idline($id) $lineno
786 set idline($id) $lineno
787 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
787 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
788 if {![info exists commitinfo($id)]} {
788 if {![info exists commitinfo($id)]} {
789 readcommit $id
789 readcommit $id
790 if {![info exists commitinfo($id)]} {
790 if {![info exists commitinfo($id)]} {
791 set commitinfo($id) {"No commit information available"}
791 set commitinfo($id) {"No commit information available"}
792 set nparents($id) 0
792 set nparents($id) 0
793 }
793 }
794 }
794 }
795 assigncolor $id
795 assigncolor $id
796 set currentparents {}
796 set currentparents {}
797 set dupparents {}
797 set dupparents {}
798 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
798 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
799 foreach p $parents($id) {
799 foreach p $parents($id) {
800 if {[lsearch -exact $currentparents $p] < 0} {
800 if {[lsearch -exact $currentparents $p] < 0} {
801 lappend currentparents $p
801 lappend currentparents $p
802 } else {
802 } else {
803 # remember that this parent was listed twice
803 # remember that this parent was listed twice
804 lappend dupparents $p
804 lappend dupparents $p
805 }
805 }
806 }
806 }
807 }
807 }
808 set x [xcoord $level $level $lineno]
808 set x [xcoord $level $level $lineno]
809 set y1 $canvy
809 set y1 $canvy
810 set canvy [expr $canvy + $linespc]
810 set canvy [expr $canvy + $linespc]
811 allcanvs conf -scrollregion \
811 allcanvs conf -scrollregion \
812 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
812 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
813 if {[info exists mainline($id)]} {
813 if {[info exists mainline($id)]} {
814 lappend mainline($id) $x $y1
814 lappend mainline($id) $x $y1
815 if {$mainlinearrow($id) ne "none"} {
815 if {$mainlinearrow($id) ne "none"} {
816 set mainline($id) [trimdiagstart $mainline($id)]
816 set mainline($id) [trimdiagstart $mainline($id)]
817 }
817 }
818 }
818 }
819 drawlines $id 0
819 drawlines $id 0
820 set orad [expr {$linespc / 3}]
820 set orad [expr {$linespc / 3}]
821 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
821 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
822 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
822 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
823 -fill $ofill -outline black -width 1]
823 -fill $ofill -outline black -width 1]
824 $canv raise $t
824 $canv raise $t
825 $canv bind $t <1> {selcanvline {} %x %y}
825 $canv bind $t <1> {selcanvline {} %x %y}
826 set xt [xcoord [llength $displist] $level $lineno]
826 set xt [xcoord [llength $displist] $level $lineno]
827 if {[llength $currentparents] > 2} {
827 if {[llength $currentparents] > 2} {
828 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
828 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
829 }
829 }
830 set rowtextx($lineno) $xt
830 set rowtextx($lineno) $xt
831 set idpos($id) [list $x $xt $y1]
831 set idpos($id) [list $x $xt $y1]
832 if {[info exists idtags($id)] || [info exists idheads($id)]
832 if {[info exists idtags($id)] || [info exists idheads($id)]
833 || [info exists idotherrefs($id)]} {
833 || [info exists idotherrefs($id)]} {
834 set xt [drawtags $id $x $xt $y1]
834 set xt [drawtags $id $x $xt $y1]
835 }
835 }
836 set headline [lindex $commitinfo($id) 0]
836 set headline [lindex $commitinfo($id) 0]
837 set name [lindex $commitinfo($id) 1]
837 set name [lindex $commitinfo($id) 1]
838 set date [lindex $commitinfo($id) 2]
838 set date [lindex $commitinfo($id) 2]
839 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
839 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
840 -text $headline -font $mainfont ]
840 -text $headline -font $mainfont ]
841 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
841 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
842 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
842 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
843 -text $name -font $namefont]
843 -text $name -font $namefont]
844 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
844 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
845 -text $date -font $mainfont]
845 -text $date -font $mainfont]
846
846
847 set olddlevel $level
847 set olddlevel $level
848 set olddisplist $displist
848 set olddisplist $displist
849 set oldnlines [llength $displist]
849 set oldnlines [llength $displist]
850 }
850 }
851
851
852 proc drawtags {id x xt y1} {
852 proc drawtags {id x xt y1} {
853 global idtags idheads idotherrefs
853 global idtags idheads idotherrefs
854 global linespc lthickness
854 global linespc lthickness
855 global canv mainfont idline rowtextx
855 global canv mainfont idline rowtextx
856
856
857 set marks {}
857 set marks {}
858 set ntags 0
858 set ntags 0
859 set nheads 0
859 set nheads 0
860 if {[info exists idtags($id)]} {
860 if {[info exists idtags($id)]} {
861 set marks $idtags($id)
861 set marks $idtags($id)
862 set ntags [llength $marks]
862 set ntags [llength $marks]
863 }
863 }
864 if {[info exists idheads($id)]} {
864 if {[info exists idheads($id)]} {
865 set marks [concat $marks $idheads($id)]
865 set marks [concat $marks $idheads($id)]
866 set nheads [llength $idheads($id)]
866 set nheads [llength $idheads($id)]
867 }
867 }
868 if {[info exists idotherrefs($id)]} {
868 if {[info exists idotherrefs($id)]} {
869 set marks [concat $marks $idotherrefs($id)]
869 set marks [concat $marks $idotherrefs($id)]
870 }
870 }
871 if {$marks eq {}} {
871 if {$marks eq {}} {
872 return $xt
872 return $xt
873 }
873 }
874
874
875 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
875 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
876 set yt [expr $y1 - 0.5 * $linespc]
876 set yt [expr $y1 - 0.5 * $linespc]
877 set yb [expr $yt + $linespc - 1]
877 set yb [expr $yt + $linespc - 1]
878 set xvals {}
878 set xvals {}
879 set wvals {}
879 set wvals {}
880 foreach tag $marks {
880 foreach tag $marks {
881 set wid [font measure $mainfont $tag]
881 set wid [font measure $mainfont $tag]
882 lappend xvals $xt
882 lappend xvals $xt
883 lappend wvals $wid
883 lappend wvals $wid
884 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
884 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
885 }
885 }
886 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
886 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
887 -width $lthickness -fill black -tags tag.$id]
887 -width $lthickness -fill black -tags tag.$id]
888 $canv lower $t
888 $canv lower $t
889 foreach tag $marks x $xvals wid $wvals {
889 foreach tag $marks x $xvals wid $wvals {
890 set xl [expr $x + $delta]
890 set xl [expr $x + $delta]
891 set xr [expr $x + $delta + $wid + $lthickness]
891 set xr [expr $x + $delta + $wid + $lthickness]
892 if {[incr ntags -1] >= 0} {
892 if {[incr ntags -1] >= 0} {
893 # draw a tag
893 # draw a tag
894 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
894 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
895 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
895 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
896 -width 1 -outline black -fill yellow -tags tag.$id]
896 -width 1 -outline black -fill yellow -tags tag.$id]
897 $canv bind $t <1> [list showtag $tag 1]
897 $canv bind $t <1> [list showtag $tag 1]
898 set rowtextx($idline($id)) [expr {$xr + $linespc}]
898 set rowtextx($idline($id)) [expr {$xr + $linespc}]
899 } else {
899 } else {
900 # draw a head or other ref
900 # draw a head or other ref
901 if {[incr nheads -1] >= 0} {
901 if {[incr nheads -1] >= 0} {
902 set col green
902 set col green
903 } else {
903 } else {
904 set col "#ddddff"
904 set col "#ddddff"
905 }
905 }
906 set xl [expr $xl - $delta/2]
906 set xl [expr $xl - $delta/2]
907 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
907 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
908 -width 1 -outline black -fill $col -tags tag.$id
908 -width 1 -outline black -fill $col -tags tag.$id
909 }
909 }
910 set t [$canv create text $xl $y1 -anchor w -text $tag \
910 set t [$canv create text $xl $y1 -anchor w -text $tag \
911 -font $mainfont -tags tag.$id]
911 -font $mainfont -tags tag.$id]
912 if {$ntags >= 0} {
912 if {$ntags >= 0} {
913 $canv bind $t <1> [list showtag $tag 1]
913 $canv bind $t <1> [list showtag $tag 1]
914 }
914 }
915 }
915 }
916 return $xt
916 return $xt
917 }
917 }
918
918
919 proc notecrossings {id lo hi corner} {
919 proc notecrossings {id lo hi corner} {
920 global olddisplist crossings cornercrossings
920 global olddisplist crossings cornercrossings
921
921
922 for {set i $lo} {[incr i] < $hi} {} {
922 for {set i $lo} {[incr i] < $hi} {} {
923 set p [lindex $olddisplist $i]
923 set p [lindex $olddisplist $i]
924 if {$p == {}} continue
924 if {$p == {}} continue
925 if {$i == $corner} {
925 if {$i == $corner} {
926 if {![info exists cornercrossings($id)]
926 if {![info exists cornercrossings($id)]
927 || [lsearch -exact $cornercrossings($id) $p] < 0} {
927 || [lsearch -exact $cornercrossings($id) $p] < 0} {
928 lappend cornercrossings($id) $p
928 lappend cornercrossings($id) $p
929 }
929 }
930 if {![info exists cornercrossings($p)]
930 if {![info exists cornercrossings($p)]
931 || [lsearch -exact $cornercrossings($p) $id] < 0} {
931 || [lsearch -exact $cornercrossings($p) $id] < 0} {
932 lappend cornercrossings($p) $id
932 lappend cornercrossings($p) $id
933 }
933 }
934 } else {
934 } else {
935 if {![info exists crossings($id)]
935 if {![info exists crossings($id)]
936 || [lsearch -exact $crossings($id) $p] < 0} {
936 || [lsearch -exact $crossings($id) $p] < 0} {
937 lappend crossings($id) $p
937 lappend crossings($id) $p
938 }
938 }
939 if {![info exists crossings($p)]
939 if {![info exists crossings($p)]
940 || [lsearch -exact $crossings($p) $id] < 0} {
940 || [lsearch -exact $crossings($p) $id] < 0} {
941 lappend crossings($p) $id
941 lappend crossings($p) $id
942 }
942 }
943 }
943 }
944 }
944 }
945 }
945 }
946
946
947 proc xcoord {i level ln} {
947 proc xcoord {i level ln} {
948 global canvx0 xspc1 xspc2
948 global canvx0 xspc1 xspc2
949
949
950 set x [expr {$canvx0 + $i * $xspc1($ln)}]
950 set x [expr {$canvx0 + $i * $xspc1($ln)}]
951 if {$i > 0 && $i == $level} {
951 if {$i > 0 && $i == $level} {
952 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
952 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
953 } elseif {$i > $level} {
953 } elseif {$i > $level} {
954 set x [expr {$x + $xspc2 - $xspc1($ln)}]
954 set x [expr {$x + $xspc2 - $xspc1($ln)}]
955 }
955 }
956 return $x
956 return $x
957 }
957 }
958
958
959 # it seems Tk can't draw arrows on the end of diagonal line segments...
959 # it seems Tk can't draw arrows on the end of diagonal line segments...
960 proc trimdiagend {line} {
960 proc trimdiagend {line} {
961 while {[llength $line] > 4} {
961 while {[llength $line] > 4} {
962 set x1 [lindex $line end-3]
962 set x1 [lindex $line end-3]
963 set y1 [lindex $line end-2]
963 set y1 [lindex $line end-2]
964 set x2 [lindex $line end-1]
964 set x2 [lindex $line end-1]
965 set y2 [lindex $line end]
965 set y2 [lindex $line end]
966 if {($x1 == $x2) != ($y1 == $y2)} break
966 if {($x1 == $x2) != ($y1 == $y2)} break
967 set line [lreplace $line end-1 end]
967 set line [lreplace $line end-1 end]
968 }
968 }
969 return $line
969 return $line
970 }
970 }
971
971
972 proc trimdiagstart {line} {
972 proc trimdiagstart {line} {
973 while {[llength $line] > 4} {
973 while {[llength $line] > 4} {
974 set x1 [lindex $line 0]
974 set x1 [lindex $line 0]
975 set y1 [lindex $line 1]
975 set y1 [lindex $line 1]
976 set x2 [lindex $line 2]
976 set x2 [lindex $line 2]
977 set y2 [lindex $line 3]
977 set y2 [lindex $line 3]
978 if {($x1 == $x2) != ($y1 == $y2)} break
978 if {($x1 == $x2) != ($y1 == $y2)} break
979 set line [lreplace $line 0 1]
979 set line [lreplace $line 0 1]
980 }
980 }
981 return $line
981 return $line
982 }
982 }
983
983
984 proc drawslants {id needonscreen nohs} {
984 proc drawslants {id needonscreen nohs} {
985 global canv mainline mainlinearrow sidelines
985 global canv mainline mainlinearrow sidelines
986 global canvx0 canvy xspc1 xspc2 lthickness
986 global canvx0 canvy xspc1 xspc2 lthickness
987 global currentparents dupparents
987 global currentparents dupparents
988 global lthickness linespc canvy colormap lineno geometry
988 global lthickness linespc canvy colormap lineno geometry
989 global maxgraphpct maxwidth
989 global maxgraphpct maxwidth
990 global displist onscreen lastuse
990 global displist onscreen lastuse
991 global parents commitlisted
991 global parents commitlisted
992 global oldnlines olddlevel olddisplist
992 global oldnlines olddlevel olddisplist
993 global nhyperspace numcommits nnewparents
993 global nhyperspace numcommits nnewparents
994
994
995 if {$lineno < 0} {
995 if {$lineno < 0} {
996 lappend displist $id
996 lappend displist $id
997 set onscreen($id) 1
997 set onscreen($id) 1
998 return 0
998 return 0
999 }
999 }
1000
1000
1001 set y1 [expr {$canvy - $linespc}]
1001 set y1 [expr {$canvy - $linespc}]
1002 set y2 $canvy
1002 set y2 $canvy
1003
1003
1004 # work out what we need to get back on screen
1004 # work out what we need to get back on screen
1005 set reins {}
1005 set reins {}
1006 if {$onscreen($id) < 0} {
1006 if {$onscreen($id) < 0} {
1007 # next to do isn't displayed, better get it on screen...
1007 # next to do isn't displayed, better get it on screen...
1008 lappend reins [list $id 0]
1008 lappend reins [list $id 0]
1009 }
1009 }
1010 # make sure all the previous commits's parents are on the screen
1010 # make sure all the previous commits's parents are on the screen
1011 foreach p $currentparents {
1011 foreach p $currentparents {
1012 if {$onscreen($p) < 0} {
1012 if {$onscreen($p) < 0} {
1013 lappend reins [list $p 0]
1013 lappend reins [list $p 0]
1014 }
1014 }
1015 }
1015 }
1016 # bring back anything requested by caller
1016 # bring back anything requested by caller
1017 if {$needonscreen ne {}} {
1017 if {$needonscreen ne {}} {
1018 lappend reins $needonscreen
1018 lappend reins $needonscreen
1019 }
1019 }
1020
1020
1021 # try the shortcut
1021 # try the shortcut
1022 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1022 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1023 set dlevel $olddlevel
1023 set dlevel $olddlevel
1024 set x [xcoord $dlevel $dlevel $lineno]
1024 set x [xcoord $dlevel $dlevel $lineno]
1025 set mainline($id) [list $x $y1]
1025 set mainline($id) [list $x $y1]
1026 set mainlinearrow($id) none
1026 set mainlinearrow($id) none
1027 set lastuse($id) $lineno
1027 set lastuse($id) $lineno
1028 set displist [lreplace $displist $dlevel $dlevel $id]
1028 set displist [lreplace $displist $dlevel $dlevel $id]
1029 set onscreen($id) 1
1029 set onscreen($id) 1
1030 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1030 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1031 return $dlevel
1031 return $dlevel
1032 }
1032 }
1033
1033
1034 # update displist
1034 # update displist
1035 set displist [lreplace $displist $olddlevel $olddlevel]
1035 set displist [lreplace $displist $olddlevel $olddlevel]
1036 set j $olddlevel
1036 set j $olddlevel
1037 foreach p $currentparents {
1037 foreach p $currentparents {
1038 set lastuse($p) $lineno
1038 set lastuse($p) $lineno
1039 if {$onscreen($p) == 0} {
1039 if {$onscreen($p) == 0} {
1040 set displist [linsert $displist $j $p]
1040 set displist [linsert $displist $j $p]
1041 set onscreen($p) 1
1041 set onscreen($p) 1
1042 incr j
1042 incr j
1043 }
1043 }
1044 }
1044 }
1045 if {$onscreen($id) == 0} {
1045 if {$onscreen($id) == 0} {
1046 lappend displist $id
1046 lappend displist $id
1047 set onscreen($id) 1
1047 set onscreen($id) 1
1048 }
1048 }
1049
1049
1050 # remove the null entry if present
1050 # remove the null entry if present
1051 set nullentry [lsearch -exact $displist {}]
1051 set nullentry [lsearch -exact $displist {}]
1052 if {$nullentry >= 0} {
1052 if {$nullentry >= 0} {
1053 set displist [lreplace $displist $nullentry $nullentry]
1053 set displist [lreplace $displist $nullentry $nullentry]
1054 }
1054 }
1055
1055
1056 # bring back the ones we need now (if we did it earlier
1056 # bring back the ones we need now (if we did it earlier
1057 # it would change displist and invalidate olddlevel)
1057 # it would change displist and invalidate olddlevel)
1058 foreach pi $reins {
1058 foreach pi $reins {
1059 # test again in case of duplicates in reins
1059 # test again in case of duplicates in reins
1060 set p [lindex $pi 0]
1060 set p [lindex $pi 0]
1061 if {$onscreen($p) < 0} {
1061 if {$onscreen($p) < 0} {
1062 set onscreen($p) 1
1062 set onscreen($p) 1
1063 set lastuse($p) $lineno
1063 set lastuse($p) $lineno
1064 set displist [linsert $displist [lindex $pi 1] $p]
1064 set displist [linsert $displist [lindex $pi 1] $p]
1065 incr nhyperspace -1
1065 incr nhyperspace -1
1066 }
1066 }
1067 }
1067 }
1068
1068
1069 set lastuse($id) $lineno
1069 set lastuse($id) $lineno
1070
1070
1071 # see if we need to make any lines jump off into hyperspace
1071 # see if we need to make any lines jump off into hyperspace
1072 set displ [llength $displist]
1072 set displ [llength $displist]
1073 if {$displ > $maxwidth} {
1073 if {$displ > $maxwidth} {
1074 set ages {}
1074 set ages {}
1075 foreach x $displist {
1075 foreach x $displist {
1076 lappend ages [list $lastuse($x) $x]
1076 lappend ages [list $lastuse($x) $x]
1077 }
1077 }
1078 set ages [lsort -integer -index 0 $ages]
1078 set ages [lsort -integer -index 0 $ages]
1079 set k 0
1079 set k 0
1080 while {$displ > $maxwidth} {
1080 while {$displ > $maxwidth} {
1081 set use [lindex $ages $k 0]
1081 set use [lindex $ages $k 0]
1082 set victim [lindex $ages $k 1]
1082 set victim [lindex $ages $k 1]
1083 if {$use >= $lineno - 5} break
1083 if {$use >= $lineno - 5} break
1084 incr k
1084 incr k
1085 if {[lsearch -exact $nohs $victim] >= 0} continue
1085 if {[lsearch -exact $nohs $victim] >= 0} continue
1086 set i [lsearch -exact $displist $victim]
1086 set i [lsearch -exact $displist $victim]
1087 set displist [lreplace $displist $i $i]
1087 set displist [lreplace $displist $i $i]
1088 set onscreen($victim) -1
1088 set onscreen($victim) -1
1089 incr nhyperspace
1089 incr nhyperspace
1090 incr displ -1
1090 incr displ -1
1091 if {$i < $nullentry} {
1091 if {$i < $nullentry} {
1092 incr nullentry -1
1092 incr nullentry -1
1093 }
1093 }
1094 set x [lindex $mainline($victim) end-1]
1094 set x [lindex $mainline($victim) end-1]
1095 lappend mainline($victim) $x $y1
1095 lappend mainline($victim) $x $y1
1096 set line [trimdiagend $mainline($victim)]
1096 set line [trimdiagend $mainline($victim)]
1097 set arrow "last"
1097 set arrow "last"
1098 if {$mainlinearrow($victim) ne "none"} {
1098 if {$mainlinearrow($victim) ne "none"} {
1099 set line [trimdiagstart $line]
1099 set line [trimdiagstart $line]
1100 set arrow "both"
1100 set arrow "both"
1101 }
1101 }
1102 lappend sidelines($victim) [list $line 1 $arrow]
1102 lappend sidelines($victim) [list $line 1 $arrow]
1103 unset mainline($victim)
1103 unset mainline($victim)
1104 }
1104 }
1105 }
1105 }
1106
1106
1107 set dlevel [lsearch -exact $displist $id]
1107 set dlevel [lsearch -exact $displist $id]
1108
1108
1109 # If we are reducing, put in a null entry
1109 # If we are reducing, put in a null entry
1110 if {$displ < $oldnlines} {
1110 if {$displ < $oldnlines} {
1111 # does the next line look like a merge?
1111 # does the next line look like a merge?
1112 # i.e. does it have > 1 new parent?
1112 # i.e. does it have > 1 new parent?
1113 if {$nnewparents($id) > 1} {
1113 if {$nnewparents($id) > 1} {
1114 set i [expr {$dlevel + 1}]
1114 set i [expr {$dlevel + 1}]
1115 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1115 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1116 set i $olddlevel
1116 set i $olddlevel
1117 if {$nullentry >= 0 && $nullentry < $i} {
1117 if {$nullentry >= 0 && $nullentry < $i} {
1118 incr i -1
1118 incr i -1
1119 }
1119 }
1120 } elseif {$nullentry >= 0} {
1120 } elseif {$nullentry >= 0} {
1121 set i $nullentry
1121 set i $nullentry
1122 while {$i < $displ
1122 while {$i < $displ
1123 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1123 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1124 incr i
1124 incr i
1125 }
1125 }
1126 } else {
1126 } else {
1127 set i $olddlevel
1127 set i $olddlevel
1128 if {$dlevel >= $i} {
1128 if {$dlevel >= $i} {
1129 incr i
1129 incr i
1130 }
1130 }
1131 }
1131 }
1132 if {$i < $displ} {
1132 if {$i < $displ} {
1133 set displist [linsert $displist $i {}]
1133 set displist [linsert $displist $i {}]
1134 incr displ
1134 incr displ
1135 if {$dlevel >= $i} {
1135 if {$dlevel >= $i} {
1136 incr dlevel
1136 incr dlevel
1137 }
1137 }
1138 }
1138 }
1139 }
1139 }
1140
1140
1141 # decide on the line spacing for the next line
1141 # decide on the line spacing for the next line
1142 set lj [expr {$lineno + 1}]
1142 set lj [expr {$lineno + 1}]
1143 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1143 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1144 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1144 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1145 set xspc1($lj) $xspc2
1145 set xspc1($lj) $xspc2
1146 } else {
1146 } else {
1147 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1147 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1148 if {$xspc1($lj) < $lthickness} {
1148 if {$xspc1($lj) < $lthickness} {
1149 set xspc1($lj) $lthickness
1149 set xspc1($lj) $lthickness
1150 }
1150 }
1151 }
1151 }
1152
1152
1153 foreach idi $reins {
1153 foreach idi $reins {
1154 set id [lindex $idi 0]
1154 set id [lindex $idi 0]
1155 set j [lsearch -exact $displist $id]
1155 set j [lsearch -exact $displist $id]
1156 set xj [xcoord $j $dlevel $lj]
1156 set xj [xcoord $j $dlevel $lj]
1157 set mainline($id) [list $xj $y2]
1157 set mainline($id) [list $xj $y2]
1158 set mainlinearrow($id) first
1158 set mainlinearrow($id) first
1159 }
1159 }
1160
1160
1161 set i -1
1161 set i -1
1162 foreach id $olddisplist {
1162 foreach id $olddisplist {
1163 incr i
1163 incr i
1164 if {$id == {}} continue
1164 if {$id == {}} continue
1165 if {$onscreen($id) <= 0} continue
1165 if {$onscreen($id) <= 0} continue
1166 set xi [xcoord $i $olddlevel $lineno]
1166 set xi [xcoord $i $olddlevel $lineno]
1167 if {$i == $olddlevel} {
1167 if {$i == $olddlevel} {
1168 foreach p $currentparents {
1168 foreach p $currentparents {
1169 set j [lsearch -exact $displist $p]
1169 set j [lsearch -exact $displist $p]
1170 set coords [list $xi $y1]
1170 set coords [list $xi $y1]
1171 set xj [xcoord $j $dlevel $lj]
1171 set xj [xcoord $j $dlevel $lj]
1172 if {$xj < $xi - $linespc} {
1172 if {$xj < $xi - $linespc} {
1173 lappend coords [expr {$xj + $linespc}] $y1
1173 lappend coords [expr {$xj + $linespc}] $y1
1174 notecrossings $p $j $i [expr {$j + 1}]
1174 notecrossings $p $j $i [expr {$j + 1}]
1175 } elseif {$xj > $xi + $linespc} {
1175 } elseif {$xj > $xi + $linespc} {
1176 lappend coords [expr {$xj - $linespc}] $y1
1176 lappend coords [expr {$xj - $linespc}] $y1
1177 notecrossings $p $i $j [expr {$j - 1}]
1177 notecrossings $p $i $j [expr {$j - 1}]
1178 }
1178 }
1179 if {[lsearch -exact $dupparents $p] >= 0} {
1179 if {[lsearch -exact $dupparents $p] >= 0} {
1180 # draw a double-width line to indicate the doubled parent
1180 # draw a double-width line to indicate the doubled parent
1181 lappend coords $xj $y2
1181 lappend coords $xj $y2
1182 lappend sidelines($p) [list $coords 2 none]
1182 lappend sidelines($p) [list $coords 2 none]
1183 if {![info exists mainline($p)]} {
1183 if {![info exists mainline($p)]} {
1184 set mainline($p) [list $xj $y2]
1184 set mainline($p) [list $xj $y2]
1185 set mainlinearrow($p) none
1185 set mainlinearrow($p) none
1186 }
1186 }
1187 } else {
1187 } else {
1188 # normal case, no parent duplicated
1188 # normal case, no parent duplicated
1189 set yb $y2
1189 set yb $y2
1190 set dx [expr {abs($xi - $xj)}]
1190 set dx [expr {abs($xi - $xj)}]
1191 if {0 && $dx < $linespc} {
1191 if {0 && $dx < $linespc} {
1192 set yb [expr {$y1 + $dx}]
1192 set yb [expr {$y1 + $dx}]
1193 }
1193 }
1194 if {![info exists mainline($p)]} {
1194 if {![info exists mainline($p)]} {
1195 if {$xi != $xj} {
1195 if {$xi != $xj} {
1196 lappend coords $xj $yb
1196 lappend coords $xj $yb
1197 }
1197 }
1198 set mainline($p) $coords
1198 set mainline($p) $coords
1199 set mainlinearrow($p) none
1199 set mainlinearrow($p) none
1200 } else {
1200 } else {
1201 lappend coords $xj $yb
1201 lappend coords $xj $yb
1202 if {$yb < $y2} {
1202 if {$yb < $y2} {
1203 lappend coords $xj $y2
1203 lappend coords $xj $y2
1204 }
1204 }
1205 lappend sidelines($p) [list $coords 1 none]
1205 lappend sidelines($p) [list $coords 1 none]
1206 }
1206 }
1207 }
1207 }
1208 }
1208 }
1209 } else {
1209 } else {
1210 set j $i
1210 set j $i
1211 if {[lindex $displist $i] != $id} {
1211 if {[lindex $displist $i] != $id} {
1212 set j [lsearch -exact $displist $id]
1212 set j [lsearch -exact $displist $id]
1213 }
1213 }
1214 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1214 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1215 || ($olddlevel < $i && $i < $dlevel)
1215 || ($olddlevel < $i && $i < $dlevel)
1216 || ($dlevel < $i && $i < $olddlevel)} {
1216 || ($dlevel < $i && $i < $olddlevel)} {
1217 set xj [xcoord $j $dlevel $lj]
1217 set xj [xcoord $j $dlevel $lj]
1218 lappend mainline($id) $xi $y1 $xj $y2
1218 lappend mainline($id) $xi $y1 $xj $y2
1219 }
1219 }
1220 }
1220 }
1221 }
1221 }
1222 return $dlevel
1222 return $dlevel
1223 }
1223 }
1224
1224
1225 # search for x in a list of lists
1225 # search for x in a list of lists
1226 proc llsearch {llist x} {
1226 proc llsearch {llist x} {
1227 set i 0
1227 set i 0
1228 foreach l $llist {
1228 foreach l $llist {
1229 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1229 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1230 return $i
1230 return $i
1231 }
1231 }
1232 incr i
1232 incr i
1233 }
1233 }
1234 return -1
1234 return -1
1235 }
1235 }
1236
1236
1237 proc drawmore {reading} {
1237 proc drawmore {reading} {
1238 global displayorder numcommits ncmupdate nextupdate
1238 global displayorder numcommits ncmupdate nextupdate
1239 global stopped nhyperspace parents commitlisted
1239 global stopped nhyperspace parents commitlisted
1240 global maxwidth onscreen displist currentparents olddlevel
1240 global maxwidth onscreen displist currentparents olddlevel
1241
1241
1242 set n [llength $displayorder]
1242 set n [llength $displayorder]
1243 while {$numcommits < $n} {
1243 while {$numcommits < $n} {
1244 set id [lindex $displayorder $numcommits]
1244 set id [lindex $displayorder $numcommits]
1245 set ctxend [expr {$numcommits + 10}]
1245 set ctxend [expr {$numcommits + 10}]
1246 if {!$reading && $ctxend > $n} {
1246 if {!$reading && $ctxend > $n} {
1247 set ctxend $n
1247 set ctxend $n
1248 }
1248 }
1249 set dlist {}
1249 set dlist {}
1250 if {$numcommits > 0} {
1250 if {$numcommits > 0} {
1251 set dlist [lreplace $displist $olddlevel $olddlevel]
1251 set dlist [lreplace $displist $olddlevel $olddlevel]
1252 set i $olddlevel
1252 set i $olddlevel
1253 foreach p $currentparents {
1253 foreach p $currentparents {
1254 if {$onscreen($p) == 0} {
1254 if {$onscreen($p) == 0} {
1255 set dlist [linsert $dlist $i $p]
1255 set dlist [linsert $dlist $i $p]
1256 incr i
1256 incr i
1257 }
1257 }
1258 }
1258 }
1259 }
1259 }
1260 set nohs {}
1260 set nohs {}
1261 set reins {}
1261 set reins {}
1262 set isfat [expr {[llength $dlist] > $maxwidth}]
1262 set isfat [expr {[llength $dlist] > $maxwidth}]
1263 if {$nhyperspace > 0 || $isfat} {
1263 if {$nhyperspace > 0 || $isfat} {
1264 if {$ctxend > $n} break
1264 if {$ctxend > $n} break
1265 # work out what to bring back and
1265 # work out what to bring back and
1266 # what we want to don't want to send into hyperspace
1266 # what we want to don't want to send into hyperspace
1267 set room 1
1267 set room 1
1268 for {set k $numcommits} {$k < $ctxend} {incr k} {
1268 for {set k $numcommits} {$k < $ctxend} {incr k} {
1269 set x [lindex $displayorder $k]
1269 set x [lindex $displayorder $k]
1270 set i [llsearch $dlist $x]
1270 set i [llsearch $dlist $x]
1271 if {$i < 0} {
1271 if {$i < 0} {
1272 set i [llength $dlist]
1272 set i [llength $dlist]
1273 lappend dlist $x
1273 lappend dlist $x
1274 }
1274 }
1275 if {[lsearch -exact $nohs $x] < 0} {
1275 if {[lsearch -exact $nohs $x] < 0} {
1276 lappend nohs $x
1276 lappend nohs $x
1277 }
1277 }
1278 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1278 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1279 set reins [list $x $i]
1279 set reins [list $x $i]
1280 }
1280 }
1281 set newp {}
1281 set newp {}
1282 if {[info exists commitlisted($x)]} {
1282 if {[info exists commitlisted($x)]} {
1283 set right 0
1283 set right 0
1284 foreach p $parents($x) {
1284 foreach p $parents($x) {
1285 if {[llsearch $dlist $p] < 0} {
1285 if {[llsearch $dlist $p] < 0} {
1286 lappend newp $p
1286 lappend newp $p
1287 if {[lsearch -exact $nohs $p] < 0} {
1287 if {[lsearch -exact $nohs $p] < 0} {
1288 lappend nohs $p
1288 lappend nohs $p
1289 }
1289 }
1290 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1290 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1291 set reins [list $p [expr {$i + $right}]]
1291 set reins [list $p [expr {$i + $right}]]
1292 }
1292 }
1293 }
1293 }
1294 set right 1
1294 set right 1
1295 }
1295 }
1296 }
1296 }
1297 set l [lindex $dlist $i]
1297 set l [lindex $dlist $i]
1298 if {[llength $l] == 1} {
1298 if {[llength $l] == 1} {
1299 set l $newp
1299 set l $newp
1300 } else {
1300 } else {
1301 set j [lsearch -exact $l $x]
1301 set j [lsearch -exact $l $x]
1302 set l [concat [lreplace $l $j $j] $newp]
1302 set l [concat [lreplace $l $j $j] $newp]
1303 }
1303 }
1304 set dlist [lreplace $dlist $i $i $l]
1304 set dlist [lreplace $dlist $i $i $l]
1305 if {$room && $isfat && [llength $newp] <= 1} {
1305 if {$room && $isfat && [llength $newp] <= 1} {
1306 set room 0
1306 set room 0
1307 }
1307 }
1308 }
1308 }
1309 }
1309 }
1310
1310
1311 set dlevel [drawslants $id $reins $nohs]
1311 set dlevel [drawslants $id $reins $nohs]
1312 drawcommitline $dlevel
1312 drawcommitline $dlevel
1313 if {[clock clicks -milliseconds] >= $nextupdate
1313 if {[clock clicks -milliseconds] >= $nextupdate
1314 && $numcommits >= $ncmupdate} {
1314 && $numcommits >= $ncmupdate} {
1315 doupdate $reading
1315 doupdate $reading
1316 if {$stopped} break
1316 if {$stopped} break
1317 }
1317 }
1318 }
1318 }
1319 }
1319 }
1320
1320
1321 # level here is an index in todo
1321 # level here is an index in todo
1322 proc updatetodo {level noshortcut} {
1322 proc updatetodo {level noshortcut} {
1323 global ncleft todo nnewparents
1323 global ncleft todo nnewparents
1324 global commitlisted parents onscreen
1324 global commitlisted parents onscreen
1325
1325
1326 set id [lindex $todo $level]
1326 set id [lindex $todo $level]
1327 set olds {}
1327 set olds {}
1328 if {[info exists commitlisted($id)]} {
1328 if {[info exists commitlisted($id)]} {
1329 foreach p $parents($id) {
1329 foreach p $parents($id) {
1330 if {[lsearch -exact $olds $p] < 0} {
1330 if {[lsearch -exact $olds $p] < 0} {
1331 lappend olds $p
1331 lappend olds $p
1332 }
1332 }
1333 }
1333 }
1334 }
1334 }
1335 if {!$noshortcut && [llength $olds] == 1} {
1335 if {!$noshortcut && [llength $olds] == 1} {
1336 set p [lindex $olds 0]
1336 set p [lindex $olds 0]
1337 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1337 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1338 set ncleft($p) 0
1338 set ncleft($p) 0
1339 set todo [lreplace $todo $level $level $p]
1339 set todo [lreplace $todo $level $level $p]
1340 set onscreen($p) 0
1340 set onscreen($p) 0
1341 set nnewparents($id) 1
1341 set nnewparents($id) 1
1342 return 0
1342 return 0
1343 }
1343 }
1344 }
1344 }
1345
1345
1346 set todo [lreplace $todo $level $level]
1346 set todo [lreplace $todo $level $level]
1347 set i $level
1347 set i $level
1348 set n 0
1348 set n 0
1349 foreach p $olds {
1349 foreach p $olds {
1350 incr ncleft($p) -1
1350 incr ncleft($p) -1
1351 set k [lsearch -exact $todo $p]
1351 set k [lsearch -exact $todo $p]
1352 if {$k < 0} {
1352 if {$k < 0} {
1353 set todo [linsert $todo $i $p]
1353 set todo [linsert $todo $i $p]
1354 set onscreen($p) 0
1354 set onscreen($p) 0
1355 incr i
1355 incr i
1356 incr n
1356 incr n
1357 }
1357 }
1358 }
1358 }
1359 set nnewparents($id) $n
1359 set nnewparents($id) $n
1360
1360
1361 return 1
1361 return 1
1362 }
1362 }
1363
1363
1364 proc decidenext {{noread 0}} {
1364 proc decidenext {{noread 0}} {
1365 global ncleft todo
1365 global ncleft todo
1366 global datemode cdate
1366 global datemode cdate
1367 global commitinfo
1367 global commitinfo
1368
1368
1369 # choose which one to do next time around
1369 # choose which one to do next time around
1370 set todol [llength $todo]
1370 set todol [llength $todo]
1371 set level -1
1371 set level -1
1372 set latest {}
1372 set latest {}
1373 for {set k $todol} {[incr k -1] >= 0} {} {
1373 for {set k $todol} {[incr k -1] >= 0} {} {
1374 set p [lindex $todo $k]
1374 set p [lindex $todo $k]
1375 if {$ncleft($p) == 0} {
1375 if {$ncleft($p) == 0} {
1376 if {$datemode} {
1376 if {$datemode} {
1377 if {![info exists commitinfo($p)]} {
1377 if {![info exists commitinfo($p)]} {
1378 if {$noread} {
1378 if {$noread} {
1379 return {}
1379 return {}
1380 }
1380 }
1381 readcommit $p
1381 readcommit $p
1382 }
1382 }
1383 if {$latest == {} || $cdate($p) > $latest} {
1383 if {$latest == {} || $cdate($p) > $latest} {
1384 set level $k
1384 set level $k
1385 set latest $cdate($p)
1385 set latest $cdate($p)
1386 }
1386 }
1387 } else {
1387 } else {
1388 set level $k
1388 set level $k
1389 break
1389 break
1390 }
1390 }
1391 }
1391 }
1392 }
1392 }
1393 if {$level < 0} {
1393 if {$level < 0} {
1394 if {$todo != {}} {
1394 if {$todo != {}} {
1395 puts "ERROR: none of the pending commits can be done yet:"
1395 puts "ERROR: none of the pending commits can be done yet:"
1396 foreach p $todo {
1396 foreach p $todo {
1397 puts " $p ($ncleft($p))"
1397 puts " $p ($ncleft($p))"
1398 }
1398 }
1399 }
1399 }
1400 return -1
1400 return -1
1401 }
1401 }
1402
1402
1403 return $level
1403 return $level
1404 }
1404 }
1405
1405
1406 proc drawcommit {id} {
1406 proc drawcommit {id} {
1407 global phase todo nchildren datemode nextupdate
1407 global phase todo nchildren datemode nextupdate
1408 global numcommits ncmupdate displayorder todo onscreen
1408 global numcommits ncmupdate displayorder todo onscreen
1409
1409
1410 if {$phase != "incrdraw"} {
1410 if {$phase != "incrdraw"} {
1411 set phase incrdraw
1411 set phase incrdraw
1412 set displayorder {}
1412 set displayorder {}
1413 set todo {}
1413 set todo {}
1414 initgraph
1414 initgraph
1415 }
1415 }
1416 if {$nchildren($id) == 0} {
1416 if {$nchildren($id) == 0} {
1417 lappend todo $id
1417 lappend todo $id
1418 set onscreen($id) 0
1418 set onscreen($id) 0
1419 }
1419 }
1420 set level [decidenext 1]
1420 set level [decidenext 1]
1421 if {$level == {} || $id != [lindex $todo $level]} {
1421 if {$level == {} || $id != [lindex $todo $level]} {
1422 return
1422 return
1423 }
1423 }
1424 while 1 {
1424 while 1 {
1425 lappend displayorder [lindex $todo $level]
1425 lappend displayorder [lindex $todo $level]
1426 if {[updatetodo $level $datemode]} {
1426 if {[updatetodo $level $datemode]} {
1427 set level [decidenext 1]
1427 set level [decidenext 1]
1428 if {$level == {}} break
1428 if {$level == {}} break
1429 }
1429 }
1430 set id [lindex $todo $level]
1430 set id [lindex $todo $level]
1431 if {![info exists commitlisted($id)]} {
1431 if {![info exists commitlisted($id)]} {
1432 break
1432 break
1433 }
1433 }
1434 }
1434 }
1435 drawmore 1
1435 drawmore 1
1436 }
1436 }
1437
1437
1438 proc finishcommits {} {
1438 proc finishcommits {} {
1439 global phase
1439 global phase
1440 global canv mainfont ctext maincursor textcursor
1440 global canv mainfont ctext maincursor textcursor
1441
1441
1442 if {$phase != "incrdraw"} {
1442 if {$phase != "incrdraw"} {
1443 $canv delete all
1443 $canv delete all
1444 $canv create text 3 3 -anchor nw -text "No commits selected" \
1444 $canv create text 3 3 -anchor nw -text "No commits selected" \
1445 -font $mainfont -tags textitems
1445 -font $mainfont -tags textitems
1446 set phase {}
1446 set phase {}
1447 } else {
1447 } else {
1448 drawrest
1448 drawrest
1449 }
1449 }
1450 . config -cursor $maincursor
1450 . config -cursor $maincursor
1451 settextcursor $textcursor
1451 settextcursor $textcursor
1452 }
1452 }
1453
1453
1454 # Don't change the text pane cursor if it is currently the hand cursor,
1454 # Don't change the text pane cursor if it is currently the hand cursor,
1455 # showing that we are over a sha1 ID link.
1455 # showing that we are over a sha1 ID link.
1456 proc settextcursor {c} {
1456 proc settextcursor {c} {
1457 global ctext curtextcursor
1457 global ctext curtextcursor
1458
1458
1459 if {[$ctext cget -cursor] == $curtextcursor} {
1459 if {[$ctext cget -cursor] == $curtextcursor} {
1460 $ctext config -cursor $c
1460 $ctext config -cursor $c
1461 }
1461 }
1462 set curtextcursor $c
1462 set curtextcursor $c
1463 }
1463 }
1464
1464
1465 proc drawgraph {} {
1465 proc drawgraph {} {
1466 global nextupdate startmsecs ncmupdate
1466 global nextupdate startmsecs ncmupdate
1467 global displayorder onscreen
1467 global displayorder onscreen
1468
1468
1469 if {$displayorder == {}} return
1469 if {$displayorder == {}} return
1470 set startmsecs [clock clicks -milliseconds]
1470 set startmsecs [clock clicks -milliseconds]
1471 set nextupdate [expr $startmsecs + 100]
1471 set nextupdate [expr $startmsecs + 100]
1472 set ncmupdate 1
1472 set ncmupdate 1
1473 initgraph
1473 initgraph
1474 foreach id $displayorder {
1474 foreach id $displayorder {
1475 set onscreen($id) 0
1475 set onscreen($id) 0
1476 }
1476 }
1477 drawmore 0
1477 drawmore 0
1478 }
1478 }
1479
1479
1480 proc drawrest {} {
1480 proc drawrest {} {
1481 global phase stopped redisplaying selectedline
1481 global phase stopped redisplaying selectedline
1482 global datemode todo displayorder
1482 global datemode todo displayorder
1483 global numcommits ncmupdate
1483 global numcommits ncmupdate
1484 global nextupdate startmsecs
1484 global nextupdate startmsecs
1485
1485
1486 set level [decidenext]
1486 set level [decidenext]
1487 if {$level >= 0} {
1487 if {$level >= 0} {
1488 set phase drawgraph
1488 set phase drawgraph
1489 while 1 {
1489 while 1 {
1490 lappend displayorder [lindex $todo $level]
1490 lappend displayorder [lindex $todo $level]
1491 set hard [updatetodo $level $datemode]
1491 set hard [updatetodo $level $datemode]
1492 if {$hard} {
1492 if {$hard} {
1493 set level [decidenext]
1493 set level [decidenext]
1494 if {$level < 0} break
1494 if {$level < 0} break
1495 }
1495 }
1496 }
1496 }
1497 drawmore 0
1497 drawmore 0
1498 }
1498 }
1499 set phase {}
1499 set phase {}
1500 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1500 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1501 #puts "overall $drawmsecs ms for $numcommits commits"
1501 #puts "overall $drawmsecs ms for $numcommits commits"
1502 if {$redisplaying} {
1502 if {$redisplaying} {
1503 if {$stopped == 0 && [info exists selectedline]} {
1503 if {$stopped == 0 && [info exists selectedline]} {
1504 selectline $selectedline 0
1504 selectline $selectedline 0
1505 }
1505 }
1506 if {$stopped == 1} {
1506 if {$stopped == 1} {
1507 set stopped 0
1507 set stopped 0
1508 after idle drawgraph
1508 after idle drawgraph
1509 } else {
1509 } else {
1510 set redisplaying 0
1510 set redisplaying 0
1511 }
1511 }
1512 }
1512 }
1513 }
1513 }
1514
1514
1515 proc findmatches {f} {
1515 proc findmatches {f} {
1516 global findtype foundstring foundstrlen
1516 global findtype foundstring foundstrlen
1517 if {$findtype == "Regexp"} {
1517 if {$findtype == "Regexp"} {
1518 set matches [regexp -indices -all -inline $foundstring $f]
1518 set matches [regexp -indices -all -inline $foundstring $f]
1519 } else {
1519 } else {
1520 if {$findtype == "IgnCase"} {
1520 if {$findtype == "IgnCase"} {
1521 set str [string tolower $f]
1521 set str [string tolower $f]
1522 } else {
1522 } else {
1523 set str $f
1523 set str $f
1524 }
1524 }
1525 set matches {}
1525 set matches {}
1526 set i 0
1526 set i 0
1527 while {[set j [string first $foundstring $str $i]] >= 0} {
1527 while {[set j [string first $foundstring $str $i]] >= 0} {
1528 lappend matches [list $j [expr $j+$foundstrlen-1]]
1528 lappend matches [list $j [expr $j+$foundstrlen-1]]
1529 set i [expr $j + $foundstrlen]
1529 set i [expr $j + $foundstrlen]
1530 }
1530 }
1531 }
1531 }
1532 return $matches
1532 return $matches
1533 }
1533 }
1534
1534
1535 proc dofind {} {
1535 proc dofind {} {
1536 global findtype findloc findstring markedmatches commitinfo
1536 global findtype findloc findstring markedmatches commitinfo
1537 global numcommits lineid linehtag linentag linedtag
1537 global numcommits lineid linehtag linentag linedtag
1538 global mainfont namefont canv canv2 canv3 selectedline
1538 global mainfont namefont canv canv2 canv3 selectedline
1539 global matchinglines foundstring foundstrlen
1539 global matchinglines foundstring foundstrlen
1540
1540
1541 stopfindproc
1541 stopfindproc
1542 unmarkmatches
1542 unmarkmatches
1543 focus .
1543 focus .
1544 set matchinglines {}
1544 set matchinglines {}
1545 if {$findloc == "Pickaxe"} {
1545 if {$findloc == "Pickaxe"} {
1546 findpatches
1546 findpatches
1547 return
1547 return
1548 }
1548 }
1549 if {$findtype == "IgnCase"} {
1549 if {$findtype == "IgnCase"} {
1550 set foundstring [string tolower $findstring]
1550 set foundstring [string tolower $findstring]
1551 } else {
1551 } else {
1552 set foundstring $findstring
1552 set foundstring $findstring
1553 }
1553 }
1554 set foundstrlen [string length $findstring]
1554 set foundstrlen [string length $findstring]
1555 if {$foundstrlen == 0} return
1555 if {$foundstrlen == 0} return
1556 if {$findloc == "Files"} {
1556 if {$findloc == "Files"} {
1557 findfiles
1557 findfiles
1558 return
1558 return
1559 }
1559 }
1560 if {![info exists selectedline]} {
1560 if {![info exists selectedline]} {
1561 set oldsel -1
1561 set oldsel -1
1562 } else {
1562 } else {
1563 set oldsel $selectedline
1563 set oldsel $selectedline
1564 }
1564 }
1565 set didsel 0
1565 set didsel 0
1566 set fldtypes {Headline Author Date Committer CDate Comment}
1566 set fldtypes {Headline Author Date Committer CDate Comment}
1567 for {set l 0} {$l < $numcommits} {incr l} {
1567 for {set l 0} {$l < $numcommits} {incr l} {
1568 set id $lineid($l)
1568 set id $lineid($l)
1569 set info $commitinfo($id)
1569 set info $commitinfo($id)
1570 set doesmatch 0
1570 set doesmatch 0
1571 foreach f $info ty $fldtypes {
1571 foreach f $info ty $fldtypes {
1572 if {$findloc != "All fields" && $findloc != $ty} {
1572 if {$findloc != "All fields" && $findloc != $ty} {
1573 continue
1573 continue
1574 }
1574 }
1575 set matches [findmatches $f]
1575 set matches [findmatches $f]
1576 if {$matches == {}} continue
1576 if {$matches == {}} continue
1577 set doesmatch 1
1577 set doesmatch 1
1578 if {$ty == "Headline"} {
1578 if {$ty == "Headline"} {
1579 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1579 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1580 } elseif {$ty == "Author"} {
1580 } elseif {$ty == "Author"} {
1581 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1581 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1582 } elseif {$ty == "Date"} {
1582 } elseif {$ty == "Date"} {
1583 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1583 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1584 }
1584 }
1585 }
1585 }
1586 if {$doesmatch} {
1586 if {$doesmatch} {
1587 lappend matchinglines $l
1587 lappend matchinglines $l
1588 if {!$didsel && $l > $oldsel} {
1588 if {!$didsel && $l > $oldsel} {
1589 findselectline $l
1589 findselectline $l
1590 set didsel 1
1590 set didsel 1
1591 }
1591 }
1592 }
1592 }
1593 }
1593 }
1594 if {$matchinglines == {}} {
1594 if {$matchinglines == {}} {
1595 bell
1595 bell
1596 } elseif {!$didsel} {
1596 } elseif {!$didsel} {
1597 findselectline [lindex $matchinglines 0]
1597 findselectline [lindex $matchinglines 0]
1598 }
1598 }
1599 }
1599 }
1600
1600
1601 proc findselectline {l} {
1601 proc findselectline {l} {
1602 global findloc commentend ctext
1602 global findloc commentend ctext
1603 selectline $l 1
1603 selectline $l 1
1604 if {$findloc == "All fields" || $findloc == "Comments"} {
1604 if {$findloc == "All fields" || $findloc == "Comments"} {
1605 # highlight the matches in the comments
1605 # highlight the matches in the comments
1606 set f [$ctext get 1.0 $commentend]
1606 set f [$ctext get 1.0 $commentend]
1607 set matches [findmatches $f]
1607 set matches [findmatches $f]
1608 foreach match $matches {
1608 foreach match $matches {
1609 set start [lindex $match 0]
1609 set start [lindex $match 0]
1610 set end [expr [lindex $match 1] + 1]
1610 set end [expr [lindex $match 1] + 1]
1611 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1611 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1612 }
1612 }
1613 }
1613 }
1614 }
1614 }
1615
1615
1616 proc findnext {restart} {
1616 proc findnext {restart} {
1617 global matchinglines selectedline
1617 global matchinglines selectedline
1618 if {![info exists matchinglines]} {
1618 if {![info exists matchinglines]} {
1619 if {$restart} {
1619 if {$restart} {
1620 dofind
1620 dofind
1621 }
1621 }
1622 return
1622 return
1623 }
1623 }
1624 if {![info exists selectedline]} return
1624 if {![info exists selectedline]} return
1625 foreach l $matchinglines {
1625 foreach l $matchinglines {
1626 if {$l > $selectedline} {
1626 if {$l > $selectedline} {
1627 findselectline $l
1627 findselectline $l
1628 return
1628 return
1629 }
1629 }
1630 }
1630 }
1631 bell
1631 bell
1632 }
1632 }
1633
1633
1634 proc findprev {} {
1634 proc findprev {} {
1635 global matchinglines selectedline
1635 global matchinglines selectedline
1636 if {![info exists matchinglines]} {
1636 if {![info exists matchinglines]} {
1637 dofind
1637 dofind
1638 return
1638 return
1639 }
1639 }
1640 if {![info exists selectedline]} return
1640 if {![info exists selectedline]} return
1641 set prev {}
1641 set prev {}
1642 foreach l $matchinglines {
1642 foreach l $matchinglines {
1643 if {$l >= $selectedline} break
1643 if {$l >= $selectedline} break
1644 set prev $l
1644 set prev $l
1645 }
1645 }
1646 if {$prev != {}} {
1646 if {$prev != {}} {
1647 findselectline $prev
1647 findselectline $prev
1648 } else {
1648 } else {
1649 bell
1649 bell
1650 }
1650 }
1651 }
1651 }
1652
1652
1653 proc findlocchange {name ix op} {
1653 proc findlocchange {name ix op} {
1654 global findloc findtype findtypemenu
1654 global findloc findtype findtypemenu
1655 if {$findloc == "Pickaxe"} {
1655 if {$findloc == "Pickaxe"} {
1656 set findtype Exact
1656 set findtype Exact
1657 set state disabled
1657 set state disabled
1658 } else {
1658 } else {
1659 set state normal
1659 set state normal
1660 }
1660 }
1661 $findtypemenu entryconf 1 -state $state
1661 $findtypemenu entryconf 1 -state $state
1662 $findtypemenu entryconf 2 -state $state
1662 $findtypemenu entryconf 2 -state $state
1663 }
1663 }
1664
1664
1665 proc stopfindproc {{done 0}} {
1665 proc stopfindproc {{done 0}} {
1666 global findprocpid findprocfile findids
1666 global findprocpid findprocfile findids
1667 global ctext findoldcursor phase maincursor textcursor
1667 global ctext findoldcursor phase maincursor textcursor
1668 global findinprogress
1668 global findinprogress
1669
1669
1670 catch {unset findids}
1670 catch {unset findids}
1671 if {[info exists findprocpid]} {
1671 if {[info exists findprocpid]} {
1672 if {!$done} {
1672 if {!$done} {
1673 catch {exec kill $findprocpid}
1673 catch {exec kill $findprocpid}
1674 }
1674 }
1675 catch {close $findprocfile}
1675 catch {close $findprocfile}
1676 unset findprocpid
1676 unset findprocpid
1677 }
1677 }
1678 if {[info exists findinprogress]} {
1678 if {[info exists findinprogress]} {
1679 unset findinprogress
1679 unset findinprogress
1680 if {$phase != "incrdraw"} {
1680 if {$phase != "incrdraw"} {
1681 . config -cursor $maincursor
1681 . config -cursor $maincursor
1682 settextcursor $textcursor
1682 settextcursor $textcursor
1683 }
1683 }
1684 }
1684 }
1685 }
1685 }
1686
1686
1687 proc findpatches {} {
1687 proc findpatches {} {
1688 global findstring selectedline numcommits
1688 global findstring selectedline numcommits
1689 global findprocpid findprocfile
1689 global findprocpid findprocfile
1690 global finddidsel ctext lineid findinprogress
1690 global finddidsel ctext lineid findinprogress
1691 global findinsertpos
1691 global findinsertpos
1692
1692
1693 if {$numcommits == 0} return
1693 if {$numcommits == 0} return
1694
1694
1695 # make a list of all the ids to search, starting at the one
1695 # make a list of all the ids to search, starting at the one
1696 # after the selected line (if any)
1696 # after the selected line (if any)
1697 if {[info exists selectedline]} {
1697 if {[info exists selectedline]} {
1698 set l $selectedline
1698 set l $selectedline
1699 } else {
1699 } else {
1700 set l -1
1700 set l -1
1701 }
1701 }
1702 set inputids {}
1702 set inputids {}
1703 for {set i 0} {$i < $numcommits} {incr i} {
1703 for {set i 0} {$i < $numcommits} {incr i} {
1704 if {[incr l] >= $numcommits} {
1704 if {[incr l] >= $numcommits} {
1705 set l 0
1705 set l 0
1706 }
1706 }
1707 append inputids $lineid($l) "\n"
1707 append inputids $lineid($l) "\n"
1708 }
1708 }
1709
1709
1710 if {[catch {
1710 if {[catch {
1711 set f [open [list | hg debug-diff-tree --stdin -s -r -S$findstring \
1711 set f [open [list | hg debug-diff-tree --stdin -s -r -S$findstring \
1712 << $inputids] r]
1712 << $inputids] r]
1713 } err]} {
1713 } err]} {
1714 error_popup "Error starting search process: $err"
1714 error_popup "Error starting search process: $err"
1715 return
1715 return
1716 }
1716 }
1717
1717
1718 set findinsertpos end
1718 set findinsertpos end
1719 set findprocfile $f
1719 set findprocfile $f
1720 set findprocpid [pid $f]
1720 set findprocpid [pid $f]
1721 fconfigure $f -blocking 0
1721 fconfigure $f -blocking 0
1722 fileevent $f readable readfindproc
1722 fileevent $f readable readfindproc
1723 set finddidsel 0
1723 set finddidsel 0
1724 . config -cursor watch
1724 . config -cursor watch
1725 settextcursor watch
1725 settextcursor watch
1726 set findinprogress 1
1726 set findinprogress 1
1727 }
1727 }
1728
1728
1729 proc readfindproc {} {
1729 proc readfindproc {} {
1730 global findprocfile finddidsel
1730 global findprocfile finddidsel
1731 global idline matchinglines findinsertpos
1731 global idline matchinglines findinsertpos
1732
1732
1733 set n [gets $findprocfile line]
1733 set n [gets $findprocfile line]
1734 if {$n < 0} {
1734 if {$n < 0} {
1735 if {[eof $findprocfile]} {
1735 if {[eof $findprocfile]} {
1736 stopfindproc 1
1736 stopfindproc 1
1737 if {!$finddidsel} {
1737 if {!$finddidsel} {
1738 bell
1738 bell
1739 }
1739 }
1740 }
1740 }
1741 return
1741 return
1742 }
1742 }
1743 if {![regexp {^[0-9a-f]{40}} $line id]} {
1743 if {![regexp {^[0-9a-f]{40}} $line id]} {
1744 error_popup "Can't parse git-diff-tree output: $line"
1744 error_popup "Can't parse git-diff-tree output: $line"
1745 stopfindproc
1745 stopfindproc
1746 return
1746 return
1747 }
1747 }
1748 if {![info exists idline($id)]} {
1748 if {![info exists idline($id)]} {
1749 puts stderr "spurious id: $id"
1749 puts stderr "spurious id: $id"
1750 return
1750 return
1751 }
1751 }
1752 set l $idline($id)
1752 set l $idline($id)
1753 insertmatch $l $id
1753 insertmatch $l $id
1754 }
1754 }
1755
1755
1756 proc insertmatch {l id} {
1756 proc insertmatch {l id} {
1757 global matchinglines findinsertpos finddidsel
1757 global matchinglines findinsertpos finddidsel
1758
1758
1759 if {$findinsertpos == "end"} {
1759 if {$findinsertpos == "end"} {
1760 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1760 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1761 set matchinglines [linsert $matchinglines 0 $l]
1761 set matchinglines [linsert $matchinglines 0 $l]
1762 set findinsertpos 1
1762 set findinsertpos 1
1763 } else {
1763 } else {
1764 lappend matchinglines $l
1764 lappend matchinglines $l
1765 }
1765 }
1766 } else {
1766 } else {
1767 set matchinglines [linsert $matchinglines $findinsertpos $l]
1767 set matchinglines [linsert $matchinglines $findinsertpos $l]
1768 incr findinsertpos
1768 incr findinsertpos
1769 }
1769 }
1770 markheadline $l $id
1770 markheadline $l $id
1771 if {!$finddidsel} {
1771 if {!$finddidsel} {
1772 findselectline $l
1772 findselectline $l
1773 set finddidsel 1
1773 set finddidsel 1
1774 }
1774 }
1775 }
1775 }
1776
1776
1777 proc findfiles {} {
1777 proc findfiles {} {
1778 global selectedline numcommits lineid ctext
1778 global selectedline numcommits lineid ctext
1779 global ffileline finddidsel parents nparents
1779 global ffileline finddidsel parents nparents
1780 global findinprogress findstartline findinsertpos
1780 global findinprogress findstartline findinsertpos
1781 global treediffs fdiffids fdiffsneeded fdiffpos
1781 global treediffs fdiffids fdiffsneeded fdiffpos
1782 global findmergefiles
1782 global findmergefiles
1783
1783
1784 if {$numcommits == 0} return
1784 if {$numcommits == 0} return
1785
1785
1786 if {[info exists selectedline]} {
1786 if {[info exists selectedline]} {
1787 set l [expr {$selectedline + 1}]
1787 set l [expr {$selectedline + 1}]
1788 } else {
1788 } else {
1789 set l 0
1789 set l 0
1790 }
1790 }
1791 set ffileline $l
1791 set ffileline $l
1792 set findstartline $l
1792 set findstartline $l
1793 set diffsneeded {}
1793 set diffsneeded {}
1794 set fdiffsneeded {}
1794 set fdiffsneeded {}
1795 while 1 {
1795 while 1 {
1796 set id $lineid($l)
1796 set id $lineid($l)
1797 if {$findmergefiles || $nparents($id) == 1} {
1797 if {$findmergefiles || $nparents($id) == 1} {
1798 foreach p $parents($id) {
1798 foreach p $parents($id) {
1799 if {![info exists treediffs([list $id $p])]} {
1799 if {![info exists treediffs([list $id $p])]} {
1800 append diffsneeded "$id $p\n"
1800 append diffsneeded "$id $p\n"
1801 lappend fdiffsneeded [list $id $p]
1801 lappend fdiffsneeded [list $id $p]
1802 }
1802 }
1803 }
1803 }
1804 }
1804 }
1805 if {[incr l] >= $numcommits} {
1805 if {[incr l] >= $numcommits} {
1806 set l 0
1806 set l 0
1807 }
1807 }
1808 if {$l == $findstartline} break
1808 if {$l == $findstartline} break
1809 }
1809 }
1810
1810
1811 # start off a git-diff-tree process if needed
1811 # start off a git-diff-tree process if needed
1812 if {$diffsneeded ne {}} {
1812 if {$diffsneeded ne {}} {
1813 if {[catch {
1813 if {[catch {
1814 set df [open [list | hg debug-diff-tree -r --stdin << $diffsneeded] r]
1814 set df [open [list | hg debug-diff-tree -r --stdin << $diffsneeded] r]
1815 } err ]} {
1815 } err ]} {
1816 error_popup "Error starting search process: $err"
1816 error_popup "Error starting search process: $err"
1817 return
1817 return
1818 }
1818 }
1819 catch {unset fdiffids}
1819 catch {unset fdiffids}
1820 set fdiffpos 0
1820 set fdiffpos 0
1821 fconfigure $df -blocking 0
1821 fconfigure $df -blocking 0
1822 fileevent $df readable [list readfilediffs $df]
1822 fileevent $df readable [list readfilediffs $df]
1823 }
1823 }
1824
1824
1825 set finddidsel 0
1825 set finddidsel 0
1826 set findinsertpos end
1826 set findinsertpos end
1827 set id $lineid($l)
1827 set id $lineid($l)
1828 set p [lindex $parents($id) 0]
1828 set p [lindex $parents($id) 0]
1829 . config -cursor watch
1829 . config -cursor watch
1830 settextcursor watch
1830 settextcursor watch
1831 set findinprogress 1
1831 set findinprogress 1
1832 findcont [list $id $p]
1832 findcont [list $id $p]
1833 update
1833 update
1834 }
1834 }
1835
1835
1836 proc readfilediffs {df} {
1836 proc readfilediffs {df} {
1837 global findids fdiffids fdiffs
1837 global findids fdiffids fdiffs
1838
1838
1839 set n [gets $df line]
1839 set n [gets $df line]
1840 if {$n < 0} {
1840 if {$n < 0} {
1841 if {[eof $df]} {
1841 if {[eof $df]} {
1842 donefilediff
1842 donefilediff
1843 if {[catch {close $df} err]} {
1843 if {[catch {close $df} err]} {
1844 stopfindproc
1844 stopfindproc
1845 bell
1845 bell
1846 error_popup "Error in hg debug-diff-tree: $err"
1846 error_popup "Error in hg debug-diff-tree: $err"
1847 } elseif {[info exists findids]} {
1847 } elseif {[info exists findids]} {
1848 set ids $findids
1848 set ids $findids
1849 stopfindproc
1849 stopfindproc
1850 bell
1850 bell
1851 error_popup "Couldn't find diffs for {$ids}"
1851 error_popup "Couldn't find diffs for {$ids}"
1852 }
1852 }
1853 }
1853 }
1854 return
1854 return
1855 }
1855 }
1856 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1856 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1857 # start of a new string of diffs
1857 # start of a new string of diffs
1858 donefilediff
1858 donefilediff
1859 set fdiffids [list $id $p]
1859 set fdiffids [list $id $p]
1860 set fdiffs {}
1860 set fdiffs {}
1861 } elseif {[string match ":*" $line]} {
1861 } elseif {[string match ":*" $line]} {
1862 lappend fdiffs [lindex $line 5]
1862 lappend fdiffs [lindex $line 5]
1863 }
1863 }
1864 }
1864 }
1865
1865
1866 proc donefilediff {} {
1866 proc donefilediff {} {
1867 global fdiffids fdiffs treediffs findids
1867 global fdiffids fdiffs treediffs findids
1868 global fdiffsneeded fdiffpos
1868 global fdiffsneeded fdiffpos
1869
1869
1870 if {[info exists fdiffids]} {
1870 if {[info exists fdiffids]} {
1871 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1871 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1872 && $fdiffpos < [llength $fdiffsneeded]} {
1872 && $fdiffpos < [llength $fdiffsneeded]} {
1873 # git-diff-tree doesn't output anything for a commit
1873 # git-diff-tree doesn't output anything for a commit
1874 # which doesn't change anything
1874 # which doesn't change anything
1875 set nullids [lindex $fdiffsneeded $fdiffpos]
1875 set nullids [lindex $fdiffsneeded $fdiffpos]
1876 set treediffs($nullids) {}
1876 set treediffs($nullids) {}
1877 if {[info exists findids] && $nullids eq $findids} {
1877 if {[info exists findids] && $nullids eq $findids} {
1878 unset findids
1878 unset findids
1879 findcont $nullids
1879 findcont $nullids
1880 }
1880 }
1881 incr fdiffpos
1881 incr fdiffpos
1882 }
1882 }
1883 incr fdiffpos
1883 incr fdiffpos
1884
1884
1885 if {![info exists treediffs($fdiffids)]} {
1885 if {![info exists treediffs($fdiffids)]} {
1886 set treediffs($fdiffids) $fdiffs
1886 set treediffs($fdiffids) $fdiffs
1887 }
1887 }
1888 if {[info exists findids] && $fdiffids eq $findids} {
1888 if {[info exists findids] && $fdiffids eq $findids} {
1889 unset findids
1889 unset findids
1890 findcont $fdiffids
1890 findcont $fdiffids
1891 }
1891 }
1892 }
1892 }
1893 }
1893 }
1894
1894
1895 proc findcont {ids} {
1895 proc findcont {ids} {
1896 global findids treediffs parents nparents
1896 global findids treediffs parents nparents
1897 global ffileline findstartline finddidsel
1897 global ffileline findstartline finddidsel
1898 global lineid numcommits matchinglines findinprogress
1898 global lineid numcommits matchinglines findinprogress
1899 global findmergefiles
1899 global findmergefiles
1900
1900
1901 set id [lindex $ids 0]
1901 set id [lindex $ids 0]
1902 set p [lindex $ids 1]
1902 set p [lindex $ids 1]
1903 set pi [lsearch -exact $parents($id) $p]
1903 set pi [lsearch -exact $parents($id) $p]
1904 set l $ffileline
1904 set l $ffileline
1905 while 1 {
1905 while 1 {
1906 if {$findmergefiles || $nparents($id) == 1} {
1906 if {$findmergefiles || $nparents($id) == 1} {
1907 if {![info exists treediffs($ids)]} {
1907 if {![info exists treediffs($ids)]} {
1908 set findids $ids
1908 set findids $ids
1909 set ffileline $l
1909 set ffileline $l
1910 return
1910 return
1911 }
1911 }
1912 set doesmatch 0
1912 set doesmatch 0
1913 foreach f $treediffs($ids) {
1913 foreach f $treediffs($ids) {
1914 set x [findmatches $f]
1914 set x [findmatches $f]
1915 if {$x != {}} {
1915 if {$x != {}} {
1916 set doesmatch 1
1916 set doesmatch 1
1917 break
1917 break
1918 }
1918 }
1919 }
1919 }
1920 if {$doesmatch} {
1920 if {$doesmatch} {
1921 insertmatch $l $id
1921 insertmatch $l $id
1922 set pi $nparents($id)
1922 set pi $nparents($id)
1923 }
1923 }
1924 } else {
1924 } else {
1925 set pi $nparents($id)
1925 set pi $nparents($id)
1926 }
1926 }
1927 if {[incr pi] >= $nparents($id)} {
1927 if {[incr pi] >= $nparents($id)} {
1928 set pi 0
1928 set pi 0
1929 if {[incr l] >= $numcommits} {
1929 if {[incr l] >= $numcommits} {
1930 set l 0
1930 set l 0
1931 }
1931 }
1932 if {$l == $findstartline} break
1932 if {$l == $findstartline} break
1933 set id $lineid($l)
1933 set id $lineid($l)
1934 }
1934 }
1935 set p [lindex $parents($id) $pi]
1935 set p [lindex $parents($id) $pi]
1936 set ids [list $id $p]
1936 set ids [list $id $p]
1937 }
1937 }
1938 stopfindproc
1938 stopfindproc
1939 if {!$finddidsel} {
1939 if {!$finddidsel} {
1940 bell
1940 bell
1941 }
1941 }
1942 }
1942 }
1943
1943
1944 # mark a commit as matching by putting a yellow background
1944 # mark a commit as matching by putting a yellow background
1945 # behind the headline
1945 # behind the headline
1946 proc markheadline {l id} {
1946 proc markheadline {l id} {
1947 global canv mainfont linehtag commitinfo
1947 global canv mainfont linehtag commitinfo
1948
1948
1949 set bbox [$canv bbox $linehtag($l)]
1949 set bbox [$canv bbox $linehtag($l)]
1950 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1950 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1951 $canv lower $t
1951 $canv lower $t
1952 }
1952 }
1953
1953
1954 # mark the bits of a headline, author or date that match a find string
1954 # mark the bits of a headline, author or date that match a find string
1955 proc markmatches {canv l str tag matches font} {
1955 proc markmatches {canv l str tag matches font} {
1956 set bbox [$canv bbox $tag]
1956 set bbox [$canv bbox $tag]
1957 set x0 [lindex $bbox 0]
1957 set x0 [lindex $bbox 0]
1958 set y0 [lindex $bbox 1]
1958 set y0 [lindex $bbox 1]
1959 set y1 [lindex $bbox 3]
1959 set y1 [lindex $bbox 3]
1960 foreach match $matches {
1960 foreach match $matches {
1961 set start [lindex $match 0]
1961 set start [lindex $match 0]
1962 set end [lindex $match 1]
1962 set end [lindex $match 1]
1963 if {$start > $end} continue
1963 if {$start > $end} continue
1964 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1964 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1965 set xlen [font measure $font [string range $str 0 [expr $end]]]
1965 set xlen [font measure $font [string range $str 0 [expr $end]]]
1966 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1966 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1967 -outline {} -tags matches -fill yellow]
1967 -outline {} -tags matches -fill yellow]
1968 $canv lower $t
1968 $canv lower $t
1969 }
1969 }
1970 }
1970 }
1971
1971
1972 proc unmarkmatches {} {
1972 proc unmarkmatches {} {
1973 global matchinglines findids
1973 global matchinglines findids
1974 allcanvs delete matches
1974 allcanvs delete matches
1975 catch {unset matchinglines}
1975 catch {unset matchinglines}
1976 catch {unset findids}
1976 catch {unset findids}
1977 }
1977 }
1978
1978
1979 proc selcanvline {w x y} {
1979 proc selcanvline {w x y} {
1980 global canv canvy0 ctext linespc
1980 global canv canvy0 ctext linespc
1981 global lineid linehtag linentag linedtag rowtextx
1981 global lineid linehtag linentag linedtag rowtextx
1982 set ymax [lindex [$canv cget -scrollregion] 3]
1982 set ymax [lindex [$canv cget -scrollregion] 3]
1983 if {$ymax == {}} return
1983 if {$ymax == {}} return
1984 set yfrac [lindex [$canv yview] 0]
1984 set yfrac [lindex [$canv yview] 0]
1985 set y [expr {$y + $yfrac * $ymax}]
1985 set y [expr {$y + $yfrac * $ymax}]
1986 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1986 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1987 if {$l < 0} {
1987 if {$l < 0} {
1988 set l 0
1988 set l 0
1989 }
1989 }
1990 if {$w eq $canv} {
1990 if {$w eq $canv} {
1991 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1991 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1992 }
1992 }
1993 unmarkmatches
1993 unmarkmatches
1994 selectline $l 1
1994 selectline $l 1
1995 }
1995 }
1996
1996
1997 proc commit_descriptor {p} {
1997 proc commit_descriptor {p} {
1998 global commitinfo
1998 global commitinfo
1999 set l "..."
1999 set l "..."
2000 if {[info exists commitinfo($p)]} {
2000 if {[info exists commitinfo($p)]} {
2001 set l [lindex $commitinfo($p) 0]
2001 set l [lindex $commitinfo($p) 0]
2002 }
2002 }
2003 return "$p ($l)"
2003 return "$p ($l)"
2004 }
2004 }
2005
2005
2006 # append some text to the ctext widget, and make any SHA1 ID
2006 # append some text to the ctext widget, and make any SHA1 ID
2007 # that we know about be a clickable link.
2007 # that we know about be a clickable link.
2008 proc appendwithlinks {text} {
2008 proc appendwithlinks {text} {
2009 global ctext idline linknum
2009 global ctext idline linknum
2010
2010
2011 set start [$ctext index "end - 1c"]
2011 set start [$ctext index "end - 1c"]
2012 $ctext insert end $text
2012 $ctext insert end $text
2013 $ctext insert end "\n"
2013 $ctext insert end "\n"
2014 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
2014 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
2015 foreach l $links {
2015 foreach l $links {
2016 set s [lindex $l 0]
2016 set s [lindex $l 0]
2017 set e [lindex $l 1]
2017 set e [lindex $l 1]
2018 set linkid [string range $text $s $e]
2018 set linkid [string range $text $s $e]
2019 if {![info exists idline($linkid)]} continue
2019 if {![info exists idline($linkid)]} continue
2020 incr e
2020 incr e
2021 $ctext tag add link "$start + $s c" "$start + $e c"
2021 $ctext tag add link "$start + $s c" "$start + $e c"
2022 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2022 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2023 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2023 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2024 incr linknum
2024 incr linknum
2025 }
2025 }
2026 $ctext tag conf link -foreground blue -underline 1
2026 $ctext tag conf link -foreground blue -underline 1
2027 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2027 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2028 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2028 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2029 }
2029 }
2030
2030
2031 proc selectline {l isnew} {
2031 proc selectline {l isnew} {
2032 global canv canv2 canv3 ctext commitinfo selectedline
2032 global canv canv2 canv3 ctext commitinfo selectedline
2033 global lineid linehtag linentag linedtag
2033 global lineid linehtag linentag linedtag
2034 global canvy0 linespc parents nparents children
2034 global canvy0 linespc parents nparents children
2035 global cflist currentid sha1entry
2035 global cflist currentid sha1entry
2036 global commentend idtags idline linknum
2036 global commentend idtags idline linknum
2037
2037
2038 $canv delete hover
2038 $canv delete hover
2039 normalline
2039 normalline
2040 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2040 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2041 $canv delete secsel
2041 $canv delete secsel
2042 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2042 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2043 -tags secsel -fill [$canv cget -selectbackground]]
2043 -tags secsel -fill [$canv cget -selectbackground]]
2044 $canv lower $t
2044 $canv lower $t
2045 $canv2 delete secsel
2045 $canv2 delete secsel
2046 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2046 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2047 -tags secsel -fill [$canv2 cget -selectbackground]]
2047 -tags secsel -fill [$canv2 cget -selectbackground]]
2048 $canv2 lower $t
2048 $canv2 lower $t
2049 $canv3 delete secsel
2049 $canv3 delete secsel
2050 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2050 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2051 -tags secsel -fill [$canv3 cget -selectbackground]]
2051 -tags secsel -fill [$canv3 cget -selectbackground]]
2052 $canv3 lower $t
2052 $canv3 lower $t
2053 set y [expr {$canvy0 + $l * $linespc}]
2053 set y [expr {$canvy0 + $l * $linespc}]
2054 set ymax [lindex [$canv cget -scrollregion] 3]
2054 set ymax [lindex [$canv cget -scrollregion] 3]
2055 set ytop [expr {$y - $linespc - 1}]
2055 set ytop [expr {$y - $linespc - 1}]
2056 set ybot [expr {$y + $linespc + 1}]
2056 set ybot [expr {$y + $linespc + 1}]
2057 set wnow [$canv yview]
2057 set wnow [$canv yview]
2058 set wtop [expr [lindex $wnow 0] * $ymax]
2058 set wtop [expr [lindex $wnow 0] * $ymax]
2059 set wbot [expr [lindex $wnow 1] * $ymax]
2059 set wbot [expr [lindex $wnow 1] * $ymax]
2060 set wh [expr {$wbot - $wtop}]
2060 set wh [expr {$wbot - $wtop}]
2061 set newtop $wtop
2061 set newtop $wtop
2062 if {$ytop < $wtop} {
2062 if {$ytop < $wtop} {
2063 if {$ybot < $wtop} {
2063 if {$ybot < $wtop} {
2064 set newtop [expr {$y - $wh / 2.0}]
2064 set newtop [expr {$y - $wh / 2.0}]
2065 } else {
2065 } else {
2066 set newtop $ytop
2066 set newtop $ytop
2067 if {$newtop > $wtop - $linespc} {
2067 if {$newtop > $wtop - $linespc} {
2068 set newtop [expr {$wtop - $linespc}]
2068 set newtop [expr {$wtop - $linespc}]
2069 }
2069 }
2070 }
2070 }
2071 } elseif {$ybot > $wbot} {
2071 } elseif {$ybot > $wbot} {
2072 if {$ytop > $wbot} {
2072 if {$ytop > $wbot} {
2073 set newtop [expr {$y - $wh / 2.0}]
2073 set newtop [expr {$y - $wh / 2.0}]
2074 } else {
2074 } else {
2075 set newtop [expr {$ybot - $wh}]
2075 set newtop [expr {$ybot - $wh}]
2076 if {$newtop < $wtop + $linespc} {
2076 if {$newtop < $wtop + $linespc} {
2077 set newtop [expr {$wtop + $linespc}]
2077 set newtop [expr {$wtop + $linespc}]
2078 }
2078 }
2079 }
2079 }
2080 }
2080 }
2081 if {$newtop != $wtop} {
2081 if {$newtop != $wtop} {
2082 if {$newtop < 0} {
2082 if {$newtop < 0} {
2083 set newtop 0
2083 set newtop 0
2084 }
2084 }
2085 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2085 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2086 }
2086 }
2087
2087
2088 if {$isnew} {
2088 if {$isnew} {
2089 addtohistory [list selectline $l 0]
2089 addtohistory [list selectline $l 0]
2090 }
2090 }
2091
2091
2092 set selectedline $l
2092 set selectedline $l
2093
2093
2094 set id $lineid($l)
2094 set id $lineid($l)
2095 set currentid $id
2095 set currentid $id
2096 $sha1entry delete 0 end
2096 $sha1entry delete 0 end
2097 $sha1entry insert 0 $id
2097 $sha1entry insert 0 $id
2098 $sha1entry selection from 0
2098 $sha1entry selection from 0
2099 $sha1entry selection to end
2099 $sha1entry selection to end
2100
2100
2101 $ctext conf -state normal
2101 $ctext conf -state normal
2102 $ctext delete 0.0 end
2102 $ctext delete 0.0 end
2103 set linknum 0
2103 set linknum 0
2104 $ctext mark set fmark.0 0.0
2104 $ctext mark set fmark.0 0.0
2105 $ctext mark gravity fmark.0 left
2105 $ctext mark gravity fmark.0 left
2106 set info $commitinfo($id)
2106 set info $commitinfo($id)
2107 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2107 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2108 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2108 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2109 if {[info exists idtags($id)]} {
2109 if {[info exists idtags($id)]} {
2110 $ctext insert end "Tags:"
2110 $ctext insert end "Tags:"
2111 foreach tag $idtags($id) {
2111 foreach tag $idtags($id) {
2112 $ctext insert end " $tag"
2112 $ctext insert end " $tag"
2113 }
2113 }
2114 $ctext insert end "\n"
2114 $ctext insert end "\n"
2115 }
2115 }
2116
2116
2117 set comment {}
2117 set comment {}
2118 if {[info exists parents($id)]} {
2118 if {[info exists parents($id)]} {
2119 foreach p $parents($id) {
2119 foreach p $parents($id) {
2120 append comment "Parent: [commit_descriptor $p]\n"
2120 append comment "Parent: [commit_descriptor $p]\n"
2121 }
2121 }
2122 }
2122 }
2123 if {[info exists children($id)]} {
2123 if {[info exists children($id)]} {
2124 foreach c $children($id) {
2124 foreach c $children($id) {
2125 append comment "Child: [commit_descriptor $c]\n"
2125 append comment "Child: [commit_descriptor $c]\n"
2126 }
2126 }
2127 }
2127 }
2128 append comment "\n"
2128 append comment "\n"
2129 append comment [lindex $info 5]
2129 append comment [lindex $info 5]
2130
2130
2131 # make anything that looks like a SHA1 ID be a clickable link
2131 # make anything that looks like a SHA1 ID be a clickable link
2132 appendwithlinks $comment
2132 appendwithlinks $comment
2133
2133
2134 $ctext tag delete Comments
2134 $ctext tag delete Comments
2135 $ctext tag remove found 1.0 end
2135 $ctext tag remove found 1.0 end
2136 $ctext conf -state disabled
2136 $ctext conf -state disabled
2137 set commentend [$ctext index "end - 1c"]
2137 set commentend [$ctext index "end - 1c"]
2138
2138
2139 $cflist delete 0 end
2139 $cflist delete 0 end
2140 $cflist insert end "Comments"
2140 $cflist insert end "Comments"
2141 if {$nparents($id) == 1} {
2141 if {$nparents($id) == 1} {
2142 startdiff [concat $id $parents($id)]
2142 startdiff [concat $id $parents($id)]
2143 } elseif {$nparents($id) > 1} {
2143 } elseif {$nparents($id) > 1} {
2144 mergediff $id
2144 mergediff $id
2145 }
2145 }
2146 }
2146 }
2147
2147
2148 proc selnextline {dir} {
2148 proc selnextline {dir} {
2149 global selectedline
2149 global selectedline
2150 if {![info exists selectedline]} return
2150 if {![info exists selectedline]} return
2151 set l [expr $selectedline + $dir]
2151 set l [expr $selectedline + $dir]
2152 unmarkmatches
2152 unmarkmatches
2153 selectline $l 1
2153 selectline $l 1
2154 }
2154 }
2155
2155
2156 proc unselectline {} {
2156 proc unselectline {} {
2157 global selectedline
2157 global selectedline
2158
2158
2159 catch {unset selectedline}
2159 catch {unset selectedline}
2160 allcanvs delete secsel
2160 allcanvs delete secsel
2161 }
2161 }
2162
2162
2163 proc addtohistory {cmd} {
2163 proc addtohistory {cmd} {
2164 global history historyindex
2164 global history historyindex
2165
2165
2166 if {$historyindex > 0
2166 if {$historyindex > 0
2167 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2167 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2168 return
2168 return
2169 }
2169 }
2170
2170
2171 if {$historyindex < [llength $history]} {
2171 if {$historyindex < [llength $history]} {
2172 set history [lreplace $history $historyindex end $cmd]
2172 set history [lreplace $history $historyindex end $cmd]
2173 } else {
2173 } else {
2174 lappend history $cmd
2174 lappend history $cmd
2175 }
2175 }
2176 incr historyindex
2176 incr historyindex
2177 if {$historyindex > 1} {
2177 if {$historyindex > 1} {
2178 .ctop.top.bar.leftbut conf -state normal
2178 .ctop.top.bar.leftbut conf -state normal
2179 } else {
2179 } else {
2180 .ctop.top.bar.leftbut conf -state disabled
2180 .ctop.top.bar.leftbut conf -state disabled
2181 }
2181 }
2182 .ctop.top.bar.rightbut conf -state disabled
2182 .ctop.top.bar.rightbut conf -state disabled
2183 }
2183 }
2184
2184
2185 proc goback {} {
2185 proc goback {} {
2186 global history historyindex
2186 global history historyindex
2187
2187
2188 if {$historyindex > 1} {
2188 if {$historyindex > 1} {
2189 incr historyindex -1
2189 incr historyindex -1
2190 set cmd [lindex $history [expr {$historyindex - 1}]]
2190 set cmd [lindex $history [expr {$historyindex - 1}]]
2191 eval $cmd
2191 eval $cmd
2192 .ctop.top.bar.rightbut conf -state normal
2192 .ctop.top.bar.rightbut conf -state normal
2193 }
2193 }
2194 if {$historyindex <= 1} {
2194 if {$historyindex <= 1} {
2195 .ctop.top.bar.leftbut conf -state disabled
2195 .ctop.top.bar.leftbut conf -state disabled
2196 }
2196 }
2197 }
2197 }
2198
2198
2199 proc goforw {} {
2199 proc goforw {} {
2200 global history historyindex
2200 global history historyindex
2201
2201
2202 if {$historyindex < [llength $history]} {
2202 if {$historyindex < [llength $history]} {
2203 set cmd [lindex $history $historyindex]
2203 set cmd [lindex $history $historyindex]
2204 incr historyindex
2204 incr historyindex
2205 eval $cmd
2205 eval $cmd
2206 .ctop.top.bar.leftbut conf -state normal
2206 .ctop.top.bar.leftbut conf -state normal
2207 }
2207 }
2208 if {$historyindex >= [llength $history]} {
2208 if {$historyindex >= [llength $history]} {
2209 .ctop.top.bar.rightbut conf -state disabled
2209 .ctop.top.bar.rightbut conf -state disabled
2210 }
2210 }
2211 }
2211 }
2212
2212
2213 proc mergediff {id} {
2213 proc mergediff {id} {
2214 global parents diffmergeid diffmergegca mergefilelist diffpindex
2214 global parents diffmergeid diffmergegca mergefilelist diffpindex
2215
2215
2216 set diffmergeid $id
2216 set diffmergeid $id
2217 set diffpindex -1
2217 set diffpindex -1
2218 set diffmergegca [findgca $parents($id)]
2218 set diffmergegca [findgca $parents($id)]
2219 if {[info exists mergefilelist($id)]} {
2219 if {[info exists mergefilelist($id)]} {
2220 if {$mergefilelist($id) ne {}} {
2220 if {$mergefilelist($id) ne {}} {
2221 showmergediff
2221 showmergediff
2222 }
2222 }
2223 } else {
2223 } else {
2224 contmergediff {}
2224 contmergediff {}
2225 }
2225 }
2226 }
2226 }
2227
2227
2228 proc findgca {ids} {
2228 proc findgca {ids} {
2229 set gca {}
2229 set gca {}
2230 foreach id $ids {
2230 foreach id $ids {
2231 if {$gca eq {}} {
2231 if {$gca eq {}} {
2232 set gca $id
2232 set gca $id
2233 } else {
2233 } else {
2234 if {[catch {
2234 if {[catch {
2235 set gca [exec hg debug-merge-base $gca $id]
2235 set gca [exec hg debug-merge-base $gca $id]
2236 } err]} {
2236 } err]} {
2237 return {}
2237 return {}
2238 }
2238 }
2239 }
2239 }
2240 }
2240 }
2241 return $gca
2241 return $gca
2242 }
2242 }
2243
2243
2244 proc contmergediff {ids} {
2244 proc contmergediff {ids} {
2245 global diffmergeid diffpindex parents nparents diffmergegca
2245 global diffmergeid diffpindex parents nparents diffmergegca
2246 global treediffs mergefilelist diffids treepending
2246 global treediffs mergefilelist diffids treepending
2247
2247
2248 # diff the child against each of the parents, and diff
2248 # diff the child against each of the parents, and diff
2249 # each of the parents against the GCA.
2249 # each of the parents against the GCA.
2250 while 1 {
2250 while 1 {
2251 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2251 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2252 set ids [list [lindex $ids 1] $diffmergegca]
2252 set ids [list [lindex $ids 1] $diffmergegca]
2253 } else {
2253 } else {
2254 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2254 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2255 set p [lindex $parents($diffmergeid) $diffpindex]
2255 set p [lindex $parents($diffmergeid) $diffpindex]
2256 set ids [list $diffmergeid $p]
2256 set ids [list $diffmergeid $p]
2257 }
2257 }
2258 if {![info exists treediffs($ids)]} {
2258 if {![info exists treediffs($ids)]} {
2259 set diffids $ids
2259 set diffids $ids
2260 if {![info exists treepending]} {
2260 if {![info exists treepending]} {
2261 gettreediffs $ids
2261 gettreediffs $ids
2262 }
2262 }
2263 return
2263 return
2264 }
2264 }
2265 }
2265 }
2266
2266
2267 # If a file in some parent is different from the child and also
2267 # If a file in some parent is different from the child and also
2268 # different from the GCA, then it's interesting.
2268 # different from the GCA, then it's interesting.
2269 # If we don't have a GCA, then a file is interesting if it is
2269 # If we don't have a GCA, then a file is interesting if it is
2270 # different from the child in all the parents.
2270 # different from the child in all the parents.
2271 if {$diffmergegca ne {}} {
2271 if {$diffmergegca ne {}} {
2272 set files {}
2272 set files {}
2273 foreach p $parents($diffmergeid) {
2273 foreach p $parents($diffmergeid) {
2274 set gcadiffs $treediffs([list $p $diffmergegca])
2274 set gcadiffs $treediffs([list $p $diffmergegca])
2275 foreach f $treediffs([list $diffmergeid $p]) {
2275 foreach f $treediffs([list $diffmergeid $p]) {
2276 if {[lsearch -exact $files $f] < 0
2276 if {[lsearch -exact $files $f] < 0
2277 && [lsearch -exact $gcadiffs $f] >= 0} {
2277 && [lsearch -exact $gcadiffs $f] >= 0} {
2278 lappend files $f
2278 lappend files $f
2279 }
2279 }
2280 }
2280 }
2281 }
2281 }
2282 set files [lsort $files]
2282 set files [lsort $files]
2283 } else {
2283 } else {
2284 set p [lindex $parents($diffmergeid) 0]
2284 set p [lindex $parents($diffmergeid) 0]
2285 set files $treediffs([list $diffmergeid $p])
2285 set files $treediffs([list $diffmergeid $p])
2286 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2286 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2287 set p [lindex $parents($diffmergeid) $i]
2287 set p [lindex $parents($diffmergeid) $i]
2288 set df $treediffs([list $diffmergeid $p])
2288 set df $treediffs([list $diffmergeid $p])
2289 set nf {}
2289 set nf {}
2290 foreach f $files {
2290 foreach f $files {
2291 if {[lsearch -exact $df $f] >= 0} {
2291 if {[lsearch -exact $df $f] >= 0} {
2292 lappend nf $f
2292 lappend nf $f
2293 }
2293 }
2294 }
2294 }
2295 set files $nf
2295 set files $nf
2296 }
2296 }
2297 }
2297 }
2298
2298
2299 set mergefilelist($diffmergeid) $files
2299 set mergefilelist($diffmergeid) $files
2300 if {$files ne {}} {
2300 if {$files ne {}} {
2301 showmergediff
2301 showmergediff
2302 }
2302 }
2303 }
2303 }
2304
2304
2305 proc showmergediff {} {
2305 proc showmergediff {} {
2306 global cflist diffmergeid mergefilelist parents
2306 global cflist diffmergeid mergefilelist parents
2307 global diffopts diffinhunk currentfile currenthunk filelines
2307 global diffopts diffinhunk currentfile currenthunk filelines
2308 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2308 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2309
2309
2310 set files $mergefilelist($diffmergeid)
2310 set files $mergefilelist($diffmergeid)
2311 foreach f $files {
2311 foreach f $files {
2312 $cflist insert end $f
2312 $cflist insert end $f
2313 }
2313 }
2314 set env(GIT_DIFF_OPTS) $diffopts
2314 set env(GIT_DIFF_OPTS) $diffopts
2315 set flist {}
2315 set flist {}
2316 catch {unset currentfile}
2316 catch {unset currentfile}
2317 catch {unset currenthunk}
2317 catch {unset currenthunk}
2318 catch {unset filelines}
2318 catch {unset filelines}
2319 catch {unset groupfilenum}
2319 catch {unset groupfilenum}
2320 catch {unset grouphunks}
2320 catch {unset grouphunks}
2321 set groupfilelast -1
2321 set groupfilelast -1
2322 foreach p $parents($diffmergeid) {
2322 foreach p $parents($diffmergeid) {
2323 set cmd [list | hg debug-diff-tree -p $p $diffmergeid]
2323 set cmd [list | hg debug-diff-tree -p $p $diffmergeid]
2324 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2324 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2325 if {[catch {set f [open $cmd r]} err]} {
2325 if {[catch {set f [open $cmd r]} err]} {
2326 error_popup "Error getting diffs: $err"
2326 error_popup "Error getting diffs: $err"
2327 foreach f $flist {
2327 foreach f $flist {
2328 catch {close $f}
2328 catch {close $f}
2329 }
2329 }
2330 return
2330 return
2331 }
2331 }
2332 lappend flist $f
2332 lappend flist $f
2333 set ids [list $diffmergeid $p]
2333 set ids [list $diffmergeid $p]
2334 set mergefds($ids) $f
2334 set mergefds($ids) $f
2335 set diffinhunk($ids) 0
2335 set diffinhunk($ids) 0
2336 set diffblocked($ids) 0
2336 set diffblocked($ids) 0
2337 fconfigure $f -blocking 0
2337 fconfigure $f -blocking 0
2338 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2338 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2339 }
2339 }
2340 }
2340 }
2341
2341
2342 proc getmergediffline {f ids id} {
2342 proc getmergediffline {f ids id} {
2343 global diffmergeid diffinhunk diffoldlines diffnewlines
2343 global diffmergeid diffinhunk diffoldlines diffnewlines
2344 global currentfile currenthunk
2344 global currentfile currenthunk
2345 global diffoldstart diffnewstart diffoldlno diffnewlno
2345 global diffoldstart diffnewstart diffoldlno diffnewlno
2346 global diffblocked mergefilelist
2346 global diffblocked mergefilelist
2347 global noldlines nnewlines difflcounts filelines
2347 global noldlines nnewlines difflcounts filelines
2348
2348
2349 set n [gets $f line]
2349 set n [gets $f line]
2350 if {$n < 0} {
2350 if {$n < 0} {
2351 if {![eof $f]} return
2351 if {![eof $f]} return
2352 }
2352 }
2353
2353
2354 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2354 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2355 if {$n < 0} {
2355 if {$n < 0} {
2356 close $f
2356 close $f
2357 }
2357 }
2358 return
2358 return
2359 }
2359 }
2360
2360
2361 if {$diffinhunk($ids) != 0} {
2361 if {$diffinhunk($ids) != 0} {
2362 set fi $currentfile($ids)
2362 set fi $currentfile($ids)
2363 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2363 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2364 # continuing an existing hunk
2364 # continuing an existing hunk
2365 set line [string range $line 1 end]
2365 set line [string range $line 1 end]
2366 set p [lindex $ids 1]
2366 set p [lindex $ids 1]
2367 if {$match eq "-" || $match eq " "} {
2367 if {$match eq "-" || $match eq " "} {
2368 set filelines($p,$fi,$diffoldlno($ids)) $line
2368 set filelines($p,$fi,$diffoldlno($ids)) $line
2369 incr diffoldlno($ids)
2369 incr diffoldlno($ids)
2370 }
2370 }
2371 if {$match eq "+" || $match eq " "} {
2371 if {$match eq "+" || $match eq " "} {
2372 set filelines($id,$fi,$diffnewlno($ids)) $line
2372 set filelines($id,$fi,$diffnewlno($ids)) $line
2373 incr diffnewlno($ids)
2373 incr diffnewlno($ids)
2374 }
2374 }
2375 if {$match eq " "} {
2375 if {$match eq " "} {
2376 if {$diffinhunk($ids) == 2} {
2376 if {$diffinhunk($ids) == 2} {
2377 lappend difflcounts($ids) \
2377 lappend difflcounts($ids) \
2378 [list $noldlines($ids) $nnewlines($ids)]
2378 [list $noldlines($ids) $nnewlines($ids)]
2379 set noldlines($ids) 0
2379 set noldlines($ids) 0
2380 set diffinhunk($ids) 1
2380 set diffinhunk($ids) 1
2381 }
2381 }
2382 incr noldlines($ids)
2382 incr noldlines($ids)
2383 } elseif {$match eq "-" || $match eq "+"} {
2383 } elseif {$match eq "-" || $match eq "+"} {
2384 if {$diffinhunk($ids) == 1} {
2384 if {$diffinhunk($ids) == 1} {
2385 lappend difflcounts($ids) [list $noldlines($ids)]
2385 lappend difflcounts($ids) [list $noldlines($ids)]
2386 set noldlines($ids) 0
2386 set noldlines($ids) 0
2387 set nnewlines($ids) 0
2387 set nnewlines($ids) 0
2388 set diffinhunk($ids) 2
2388 set diffinhunk($ids) 2
2389 }
2389 }
2390 if {$match eq "-"} {
2390 if {$match eq "-"} {
2391 incr noldlines($ids)
2391 incr noldlines($ids)
2392 } else {
2392 } else {
2393 incr nnewlines($ids)
2393 incr nnewlines($ids)
2394 }
2394 }
2395 }
2395 }
2396 # and if it's \ No newline at end of line, then what?
2396 # and if it's \ No newline at end of line, then what?
2397 return
2397 return
2398 }
2398 }
2399 # end of a hunk
2399 # end of a hunk
2400 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2400 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2401 lappend difflcounts($ids) [list $noldlines($ids)]
2401 lappend difflcounts($ids) [list $noldlines($ids)]
2402 } elseif {$diffinhunk($ids) == 2
2402 } elseif {$diffinhunk($ids) == 2
2403 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2403 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2404 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2404 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2405 }
2405 }
2406 set currenthunk($ids) [list $currentfile($ids) \
2406 set currenthunk($ids) [list $currentfile($ids) \
2407 $diffoldstart($ids) $diffnewstart($ids) \
2407 $diffoldstart($ids) $diffnewstart($ids) \
2408 $diffoldlno($ids) $diffnewlno($ids) \
2408 $diffoldlno($ids) $diffnewlno($ids) \
2409 $difflcounts($ids)]
2409 $difflcounts($ids)]
2410 set diffinhunk($ids) 0
2410 set diffinhunk($ids) 0
2411 # -1 = need to block, 0 = unblocked, 1 = is blocked
2411 # -1 = need to block, 0 = unblocked, 1 = is blocked
2412 set diffblocked($ids) -1
2412 set diffblocked($ids) -1
2413 processhunks
2413 processhunks
2414 if {$diffblocked($ids) == -1} {
2414 if {$diffblocked($ids) == -1} {
2415 fileevent $f readable {}
2415 fileevent $f readable {}
2416 set diffblocked($ids) 1
2416 set diffblocked($ids) 1
2417 }
2417 }
2418 }
2418 }
2419
2419
2420 if {$n < 0} {
2420 if {$n < 0} {
2421 # eof
2421 # eof
2422 if {!$diffblocked($ids)} {
2422 if {!$diffblocked($ids)} {
2423 close $f
2423 close $f
2424 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2424 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2425 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2425 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2426 processhunks
2426 processhunks
2427 }
2427 }
2428 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2428 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2429 # start of a new file
2429 # start of a new file
2430 set currentfile($ids) \
2430 set currentfile($ids) \
2431 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2431 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2432 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2432 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2433 $line match f1l f1c f2l f2c rest]} {
2433 $line match f1l f1c f2l f2c rest]} {
2434 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2434 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2435 # start of a new hunk
2435 # start of a new hunk
2436 if {$f1l == 0 && $f1c == 0} {
2436 if {$f1l == 0 && $f1c == 0} {
2437 set f1l 1
2437 set f1l 1
2438 }
2438 }
2439 if {$f2l == 0 && $f2c == 0} {
2439 if {$f2l == 0 && $f2c == 0} {
2440 set f2l 1
2440 set f2l 1
2441 }
2441 }
2442 set diffinhunk($ids) 1
2442 set diffinhunk($ids) 1
2443 set diffoldstart($ids) $f1l
2443 set diffoldstart($ids) $f1l
2444 set diffnewstart($ids) $f2l
2444 set diffnewstart($ids) $f2l
2445 set diffoldlno($ids) $f1l
2445 set diffoldlno($ids) $f1l
2446 set diffnewlno($ids) $f2l
2446 set diffnewlno($ids) $f2l
2447 set difflcounts($ids) {}
2447 set difflcounts($ids) {}
2448 set noldlines($ids) 0
2448 set noldlines($ids) 0
2449 set nnewlines($ids) 0
2449 set nnewlines($ids) 0
2450 }
2450 }
2451 }
2451 }
2452 }
2452 }
2453
2453
2454 proc processhunks {} {
2454 proc processhunks {} {
2455 global diffmergeid parents nparents currenthunk
2455 global diffmergeid parents nparents currenthunk
2456 global mergefilelist diffblocked mergefds
2456 global mergefilelist diffblocked mergefds
2457 global grouphunks grouplinestart grouplineend groupfilenum
2457 global grouphunks grouplinestart grouplineend groupfilenum
2458
2458
2459 set nfiles [llength $mergefilelist($diffmergeid)]
2459 set nfiles [llength $mergefilelist($diffmergeid)]
2460 while 1 {
2460 while 1 {
2461 set fi $nfiles
2461 set fi $nfiles
2462 set lno 0
2462 set lno 0
2463 # look for the earliest hunk
2463 # look for the earliest hunk
2464 foreach p $parents($diffmergeid) {
2464 foreach p $parents($diffmergeid) {
2465 set ids [list $diffmergeid $p]
2465 set ids [list $diffmergeid $p]
2466 if {![info exists currenthunk($ids)]} return
2466 if {![info exists currenthunk($ids)]} return
2467 set i [lindex $currenthunk($ids) 0]
2467 set i [lindex $currenthunk($ids) 0]
2468 set l [lindex $currenthunk($ids) 2]
2468 set l [lindex $currenthunk($ids) 2]
2469 if {$i < $fi || ($i == $fi && $l < $lno)} {
2469 if {$i < $fi || ($i == $fi && $l < $lno)} {
2470 set fi $i
2470 set fi $i
2471 set lno $l
2471 set lno $l
2472 set pi $p
2472 set pi $p
2473 }
2473 }
2474 }
2474 }
2475
2475
2476 if {$fi < $nfiles} {
2476 if {$fi < $nfiles} {
2477 set ids [list $diffmergeid $pi]
2477 set ids [list $diffmergeid $pi]
2478 set hunk $currenthunk($ids)
2478 set hunk $currenthunk($ids)
2479 unset currenthunk($ids)
2479 unset currenthunk($ids)
2480 if {$diffblocked($ids) > 0} {
2480 if {$diffblocked($ids) > 0} {
2481 fileevent $mergefds($ids) readable \
2481 fileevent $mergefds($ids) readable \
2482 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2482 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2483 }
2483 }
2484 set diffblocked($ids) 0
2484 set diffblocked($ids) 0
2485
2485
2486 if {[info exists groupfilenum] && $groupfilenum == $fi
2486 if {[info exists groupfilenum] && $groupfilenum == $fi
2487 && $lno <= $grouplineend} {
2487 && $lno <= $grouplineend} {
2488 # add this hunk to the pending group
2488 # add this hunk to the pending group
2489 lappend grouphunks($pi) $hunk
2489 lappend grouphunks($pi) $hunk
2490 set endln [lindex $hunk 4]
2490 set endln [lindex $hunk 4]
2491 if {$endln > $grouplineend} {
2491 if {$endln > $grouplineend} {
2492 set grouplineend $endln
2492 set grouplineend $endln
2493 }
2493 }
2494 continue
2494 continue
2495 }
2495 }
2496 }
2496 }
2497
2497
2498 # succeeding stuff doesn't belong in this group, so
2498 # succeeding stuff doesn't belong in this group, so
2499 # process the group now
2499 # process the group now
2500 if {[info exists groupfilenum]} {
2500 if {[info exists groupfilenum]} {
2501 processgroup
2501 processgroup
2502 unset groupfilenum
2502 unset groupfilenum
2503 unset grouphunks
2503 unset grouphunks
2504 }
2504 }
2505
2505
2506 if {$fi >= $nfiles} break
2506 if {$fi >= $nfiles} break
2507
2507
2508 # start a new group
2508 # start a new group
2509 set groupfilenum $fi
2509 set groupfilenum $fi
2510 set grouphunks($pi) [list $hunk]
2510 set grouphunks($pi) [list $hunk]
2511 set grouplinestart $lno
2511 set grouplinestart $lno
2512 set grouplineend [lindex $hunk 4]
2512 set grouplineend [lindex $hunk 4]
2513 }
2513 }
2514 }
2514 }
2515
2515
2516 proc processgroup {} {
2516 proc processgroup {} {
2517 global groupfilelast groupfilenum difffilestart
2517 global groupfilelast groupfilenum difffilestart
2518 global mergefilelist diffmergeid ctext filelines
2518 global mergefilelist diffmergeid ctext filelines
2519 global parents diffmergeid diffoffset
2519 global parents diffmergeid diffoffset
2520 global grouphunks grouplinestart grouplineend nparents
2520 global grouphunks grouplinestart grouplineend nparents
2521 global mergemax
2521 global mergemax
2522
2522
2523 $ctext conf -state normal
2523 $ctext conf -state normal
2524 set id $diffmergeid
2524 set id $diffmergeid
2525 set f $groupfilenum
2525 set f $groupfilenum
2526 if {$groupfilelast != $f} {
2526 if {$groupfilelast != $f} {
2527 $ctext insert end "\n"
2527 $ctext insert end "\n"
2528 set here [$ctext index "end - 1c"]
2528 set here [$ctext index "end - 1c"]
2529 set difffilestart($f) $here
2529 set difffilestart($f) $here
2530 set mark fmark.[expr {$f + 1}]
2530 set mark fmark.[expr {$f + 1}]
2531 $ctext mark set $mark $here
2531 $ctext mark set $mark $here
2532 $ctext mark gravity $mark left
2532 $ctext mark gravity $mark left
2533 set header [lindex $mergefilelist($id) $f]
2533 set header [lindex $mergefilelist($id) $f]
2534 set l [expr {(78 - [string length $header]) / 2}]
2534 set l [expr {(78 - [string length $header]) / 2}]
2535 set pad [string range "----------------------------------------" 1 $l]
2535 set pad [string range "----------------------------------------" 1 $l]
2536 $ctext insert end "$pad $header $pad\n" filesep
2536 $ctext insert end "$pad $header $pad\n" filesep
2537 set groupfilelast $f
2537 set groupfilelast $f
2538 foreach p $parents($id) {
2538 foreach p $parents($id) {
2539 set diffoffset($p) 0
2539 set diffoffset($p) 0
2540 }
2540 }
2541 }
2541 }
2542
2542
2543 $ctext insert end "@@" msep
2543 $ctext insert end "@@" msep
2544 set nlines [expr {$grouplineend - $grouplinestart}]
2544 set nlines [expr {$grouplineend - $grouplinestart}]
2545 set events {}
2545 set events {}
2546 set pnum 0
2546 set pnum 0
2547 foreach p $parents($id) {
2547 foreach p $parents($id) {
2548 set startline [expr {$grouplinestart + $diffoffset($p)}]
2548 set startline [expr {$grouplinestart + $diffoffset($p)}]
2549 set ol $startline
2549 set ol $startline
2550 set nl $grouplinestart
2550 set nl $grouplinestart
2551 if {[info exists grouphunks($p)]} {
2551 if {[info exists grouphunks($p)]} {
2552 foreach h $grouphunks($p) {
2552 foreach h $grouphunks($p) {
2553 set l [lindex $h 2]
2553 set l [lindex $h 2]
2554 if {$nl < $l} {
2554 if {$nl < $l} {
2555 for {} {$nl < $l} {incr nl} {
2555 for {} {$nl < $l} {incr nl} {
2556 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2556 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2557 incr ol
2557 incr ol
2558 }
2558 }
2559 }
2559 }
2560 foreach chunk [lindex $h 5] {
2560 foreach chunk [lindex $h 5] {
2561 if {[llength $chunk] == 2} {
2561 if {[llength $chunk] == 2} {
2562 set olc [lindex $chunk 0]
2562 set olc [lindex $chunk 0]
2563 set nlc [lindex $chunk 1]
2563 set nlc [lindex $chunk 1]
2564 set nnl [expr {$nl + $nlc}]
2564 set nnl [expr {$nl + $nlc}]
2565 lappend events [list $nl $nnl $pnum $olc $nlc]
2565 lappend events [list $nl $nnl $pnum $olc $nlc]
2566 incr ol $olc
2566 incr ol $olc
2567 set nl $nnl
2567 set nl $nnl
2568 } else {
2568 } else {
2569 incr ol [lindex $chunk 0]
2569 incr ol [lindex $chunk 0]
2570 incr nl [lindex $chunk 0]
2570 incr nl [lindex $chunk 0]
2571 }
2571 }
2572 }
2572 }
2573 }
2573 }
2574 }
2574 }
2575 if {$nl < $grouplineend} {
2575 if {$nl < $grouplineend} {
2576 for {} {$nl < $grouplineend} {incr nl} {
2576 for {} {$nl < $grouplineend} {incr nl} {
2577 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2577 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2578 incr ol
2578 incr ol
2579 }
2579 }
2580 }
2580 }
2581 set nlines [expr {$ol - $startline}]
2581 set nlines [expr {$ol - $startline}]
2582 $ctext insert end " -$startline,$nlines" msep
2582 $ctext insert end " -$startline,$nlines" msep
2583 incr pnum
2583 incr pnum
2584 }
2584 }
2585
2585
2586 set nlines [expr {$grouplineend - $grouplinestart}]
2586 set nlines [expr {$grouplineend - $grouplinestart}]
2587 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2587 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2588
2588
2589 set events [lsort -integer -index 0 $events]
2589 set events [lsort -integer -index 0 $events]
2590 set nevents [llength $events]
2590 set nevents [llength $events]
2591 set nmerge $nparents($diffmergeid)
2591 set nmerge $nparents($diffmergeid)
2592 set l $grouplinestart
2592 set l $grouplinestart
2593 for {set i 0} {$i < $nevents} {set i $j} {
2593 for {set i 0} {$i < $nevents} {set i $j} {
2594 set nl [lindex $events $i 0]
2594 set nl [lindex $events $i 0]
2595 while {$l < $nl} {
2595 while {$l < $nl} {
2596 $ctext insert end " $filelines($id,$f,$l)\n"
2596 $ctext insert end " $filelines($id,$f,$l)\n"
2597 incr l
2597 incr l
2598 }
2598 }
2599 set e [lindex $events $i]
2599 set e [lindex $events $i]
2600 set enl [lindex $e 1]
2600 set enl [lindex $e 1]
2601 set j $i
2601 set j $i
2602 set active {}
2602 set active {}
2603 while 1 {
2603 while 1 {
2604 set pnum [lindex $e 2]
2604 set pnum [lindex $e 2]
2605 set olc [lindex $e 3]
2605 set olc [lindex $e 3]
2606 set nlc [lindex $e 4]
2606 set nlc [lindex $e 4]
2607 if {![info exists delta($pnum)]} {
2607 if {![info exists delta($pnum)]} {
2608 set delta($pnum) [expr {$olc - $nlc}]
2608 set delta($pnum) [expr {$olc - $nlc}]
2609 lappend active $pnum
2609 lappend active $pnum
2610 } else {
2610 } else {
2611 incr delta($pnum) [expr {$olc - $nlc}]
2611 incr delta($pnum) [expr {$olc - $nlc}]
2612 }
2612 }
2613 if {[incr j] >= $nevents} break
2613 if {[incr j] >= $nevents} break
2614 set e [lindex $events $j]
2614 set e [lindex $events $j]
2615 if {[lindex $e 0] >= $enl} break
2615 if {[lindex $e 0] >= $enl} break
2616 if {[lindex $e 1] > $enl} {
2616 if {[lindex $e 1] > $enl} {
2617 set enl [lindex $e 1]
2617 set enl [lindex $e 1]
2618 }
2618 }
2619 }
2619 }
2620 set nlc [expr {$enl - $l}]
2620 set nlc [expr {$enl - $l}]
2621 set ncol mresult
2621 set ncol mresult
2622 set bestpn -1
2622 set bestpn -1
2623 if {[llength $active] == $nmerge - 1} {
2623 if {[llength $active] == $nmerge - 1} {
2624 # no diff for one of the parents, i.e. it's identical
2624 # no diff for one of the parents, i.e. it's identical
2625 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2625 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2626 if {![info exists delta($pnum)]} {
2626 if {![info exists delta($pnum)]} {
2627 if {$pnum < $mergemax} {
2627 if {$pnum < $mergemax} {
2628 lappend ncol m$pnum
2628 lappend ncol m$pnum
2629 } else {
2629 } else {
2630 lappend ncol mmax
2630 lappend ncol mmax
2631 }
2631 }
2632 break
2632 break
2633 }
2633 }
2634 }
2634 }
2635 } elseif {[llength $active] == $nmerge} {
2635 } elseif {[llength $active] == $nmerge} {
2636 # all parents are different, see if one is very similar
2636 # all parents are different, see if one is very similar
2637 set bestsim 30
2637 set bestsim 30
2638 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2638 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2639 set sim [similarity $pnum $l $nlc $f \
2639 set sim [similarity $pnum $l $nlc $f \
2640 [lrange $events $i [expr {$j-1}]]]
2640 [lrange $events $i [expr {$j-1}]]]
2641 if {$sim > $bestsim} {
2641 if {$sim > $bestsim} {
2642 set bestsim $sim
2642 set bestsim $sim
2643 set bestpn $pnum
2643 set bestpn $pnum
2644 }
2644 }
2645 }
2645 }
2646 if {$bestpn >= 0} {
2646 if {$bestpn >= 0} {
2647 lappend ncol m$bestpn
2647 lappend ncol m$bestpn
2648 }
2648 }
2649 }
2649 }
2650 set pnum -1
2650 set pnum -1
2651 foreach p $parents($id) {
2651 foreach p $parents($id) {
2652 incr pnum
2652 incr pnum
2653 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2653 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2654 set olc [expr {$nlc + $delta($pnum)}]
2654 set olc [expr {$nlc + $delta($pnum)}]
2655 set ol [expr {$l + $diffoffset($p)}]
2655 set ol [expr {$l + $diffoffset($p)}]
2656 incr diffoffset($p) $delta($pnum)
2656 incr diffoffset($p) $delta($pnum)
2657 unset delta($pnum)
2657 unset delta($pnum)
2658 for {} {$olc > 0} {incr olc -1} {
2658 for {} {$olc > 0} {incr olc -1} {
2659 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2659 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2660 incr ol
2660 incr ol
2661 }
2661 }
2662 }
2662 }
2663 set endl [expr {$l + $nlc}]
2663 set endl [expr {$l + $nlc}]
2664 if {$bestpn >= 0} {
2664 if {$bestpn >= 0} {
2665 # show this pretty much as a normal diff
2665 # show this pretty much as a normal diff
2666 set p [lindex $parents($id) $bestpn]
2666 set p [lindex $parents($id) $bestpn]
2667 set ol [expr {$l + $diffoffset($p)}]
2667 set ol [expr {$l + $diffoffset($p)}]
2668 incr diffoffset($p) $delta($bestpn)
2668 incr diffoffset($p) $delta($bestpn)
2669 unset delta($bestpn)
2669 unset delta($bestpn)
2670 for {set k $i} {$k < $j} {incr k} {
2670 for {set k $i} {$k < $j} {incr k} {
2671 set e [lindex $events $k]
2671 set e [lindex $events $k]
2672 if {[lindex $e 2] != $bestpn} continue
2672 if {[lindex $e 2] != $bestpn} continue
2673 set nl [lindex $e 0]
2673 set nl [lindex $e 0]
2674 set ol [expr {$ol + $nl - $l}]
2674 set ol [expr {$ol + $nl - $l}]
2675 for {} {$l < $nl} {incr l} {
2675 for {} {$l < $nl} {incr l} {
2676 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2676 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2677 }
2677 }
2678 set c [lindex $e 3]
2678 set c [lindex $e 3]
2679 for {} {$c > 0} {incr c -1} {
2679 for {} {$c > 0} {incr c -1} {
2680 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2680 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2681 incr ol
2681 incr ol
2682 }
2682 }
2683 set nl [lindex $e 1]
2683 set nl [lindex $e 1]
2684 for {} {$l < $nl} {incr l} {
2684 for {} {$l < $nl} {incr l} {
2685 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2685 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2686 }
2686 }
2687 }
2687 }
2688 }
2688 }
2689 for {} {$l < $endl} {incr l} {
2689 for {} {$l < $endl} {incr l} {
2690 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2690 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2691 }
2691 }
2692 }
2692 }
2693 while {$l < $grouplineend} {
2693 while {$l < $grouplineend} {
2694 $ctext insert end " $filelines($id,$f,$l)\n"
2694 $ctext insert end " $filelines($id,$f,$l)\n"
2695 incr l
2695 incr l
2696 }
2696 }
2697 $ctext conf -state disabled
2697 $ctext conf -state disabled
2698 }
2698 }
2699
2699
2700 proc similarity {pnum l nlc f events} {
2700 proc similarity {pnum l nlc f events} {
2701 global diffmergeid parents diffoffset filelines
2701 global diffmergeid parents diffoffset filelines
2702
2702
2703 set id $diffmergeid
2703 set id $diffmergeid
2704 set p [lindex $parents($id) $pnum]
2704 set p [lindex $parents($id) $pnum]
2705 set ol [expr {$l + $diffoffset($p)}]
2705 set ol [expr {$l + $diffoffset($p)}]
2706 set endl [expr {$l + $nlc}]
2706 set endl [expr {$l + $nlc}]
2707 set same 0
2707 set same 0
2708 set diff 0
2708 set diff 0
2709 foreach e $events {
2709 foreach e $events {
2710 if {[lindex $e 2] != $pnum} continue
2710 if {[lindex $e 2] != $pnum} continue
2711 set nl [lindex $e 0]
2711 set nl [lindex $e 0]
2712 set ol [expr {$ol + $nl - $l}]
2712 set ol [expr {$ol + $nl - $l}]
2713 for {} {$l < $nl} {incr l} {
2713 for {} {$l < $nl} {incr l} {
2714 incr same [string length $filelines($id,$f,$l)]
2714 incr same [string length $filelines($id,$f,$l)]
2715 incr same
2715 incr same
2716 }
2716 }
2717 set oc [lindex $e 3]
2717 set oc [lindex $e 3]
2718 for {} {$oc > 0} {incr oc -1} {
2718 for {} {$oc > 0} {incr oc -1} {
2719 incr diff [string length $filelines($p,$f,$ol)]
2719 incr diff [string length $filelines($p,$f,$ol)]
2720 incr diff
2720 incr diff
2721 incr ol
2721 incr ol
2722 }
2722 }
2723 set nl [lindex $e 1]
2723 set nl [lindex $e 1]
2724 for {} {$l < $nl} {incr l} {
2724 for {} {$l < $nl} {incr l} {
2725 incr diff [string length $filelines($id,$f,$l)]
2725 incr diff [string length $filelines($id,$f,$l)]
2726 incr diff
2726 incr diff
2727 }
2727 }
2728 }
2728 }
2729 for {} {$l < $endl} {incr l} {
2729 for {} {$l < $endl} {incr l} {
2730 incr same [string length $filelines($id,$f,$l)]
2730 incr same [string length $filelines($id,$f,$l)]
2731 incr same
2731 incr same
2732 }
2732 }
2733 if {$same == 0} {
2733 if {$same == 0} {
2734 return 0
2734 return 0
2735 }
2735 }
2736 return [expr {200 * $same / (2 * $same + $diff)}]
2736 return [expr {200 * $same / (2 * $same + $diff)}]
2737 }
2737 }
2738
2738
2739 proc startdiff {ids} {
2739 proc startdiff {ids} {
2740 global treediffs diffids treepending diffmergeid
2740 global treediffs diffids treepending diffmergeid
2741
2741
2742 set diffids $ids
2742 set diffids $ids
2743 catch {unset diffmergeid}
2743 catch {unset diffmergeid}
2744 if {![info exists treediffs($ids)]} {
2744 if {![info exists treediffs($ids)]} {
2745 if {![info exists treepending]} {
2745 if {![info exists treepending]} {
2746 gettreediffs $ids
2746 gettreediffs $ids
2747 }
2747 }
2748 } else {
2748 } else {
2749 addtocflist $ids
2749 addtocflist $ids
2750 }
2750 }
2751 }
2751 }
2752
2752
2753 proc addtocflist {ids} {
2753 proc addtocflist {ids} {
2754 global treediffs cflist
2754 global treediffs cflist
2755 foreach f $treediffs($ids) {
2755 foreach f $treediffs($ids) {
2756 $cflist insert end $f
2756 $cflist insert end $f
2757 }
2757 }
2758 getblobdiffs $ids
2758 getblobdiffs $ids
2759 }
2759 }
2760
2760
2761 proc gettreediffs {ids} {
2761 proc gettreediffs {ids} {
2762 global treediff parents treepending
2762 global treediff parents treepending
2763 set treepending $ids
2763 set treepending $ids
2764 set treediff {}
2764 set treediff {}
2765 set id [lindex $ids 0]
2765 set id [lindex $ids 0]
2766 set p [lindex $ids 1]
2766 set p [lindex $ids 1]
2767 if [catch {set gdtf [open "|hg debug-diff-tree -r $p $id" r]}] return
2767 if [catch {set gdtf [open "|hg debug-diff-tree -r $p $id" r]}] return
2768 fconfigure $gdtf -blocking 0
2768 fconfigure $gdtf -blocking 0
2769 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2769 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2770 }
2770 }
2771
2771
2772 proc gettreediffline {gdtf ids} {
2772 proc gettreediffline {gdtf ids} {
2773 global treediff treediffs treepending diffids diffmergeid
2773 global treediff treediffs treepending diffids diffmergeid
2774
2774
2775 set n [gets $gdtf line]
2775 set n [gets $gdtf line]
2776 if {$n < 0} {
2776 if {$n < 0} {
2777 if {![eof $gdtf]} return
2777 if {![eof $gdtf]} return
2778 close $gdtf
2778 close $gdtf
2779 set treediffs($ids) $treediff
2779 set treediffs($ids) $treediff
2780 unset treepending
2780 unset treepending
2781 if {$ids != $diffids} {
2781 if {$ids != $diffids} {
2782 gettreediffs $diffids
2782 gettreediffs $diffids
2783 } else {
2783 } else {
2784 if {[info exists diffmergeid]} {
2784 if {[info exists diffmergeid]} {
2785 contmergediff $ids
2785 contmergediff $ids
2786 } else {
2786 } else {
2787 addtocflist $ids
2787 addtocflist $ids
2788 }
2788 }
2789 }
2789 }
2790 return
2790 return
2791 }
2791 }
2792 set file [lindex $line 5]
2792 set file [lindex $line 5]
2793 lappend treediff $file
2793 lappend treediff $file
2794 }
2794 }
2795
2795
2796 proc getblobdiffs {ids} {
2796 proc getblobdiffs {ids} {
2797 global diffopts blobdifffd diffids env curdifftag curtagstart
2797 global diffopts blobdifffd diffids env curdifftag curtagstart
2798 global difffilestart nextupdate diffinhdr treediffs
2798 global difffilestart nextupdate diffinhdr treediffs
2799
2799
2800 set id [lindex $ids 0]
2800 set id [lindex $ids 0]
2801 set p [lindex $ids 1]
2801 set p [lindex $ids 1]
2802 set env(GIT_DIFF_OPTS) $diffopts
2802 set env(GIT_DIFF_OPTS) $diffopts
2803 set cmd [list | hg debug-diff-tree -r -p -C $p $id]
2803 set cmd [list | hg debug-diff-tree -r -p -C $p $id]
2804 if {[catch {set bdf [open $cmd r]} err]} {
2804 if {[catch {set bdf [open $cmd r]} err]} {
2805 puts "error getting diffs: $err"
2805 puts "error getting diffs: $err"
2806 return
2806 return
2807 }
2807 }
2808 set diffinhdr 0
2808 set diffinhdr 0
2809 fconfigure $bdf -blocking 0
2809 fconfigure $bdf -blocking 0
2810 set blobdifffd($ids) $bdf
2810 set blobdifffd($ids) $bdf
2811 set curdifftag Comments
2811 set curdifftag Comments
2812 set curtagstart 0.0
2812 set curtagstart 0.0
2813 catch {unset difffilestart}
2813 catch {unset difffilestart}
2814 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2814 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2815 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2815 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2816 }
2816 }
2817
2817
2818 proc getblobdiffline {bdf ids} {
2818 proc getblobdiffline {bdf ids} {
2819 global diffids blobdifffd ctext curdifftag curtagstart
2819 global diffids blobdifffd ctext curdifftag curtagstart
2820 global diffnexthead diffnextnote difffilestart
2820 global diffnexthead diffnextnote difffilestart
2821 global nextupdate diffinhdr treediffs
2821 global nextupdate diffinhdr treediffs
2822 global gaudydiff
2822 global gaudydiff
2823
2823
2824 set n [gets $bdf line]
2824 set n [gets $bdf line]
2825 if {$n < 0} {
2825 if {$n < 0} {
2826 if {[eof $bdf]} {
2826 if {[eof $bdf]} {
2827 close $bdf
2827 close $bdf
2828 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2828 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2829 $ctext tag add $curdifftag $curtagstart end
2829 $ctext tag add $curdifftag $curtagstart end
2830 }
2830 }
2831 }
2831 }
2832 return
2832 return
2833 }
2833 }
2834 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2834 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2835 return
2835 return
2836 }
2836 }
2837 $ctext conf -state normal
2837 $ctext conf -state normal
2838 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2838 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2839 # start of a new file
2839 # start of a new file
2840 $ctext insert end "\n"
2840 $ctext insert end "\n"
2841 $ctext tag add $curdifftag $curtagstart end
2841 $ctext tag add $curdifftag $curtagstart end
2842 set curtagstart [$ctext index "end - 1c"]
2842 set curtagstart [$ctext index "end - 1c"]
2843 set header $newname
2843 set header $newname
2844 set here [$ctext index "end - 1c"]
2844 set here [$ctext index "end - 1c"]
2845 set i [lsearch -exact $treediffs($diffids) $fname]
2845 set i [lsearch -exact $treediffs($diffids) $fname]
2846 if {$i >= 0} {
2846 if {$i >= 0} {
2847 set difffilestart($i) $here
2847 set difffilestart($i) $here
2848 incr i
2848 incr i
2849 $ctext mark set fmark.$i $here
2849 $ctext mark set fmark.$i $here
2850 $ctext mark gravity fmark.$i left
2850 $ctext mark gravity fmark.$i left
2851 }
2851 }
2852 if {$newname != $fname} {
2852 if {$newname != $fname} {
2853 set i [lsearch -exact $treediffs($diffids) $newname]
2853 set i [lsearch -exact $treediffs($diffids) $newname]
2854 if {$i >= 0} {
2854 if {$i >= 0} {
2855 set difffilestart($i) $here
2855 set difffilestart($i) $here
2856 incr i
2856 incr i
2857 $ctext mark set fmark.$i $here
2857 $ctext mark set fmark.$i $here
2858 $ctext mark gravity fmark.$i left
2858 $ctext mark gravity fmark.$i left
2859 }
2859 }
2860 }
2860 }
2861 set curdifftag "f:$fname"
2861 set curdifftag "f:$fname"
2862 $ctext tag delete $curdifftag
2862 $ctext tag delete $curdifftag
2863 set l [expr {(78 - [string length $header]) / 2}]
2863 set l [expr {(78 - [string length $header]) / 2}]
2864 set pad [string range "----------------------------------------" 1 $l]
2864 set pad [string range "----------------------------------------" 1 $l]
2865 $ctext insert end "$pad $header $pad\n" filesep
2865 $ctext insert end "$pad $header $pad\n" filesep
2866 set diffinhdr 1
2866 set diffinhdr 1
2867 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2867 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2868 set diffinhdr 0
2868 set diffinhdr 0
2869 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2869 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2870 $line match f1l f1c f2l f2c rest]} {
2870 $line match f1l f1c f2l f2c rest]} {
2871 if {$gaudydiff} {
2871 if {$gaudydiff} {
2872 $ctext insert end "\t" hunksep
2872 $ctext insert end "\t" hunksep
2873 $ctext insert end " $f1l " d0 " $f2l " d1
2873 $ctext insert end " $f1l " d0 " $f2l " d1
2874 $ctext insert end " $rest \n" hunksep
2874 $ctext insert end " $rest \n" hunksep
2875 } else {
2875 } else {
2876 $ctext insert end "$line\n" hunksep
2876 $ctext insert end "$line\n" hunksep
2877 }
2877 }
2878 set diffinhdr 0
2878 set diffinhdr 0
2879 } else {
2879 } else {
2880 set x [string range $line 0 0]
2880 set x [string range $line 0 0]
2881 if {$x == "-" || $x == "+"} {
2881 if {$x == "-" || $x == "+"} {
2882 set tag [expr {$x == "+"}]
2882 set tag [expr {$x == "+"}]
2883 if {$gaudydiff} {
2883 if {$gaudydiff} {
2884 set line [string range $line 1 end]
2884 set line [string range $line 1 end]
2885 }
2885 }
2886 $ctext insert end "$line\n" d$tag
2886 $ctext insert end "$line\n" d$tag
2887 } elseif {$x == " "} {
2887 } elseif {$x == " "} {
2888 if {$gaudydiff} {
2888 if {$gaudydiff} {
2889 set line [string range $line 1 end]
2889 set line [string range $line 1 end]
2890 }
2890 }
2891 $ctext insert end "$line\n"
2891 $ctext insert end "$line\n"
2892 } elseif {$diffinhdr || $x == "\\"} {
2892 } elseif {$diffinhdr || $x == "\\"} {
2893 # e.g. "\ No newline at end of file"
2893 # e.g. "\ No newline at end of file"
2894 $ctext insert end "$line\n" filesep
2894 $ctext insert end "$line\n" filesep
2895 } else {
2895 } else {
2896 # Something else we don't recognize
2896 # Something else we don't recognize
2897 if {$curdifftag != "Comments"} {
2897 if {$curdifftag != "Comments"} {
2898 $ctext insert end "\n"
2898 $ctext insert end "\n"
2899 $ctext tag add $curdifftag $curtagstart end
2899 $ctext tag add $curdifftag $curtagstart end
2900 set curtagstart [$ctext index "end - 1c"]
2900 set curtagstart [$ctext index "end - 1c"]
2901 set curdifftag Comments
2901 set curdifftag Comments
2902 }
2902 }
2903 $ctext insert end "$line\n" filesep
2903 $ctext insert end "$line\n" filesep
2904 }
2904 }
2905 }
2905 }
2906 $ctext conf -state disabled
2906 $ctext conf -state disabled
2907 if {[clock clicks -milliseconds] >= $nextupdate} {
2907 if {[clock clicks -milliseconds] >= $nextupdate} {
2908 incr nextupdate 100
2908 incr nextupdate 100
2909 fileevent $bdf readable {}
2909 fileevent $bdf readable {}
2910 update
2910 update
2911 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2911 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2912 }
2912 }
2913 }
2913 }
2914
2914
2915 proc nextfile {} {
2915 proc nextfile {} {
2916 global difffilestart ctext
2916 global difffilestart ctext
2917 set here [$ctext index @0,0]
2917 set here [$ctext index @0,0]
2918 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2918 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2919 if {[$ctext compare $difffilestart($i) > $here]} {
2919 if {[$ctext compare $difffilestart($i) > $here]} {
2920 if {![info exists pos]
2920 if {![info exists pos]
2921 || [$ctext compare $difffilestart($i) < $pos]} {
2921 || [$ctext compare $difffilestart($i) < $pos]} {
2922 set pos $difffilestart($i)
2922 set pos $difffilestart($i)
2923 }
2923 }
2924 }
2924 }
2925 }
2925 }
2926 if {[info exists pos]} {
2926 if {[info exists pos]} {
2927 $ctext yview $pos
2927 $ctext yview $pos
2928 }
2928 }
2929 }
2929 }
2930
2930
2931 proc listboxsel {} {
2931 proc listboxsel {} {
2932 global ctext cflist currentid
2932 global ctext cflist currentid
2933 if {![info exists currentid]} return
2933 if {![info exists currentid]} return
2934 set sel [lsort [$cflist curselection]]
2934 set sel [lsort [$cflist curselection]]
2935 if {$sel eq {}} return
2935 if {$sel eq {}} return
2936 set first [lindex $sel 0]
2936 set first [lindex $sel 0]
2937 catch {$ctext yview fmark.$first}
2937 catch {$ctext yview fmark.$first}
2938 }
2938 }
2939
2939
2940 proc setcoords {} {
2940 proc setcoords {} {
2941 global linespc charspc canvx0 canvy0 mainfont
2941 global linespc charspc canvx0 canvy0 mainfont
2942 global xspc1 xspc2 lthickness
2942 global xspc1 xspc2 lthickness
2943
2943
2944 set linespc [font metrics $mainfont -linespace]
2944 set linespc [font metrics $mainfont -linespace]
2945 set charspc [font measure $mainfont "m"]
2945 set charspc [font measure $mainfont "m"]
2946 set canvy0 [expr 3 + 0.5 * $linespc]
2946 set canvy0 [expr 3 + 0.5 * $linespc]
2947 set canvx0 [expr 3 + 0.5 * $linespc]
2947 set canvx0 [expr 3 + 0.5 * $linespc]
2948 set lthickness [expr {int($linespc / 9) + 1}]
2948 set lthickness [expr {int($linespc / 9) + 1}]
2949 set xspc1(0) $linespc
2949 set xspc1(0) $linespc
2950 set xspc2 $linespc
2950 set xspc2 $linespc
2951 }
2951 }
2952
2952
2953 proc redisplay {} {
2953 proc redisplay {} {
2954 global stopped redisplaying phase
2954 global stopped redisplaying phase
2955 if {$stopped > 1} return
2955 if {$stopped > 1} return
2956 if {$phase == "getcommits"} return
2956 if {$phase == "getcommits"} return
2957 set redisplaying 1
2957 set redisplaying 1
2958 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2958 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2959 set stopped 1
2959 set stopped 1
2960 } else {
2960 } else {
2961 drawgraph
2961 drawgraph
2962 }
2962 }
2963 }
2963 }
2964
2964
2965 proc incrfont {inc} {
2965 proc incrfont {inc} {
2966 global mainfont namefont textfont ctext canv phase
2966 global mainfont namefont textfont ctext canv phase
2967 global stopped entries
2967 global stopped entries
2968 unmarkmatches
2968 unmarkmatches
2969 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2969 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2970 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2970 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2971 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2971 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2972 setcoords
2972 setcoords
2973 $ctext conf -font $textfont
2973 $ctext conf -font $textfont
2974 $ctext tag conf filesep -font [concat $textfont bold]
2974 $ctext tag conf filesep -font [concat $textfont bold]
2975 foreach e $entries {
2975 foreach e $entries {
2976 $e conf -font $mainfont
2976 $e conf -font $mainfont
2977 }
2977 }
2978 if {$phase == "getcommits"} {
2978 if {$phase == "getcommits"} {
2979 $canv itemconf textitems -font $mainfont
2979 $canv itemconf textitems -font $mainfont
2980 }
2980 }
2981 redisplay
2981 redisplay
2982 }
2982 }
2983
2983
2984 proc clearsha1 {} {
2984 proc clearsha1 {} {
2985 global sha1entry sha1string
2985 global sha1entry sha1string
2986 if {[string length $sha1string] == 40} {
2986 if {[string length $sha1string] == 40} {
2987 $sha1entry delete 0 end
2987 $sha1entry delete 0 end
2988 }
2988 }
2989 }
2989 }
2990
2990
2991 proc sha1change {n1 n2 op} {
2991 proc sha1change {n1 n2 op} {
2992 global sha1string currentid sha1but
2992 global sha1string currentid sha1but
2993 if {$sha1string == {}
2993 if {$sha1string == {}
2994 || ([info exists currentid] && $sha1string == $currentid)} {
2994 || ([info exists currentid] && $sha1string == $currentid)} {
2995 set state disabled
2995 set state disabled
2996 } else {
2996 } else {
2997 set state normal
2997 set state normal
2998 }
2998 }
2999 if {[$sha1but cget -state] == $state} return
2999 if {[$sha1but cget -state] == $state} return
3000 if {$state == "normal"} {
3000 if {$state == "normal"} {
3001 $sha1but conf -state normal -relief raised -text "Goto: "
3001 $sha1but conf -state normal -relief raised -text "Goto: "
3002 } else {
3002 } else {
3003 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3003 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3004 }
3004 }
3005 }
3005 }
3006
3006
3007 proc gotocommit {} {
3007 proc gotocommit {} {
3008 global sha1string currentid idline tagids
3008 global sha1string currentid idline tagids
3009 global lineid numcommits
3009 global lineid numcommits
3010
3010
3011 if {$sha1string == {}
3011 if {$sha1string == {}
3012 || ([info exists currentid] && $sha1string == $currentid)} return
3012 || ([info exists currentid] && $sha1string == $currentid)} return
3013 if {[info exists tagids($sha1string)]} {
3013 if {[info exists tagids($sha1string)]} {
3014 set id $tagids($sha1string)
3014 set id $tagids($sha1string)
3015 } else {
3015 } else {
3016 set id [string tolower $sha1string]
3016 set id [string tolower $sha1string]
3017 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3017 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3018 set matches {}
3018 set matches {}
3019 for {set l 0} {$l < $numcommits} {incr l} {
3019 for {set l 0} {$l < $numcommits} {incr l} {
3020 if {[string match $id* $lineid($l)]} {
3020 if {[string match $id* $lineid($l)]} {
3021 lappend matches $lineid($l)
3021 lappend matches $lineid($l)
3022 }
3022 }
3023 }
3023 }
3024 if {$matches ne {}} {
3024 if {$matches ne {}} {
3025 if {[llength $matches] > 1} {
3025 if {[llength $matches] > 1} {
3026 error_popup "Short SHA1 id $id is ambiguous"
3026 error_popup "Short SHA1 id $id is ambiguous"
3027 return
3027 return
3028 }
3028 }
3029 set id [lindex $matches 0]
3029 set id [lindex $matches 0]
3030 }
3030 }
3031 }
3031 }
3032 }
3032 }
3033 if {[info exists idline($id)]} {
3033 if {[info exists idline($id)]} {
3034 selectline $idline($id) 1
3034 selectline $idline($id) 1
3035 return
3035 return
3036 }
3036 }
3037 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3037 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3038 set type "SHA1 id"
3038 set type "SHA1 id"
3039 } else {
3039 } else {
3040 set type "Tag"
3040 set type "Tag"
3041 }
3041 }
3042 error_popup "$type $sha1string is not known"
3042 error_popup "$type $sha1string is not known"
3043 }
3043 }
3044
3044
3045 proc lineenter {x y id} {
3045 proc lineenter {x y id} {
3046 global hoverx hovery hoverid hovertimer
3046 global hoverx hovery hoverid hovertimer
3047 global commitinfo canv
3047 global commitinfo canv
3048
3048
3049 if {![info exists commitinfo($id)]} return
3049 if {![info exists commitinfo($id)]} return
3050 set hoverx $x
3050 set hoverx $x
3051 set hovery $y
3051 set hovery $y
3052 set hoverid $id
3052 set hoverid $id
3053 if {[info exists hovertimer]} {
3053 if {[info exists hovertimer]} {
3054 after cancel $hovertimer
3054 after cancel $hovertimer
3055 }
3055 }
3056 set hovertimer [after 500 linehover]
3056 set hovertimer [after 500 linehover]
3057 $canv delete hover
3057 $canv delete hover
3058 }
3058 }
3059
3059
3060 proc linemotion {x y id} {
3060 proc linemotion {x y id} {
3061 global hoverx hovery hoverid hovertimer
3061 global hoverx hovery hoverid hovertimer
3062
3062
3063 if {[info exists hoverid] && $id == $hoverid} {
3063 if {[info exists hoverid] && $id == $hoverid} {
3064 set hoverx $x
3064 set hoverx $x
3065 set hovery $y
3065 set hovery $y
3066 if {[info exists hovertimer]} {
3066 if {[info exists hovertimer]} {
3067 after cancel $hovertimer
3067 after cancel $hovertimer
3068 }
3068 }
3069 set hovertimer [after 500 linehover]
3069 set hovertimer [after 500 linehover]
3070 }
3070 }
3071 }
3071 }
3072
3072
3073 proc lineleave {id} {
3073 proc lineleave {id} {
3074 global hoverid hovertimer canv
3074 global hoverid hovertimer canv
3075
3075
3076 if {[info exists hoverid] && $id == $hoverid} {
3076 if {[info exists hoverid] && $id == $hoverid} {
3077 $canv delete hover
3077 $canv delete hover
3078 if {[info exists hovertimer]} {
3078 if {[info exists hovertimer]} {
3079 after cancel $hovertimer
3079 after cancel $hovertimer
3080 unset hovertimer
3080 unset hovertimer
3081 }
3081 }
3082 unset hoverid
3082 unset hoverid
3083 }
3083 }
3084 }
3084 }
3085
3085
3086 proc linehover {} {
3086 proc linehover {} {
3087 global hoverx hovery hoverid hovertimer
3087 global hoverx hovery hoverid hovertimer
3088 global canv linespc lthickness
3088 global canv linespc lthickness
3089 global commitinfo mainfont
3089 global commitinfo mainfont
3090
3090
3091 set text [lindex $commitinfo($hoverid) 0]
3091 set text [lindex $commitinfo($hoverid) 0]
3092 set ymax [lindex [$canv cget -scrollregion] 3]
3092 set ymax [lindex [$canv cget -scrollregion] 3]
3093 if {$ymax == {}} return
3093 if {$ymax == {}} return
3094 set yfrac [lindex [$canv yview] 0]
3094 set yfrac [lindex [$canv yview] 0]
3095 set x [expr {$hoverx + 2 * $linespc}]
3095 set x [expr {$hoverx + 2 * $linespc}]
3096 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3096 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3097 set x0 [expr {$x - 2 * $lthickness}]
3097 set x0 [expr {$x - 2 * $lthickness}]
3098 set y0 [expr {$y - 2 * $lthickness}]
3098 set y0 [expr {$y - 2 * $lthickness}]
3099 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3099 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3100 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3100 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3101 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3101 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3102 -fill \#ffff80 -outline black -width 1 -tags hover]
3102 -fill \#ffff80 -outline black -width 1 -tags hover]
3103 $canv raise $t
3103 $canv raise $t
3104 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3104 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3105 $canv raise $t
3105 $canv raise $t
3106 }
3106 }
3107
3107
3108 proc clickisonarrow {id y} {
3108 proc clickisonarrow {id y} {
3109 global mainline mainlinearrow sidelines lthickness
3109 global mainline mainlinearrow sidelines lthickness
3110
3110
3111 set thresh [expr {2 * $lthickness + 6}]
3111 set thresh [expr {2 * $lthickness + 6}]
3112 if {[info exists mainline($id)]} {
3112 if {[info exists mainline($id)]} {
3113 if {$mainlinearrow($id) ne "none"} {
3113 if {$mainlinearrow($id) ne "none"} {
3114 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3114 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3115 return "up"
3115 return "up"
3116 }
3116 }
3117 }
3117 }
3118 }
3118 }
3119 if {[info exists sidelines($id)]} {
3119 if {[info exists sidelines($id)]} {
3120 foreach ls $sidelines($id) {
3120 foreach ls $sidelines($id) {
3121 set coords [lindex $ls 0]
3121 set coords [lindex $ls 0]
3122 set arrow [lindex $ls 2]
3122 set arrow [lindex $ls 2]
3123 if {$arrow eq "first" || $arrow eq "both"} {
3123 if {$arrow eq "first" || $arrow eq "both"} {
3124 if {abs([lindex $coords 1] - $y) < $thresh} {
3124 if {abs([lindex $coords 1] - $y) < $thresh} {
3125 return "up"
3125 return "up"
3126 }
3126 }
3127 }
3127 }
3128 if {$arrow eq "last" || $arrow eq "both"} {
3128 if {$arrow eq "last" || $arrow eq "both"} {
3129 if {abs([lindex $coords end] - $y) < $thresh} {
3129 if {abs([lindex $coords end] - $y) < $thresh} {
3130 return "down"
3130 return "down"
3131 }
3131 }
3132 }
3132 }
3133 }
3133 }
3134 }
3134 }
3135 return {}
3135 return {}
3136 }
3136 }
3137
3137
3138 proc arrowjump {id dirn y} {
3138 proc arrowjump {id dirn y} {
3139 global mainline sidelines canv
3139 global mainline sidelines canv
3140
3140
3141 set yt {}
3141 set yt {}
3142 if {$dirn eq "down"} {
3142 if {$dirn eq "down"} {
3143 if {[info exists mainline($id)]} {
3143 if {[info exists mainline($id)]} {
3144 set y1 [lindex $mainline($id) 1]
3144 set y1 [lindex $mainline($id) 1]
3145 if {$y1 > $y} {
3145 if {$y1 > $y} {
3146 set yt $y1
3146 set yt $y1
3147 }
3147 }
3148 }
3148 }
3149 if {[info exists sidelines($id)]} {
3149 if {[info exists sidelines($id)]} {
3150 foreach ls $sidelines($id) {
3150 foreach ls $sidelines($id) {
3151 set y1 [lindex $ls 0 1]
3151 set y1 [lindex $ls 0 1]
3152 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3152 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3153 set yt $y1
3153 set yt $y1
3154 }
3154 }
3155 }
3155 }
3156 }
3156 }
3157 } else {
3157 } else {
3158 if {[info exists sidelines($id)]} {
3158 if {[info exists sidelines($id)]} {
3159 foreach ls $sidelines($id) {
3159 foreach ls $sidelines($id) {
3160 set y1 [lindex $ls 0 end]
3160 set y1 [lindex $ls 0 end]
3161 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3161 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3162 set yt $y1
3162 set yt $y1
3163 }
3163 }
3164 }
3164 }
3165 }
3165 }
3166 }
3166 }
3167 if {$yt eq {}} return
3167 if {$yt eq {}} return
3168 set ymax [lindex [$canv cget -scrollregion] 3]
3168 set ymax [lindex [$canv cget -scrollregion] 3]
3169 if {$ymax eq {} || $ymax <= 0} return
3169 if {$ymax eq {} || $ymax <= 0} return
3170 set view [$canv yview]
3170 set view [$canv yview]
3171 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3171 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3172 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3172 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3173 if {$yfrac < 0} {
3173 if {$yfrac < 0} {
3174 set yfrac 0
3174 set yfrac 0
3175 }
3175 }
3176 $canv yview moveto $yfrac
3176 $canv yview moveto $yfrac
3177 }
3177 }
3178
3178
3179 proc lineclick {x y id isnew} {
3179 proc lineclick {x y id isnew} {
3180 global ctext commitinfo children cflist canv thickerline
3180 global ctext commitinfo children cflist canv thickerline
3181
3181
3182 unmarkmatches
3182 unmarkmatches
3183 unselectline
3183 unselectline
3184 normalline
3184 normalline
3185 $canv delete hover
3185 $canv delete hover
3186 # draw this line thicker than normal
3186 # draw this line thicker than normal
3187 drawlines $id 1
3187 drawlines $id 1
3188 set thickerline $id
3188 set thickerline $id
3189 if {$isnew} {
3189 if {$isnew} {
3190 set ymax [lindex [$canv cget -scrollregion] 3]
3190 set ymax [lindex [$canv cget -scrollregion] 3]
3191 if {$ymax eq {}} return
3191 if {$ymax eq {}} return
3192 set yfrac [lindex [$canv yview] 0]
3192 set yfrac [lindex [$canv yview] 0]
3193 set y [expr {$y + $yfrac * $ymax}]
3193 set y [expr {$y + $yfrac * $ymax}]
3194 }
3194 }
3195 set dirn [clickisonarrow $id $y]
3195 set dirn [clickisonarrow $id $y]
3196 if {$dirn ne {}} {
3196 if {$dirn ne {}} {
3197 arrowjump $id $dirn $y
3197 arrowjump $id $dirn $y
3198 return
3198 return
3199 }
3199 }
3200
3200
3201 if {$isnew} {
3201 if {$isnew} {
3202 addtohistory [list lineclick $x $y $id 0]
3202 addtohistory [list lineclick $x $y $id 0]
3203 }
3203 }
3204 # fill the details pane with info about this line
3204 # fill the details pane with info about this line
3205 $ctext conf -state normal
3205 $ctext conf -state normal
3206 $ctext delete 0.0 end
3206 $ctext delete 0.0 end
3207 $ctext tag conf link -foreground blue -underline 1
3207 $ctext tag conf link -foreground blue -underline 1
3208 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3208 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3209 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3209 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3210 $ctext insert end "Parent:\t"
3210 $ctext insert end "Parent:\t"
3211 $ctext insert end $id [list link link0]
3211 $ctext insert end $id [list link link0]
3212 $ctext tag bind link0 <1> [list selbyid $id]
3212 $ctext tag bind link0 <1> [list selbyid $id]
3213 set info $commitinfo($id)
3213 set info $commitinfo($id)
3214 $ctext insert end "\n\t[lindex $info 0]\n"
3214 $ctext insert end "\n\t[lindex $info 0]\n"
3215 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3215 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3216 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3216 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3217 if {[info exists children($id)]} {
3217 if {[info exists children($id)]} {
3218 $ctext insert end "\nChildren:"
3218 $ctext insert end "\nChildren:"
3219 set i 0
3219 set i 0
3220 foreach child $children($id) {
3220 foreach child $children($id) {
3221 incr i
3221 incr i
3222 set info $commitinfo($child)
3222 set info $commitinfo($child)
3223 $ctext insert end "\n\t"
3223 $ctext insert end "\n\t"
3224 $ctext insert end $child [list link link$i]
3224 $ctext insert end $child [list link link$i]
3225 $ctext tag bind link$i <1> [list selbyid $child]
3225 $ctext tag bind link$i <1> [list selbyid $child]
3226 $ctext insert end "\n\t[lindex $info 0]"
3226 $ctext insert end "\n\t[lindex $info 0]"
3227 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3227 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3228 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3228 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3229 }
3229 }
3230 }
3230 }
3231 $ctext conf -state disabled
3231 $ctext conf -state disabled
3232
3232
3233 $cflist delete 0 end
3233 $cflist delete 0 end
3234 }
3234 }
3235
3235
3236 proc normalline {} {
3236 proc normalline {} {
3237 global thickerline
3237 global thickerline
3238 if {[info exists thickerline]} {
3238 if {[info exists thickerline]} {
3239 drawlines $thickerline 0
3239 drawlines $thickerline 0
3240 unset thickerline
3240 unset thickerline
3241 }
3241 }
3242 }
3242 }
3243
3243
3244 proc selbyid {id} {
3244 proc selbyid {id} {
3245 global idline
3245 global idline
3246 if {[info exists idline($id)]} {
3246 if {[info exists idline($id)]} {
3247 selectline $idline($id) 1
3247 selectline $idline($id) 1
3248 }
3248 }
3249 }
3249 }
3250
3250
3251 proc mstime {} {
3251 proc mstime {} {
3252 global startmstime
3252 global startmstime
3253 if {![info exists startmstime]} {
3253 if {![info exists startmstime]} {
3254 set startmstime [clock clicks -milliseconds]
3254 set startmstime [clock clicks -milliseconds]
3255 }
3255 }
3256 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3256 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3257 }
3257 }
3258
3258
3259 proc rowmenu {x y id} {
3259 proc rowmenu {x y id} {
3260 global rowctxmenu idline selectedline rowmenuid
3260 global rowctxmenu idline selectedline rowmenuid
3261
3261
3262 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3262 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3263 set state disabled
3263 set state disabled
3264 } else {
3264 } else {
3265 set state normal
3265 set state normal
3266 }
3266 }
3267 $rowctxmenu entryconfigure 0 -state $state
3267 $rowctxmenu entryconfigure 0 -state $state
3268 $rowctxmenu entryconfigure 1 -state $state
3268 $rowctxmenu entryconfigure 1 -state $state
3269 $rowctxmenu entryconfigure 2 -state $state
3269 $rowctxmenu entryconfigure 2 -state $state
3270 set rowmenuid $id
3270 set rowmenuid $id
3271 tk_popup $rowctxmenu $x $y
3271 tk_popup $rowctxmenu $x $y
3272 }
3272 }
3273
3273
3274 proc diffvssel {dirn} {
3274 proc diffvssel {dirn} {
3275 global rowmenuid selectedline lineid
3275 global rowmenuid selectedline lineid
3276
3276
3277 if {![info exists selectedline]} return
3277 if {![info exists selectedline]} return
3278 if {$dirn} {
3278 if {$dirn} {
3279 set oldid $lineid($selectedline)
3279 set oldid $lineid($selectedline)
3280 set newid $rowmenuid
3280 set newid $rowmenuid
3281 } else {
3281 } else {
3282 set oldid $rowmenuid
3282 set oldid $rowmenuid
3283 set newid $lineid($selectedline)
3283 set newid $lineid($selectedline)
3284 }
3284 }
3285 addtohistory [list doseldiff $oldid $newid]
3285 addtohistory [list doseldiff $oldid $newid]
3286 doseldiff $oldid $newid
3286 doseldiff $oldid $newid
3287 }
3287 }
3288
3288
3289 proc doseldiff {oldid newid} {
3289 proc doseldiff {oldid newid} {
3290 global ctext cflist
3290 global ctext cflist
3291 global commitinfo
3291 global commitinfo
3292
3292
3293 $ctext conf -state normal
3293 $ctext conf -state normal
3294 $ctext delete 0.0 end
3294 $ctext delete 0.0 end
3295 $ctext mark set fmark.0 0.0
3295 $ctext mark set fmark.0 0.0
3296 $ctext mark gravity fmark.0 left
3296 $ctext mark gravity fmark.0 left
3297 $cflist delete 0 end
3297 $cflist delete 0 end
3298 $cflist insert end "Top"
3298 $cflist insert end "Top"
3299 $ctext insert end "From "
3299 $ctext insert end "From "
3300 $ctext tag conf link -foreground blue -underline 1
3300 $ctext tag conf link -foreground blue -underline 1
3301 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3301 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3302 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3302 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3303 $ctext tag bind link0 <1> [list selbyid $oldid]
3303 $ctext tag bind link0 <1> [list selbyid $oldid]
3304 $ctext insert end $oldid [list link link0]
3304 $ctext insert end $oldid [list link link0]
3305 $ctext insert end "\n "
3305 $ctext insert end "\n "
3306 $ctext insert end [lindex $commitinfo($oldid) 0]
3306 $ctext insert end [lindex $commitinfo($oldid) 0]
3307 $ctext insert end "\n\nTo "
3307 $ctext insert end "\n\nTo "
3308 $ctext tag bind link1 <1> [list selbyid $newid]
3308 $ctext tag bind link1 <1> [list selbyid $newid]
3309 $ctext insert end $newid [list link link1]
3309 $ctext insert end $newid [list link link1]
3310 $ctext insert end "\n "
3310 $ctext insert end "\n "
3311 $ctext insert end [lindex $commitinfo($newid) 0]
3311 $ctext insert end [lindex $commitinfo($newid) 0]
3312 $ctext insert end "\n"
3312 $ctext insert end "\n"
3313 $ctext conf -state disabled
3313 $ctext conf -state disabled
3314 $ctext tag delete Comments
3314 $ctext tag delete Comments
3315 $ctext tag remove found 1.0 end
3315 $ctext tag remove found 1.0 end
3316 startdiff [list $newid $oldid]
3316 startdiff [list $newid $oldid]
3317 }
3317 }
3318
3318
3319 proc mkpatch {} {
3319 proc mkpatch {} {
3320 global rowmenuid currentid commitinfo patchtop patchnum
3320 global rowmenuid currentid commitinfo patchtop patchnum
3321
3321
3322 if {![info exists currentid]} return
3322 if {![info exists currentid]} return
3323 set oldid $currentid
3323 set oldid $currentid
3324 set oldhead [lindex $commitinfo($oldid) 0]
3324 set oldhead [lindex $commitinfo($oldid) 0]
3325 set newid $rowmenuid
3325 set newid $rowmenuid
3326 set newhead [lindex $commitinfo($newid) 0]
3326 set newhead [lindex $commitinfo($newid) 0]
3327 set top .patch
3327 set top .patch
3328 set patchtop $top
3328 set patchtop $top
3329 catch {destroy $top}
3329 catch {destroy $top}
3330 toplevel $top
3330 toplevel $top
3331 label $top.title -text "Generate patch"
3331 label $top.title -text "Generate patch"
3332 grid $top.title - -pady 10
3332 grid $top.title - -pady 10
3333 label $top.from -text "From:"
3333 label $top.from -text "From:"
3334 entry $top.fromsha1 -width 40 -relief flat
3334 entry $top.fromsha1 -width 40 -relief flat
3335 $top.fromsha1 insert 0 $oldid
3335 $top.fromsha1 insert 0 $oldid
3336 $top.fromsha1 conf -state readonly
3336 $top.fromsha1 conf -state readonly
3337 grid $top.from $top.fromsha1 -sticky w
3337 grid $top.from $top.fromsha1 -sticky w
3338 entry $top.fromhead -width 60 -relief flat
3338 entry $top.fromhead -width 60 -relief flat
3339 $top.fromhead insert 0 $oldhead
3339 $top.fromhead insert 0 $oldhead
3340 $top.fromhead conf -state readonly
3340 $top.fromhead conf -state readonly
3341 grid x $top.fromhead -sticky w
3341 grid x $top.fromhead -sticky w
3342 label $top.to -text "To:"
3342 label $top.to -text "To:"
3343 entry $top.tosha1 -width 40 -relief flat
3343 entry $top.tosha1 -width 40 -relief flat
3344 $top.tosha1 insert 0 $newid
3344 $top.tosha1 insert 0 $newid
3345 $top.tosha1 conf -state readonly
3345 $top.tosha1 conf -state readonly
3346 grid $top.to $top.tosha1 -sticky w
3346 grid $top.to $top.tosha1 -sticky w
3347 entry $top.tohead -width 60 -relief flat
3347 entry $top.tohead -width 60 -relief flat
3348 $top.tohead insert 0 $newhead
3348 $top.tohead insert 0 $newhead
3349 $top.tohead conf -state readonly
3349 $top.tohead conf -state readonly
3350 grid x $top.tohead -sticky w
3350 grid x $top.tohead -sticky w
3351 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3351 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3352 grid $top.rev x -pady 10
3352 grid $top.rev x -pady 10
3353 label $top.flab -text "Output file:"
3353 label $top.flab -text "Output file:"
3354 entry $top.fname -width 60
3354 entry $top.fname -width 60
3355 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3355 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3356 incr patchnum
3356 incr patchnum
3357 grid $top.flab $top.fname -sticky w
3357 grid $top.flab $top.fname -sticky w
3358 frame $top.buts
3358 frame $top.buts
3359 button $top.buts.gen -text "Generate" -command mkpatchgo
3359 button $top.buts.gen -text "Generate" -command mkpatchgo
3360 button $top.buts.can -text "Cancel" -command mkpatchcan
3360 button $top.buts.can -text "Cancel" -command mkpatchcan
3361 grid $top.buts.gen $top.buts.can
3361 grid $top.buts.gen $top.buts.can
3362 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3362 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3363 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3363 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3364 grid $top.buts - -pady 10 -sticky ew
3364 grid $top.buts - -pady 10 -sticky ew
3365 focus $top.fname
3365 focus $top.fname
3366 }
3366 }
3367
3367
3368 proc mkpatchrev {} {
3368 proc mkpatchrev {} {
3369 global patchtop
3369 global patchtop
3370
3370
3371 set oldid [$patchtop.fromsha1 get]
3371 set oldid [$patchtop.fromsha1 get]
3372 set oldhead [$patchtop.fromhead get]
3372 set oldhead [$patchtop.fromhead get]
3373 set newid [$patchtop.tosha1 get]
3373 set newid [$patchtop.tosha1 get]
3374 set newhead [$patchtop.tohead get]
3374 set newhead [$patchtop.tohead get]
3375 foreach e [list fromsha1 fromhead tosha1 tohead] \
3375 foreach e [list fromsha1 fromhead tosha1 tohead] \
3376 v [list $newid $newhead $oldid $oldhead] {
3376 v [list $newid $newhead $oldid $oldhead] {
3377 $patchtop.$e conf -state normal
3377 $patchtop.$e conf -state normal
3378 $patchtop.$e delete 0 end
3378 $patchtop.$e delete 0 end
3379 $patchtop.$e insert 0 $v
3379 $patchtop.$e insert 0 $v
3380 $patchtop.$e conf -state readonly
3380 $patchtop.$e conf -state readonly
3381 }
3381 }
3382 }
3382 }
3383
3383
3384 proc mkpatchgo {} {
3384 proc mkpatchgo {} {
3385 global patchtop
3385 global patchtop
3386
3386
3387 set oldid [$patchtop.fromsha1 get]
3387 set oldid [$patchtop.fromsha1 get]
3388 set newid [$patchtop.tosha1 get]
3388 set newid [$patchtop.tosha1 get]
3389 set fname [$patchtop.fname get]
3389 set fname [$patchtop.fname get]
3390 if {[catch {exec hg debug-diff-tree -p $oldid $newid >$fname &} err]} {
3390 if {[catch {exec hg debug-diff-tree -p $oldid $newid >$fname &} err]} {
3391 error_popup "Error creating patch: $err"
3391 error_popup "Error creating patch: $err"
3392 }
3392 }
3393 catch {destroy $patchtop}
3393 catch {destroy $patchtop}
3394 unset patchtop
3394 unset patchtop
3395 }
3395 }
3396
3396
3397 proc mkpatchcan {} {
3397 proc mkpatchcan {} {
3398 global patchtop
3398 global patchtop
3399
3399
3400 catch {destroy $patchtop}
3400 catch {destroy $patchtop}
3401 unset patchtop
3401 unset patchtop
3402 }
3402 }
3403
3403
3404 proc mktag {} {
3404 proc mktag {} {
3405 global rowmenuid mktagtop commitinfo
3405 global rowmenuid mktagtop commitinfo
3406
3406
3407 set top .maketag
3407 set top .maketag
3408 set mktagtop $top
3408 set mktagtop $top
3409 catch {destroy $top}
3409 catch {destroy $top}
3410 toplevel $top
3410 toplevel $top
3411 label $top.title -text "Create tag"
3411 label $top.title -text "Create tag"
3412 grid $top.title - -pady 10
3412 grid $top.title - -pady 10
3413 label $top.id -text "ID:"
3413 label $top.id -text "ID:"
3414 entry $top.sha1 -width 40 -relief flat
3414 entry $top.sha1 -width 40 -relief flat
3415 $top.sha1 insert 0 $rowmenuid
3415 $top.sha1 insert 0 $rowmenuid
3416 $top.sha1 conf -state readonly
3416 $top.sha1 conf -state readonly
3417 grid $top.id $top.sha1 -sticky w
3417 grid $top.id $top.sha1 -sticky w
3418 entry $top.head -width 60 -relief flat
3418 entry $top.head -width 60 -relief flat
3419 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3419 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3420 $top.head conf -state readonly
3420 $top.head conf -state readonly
3421 grid x $top.head -sticky w
3421 grid x $top.head -sticky w
3422 label $top.tlab -text "Tag name:"
3422 label $top.tlab -text "Tag name:"
3423 entry $top.tag -width 60
3423 entry $top.tag -width 60
3424 grid $top.tlab $top.tag -sticky w
3424 grid $top.tlab $top.tag -sticky w
3425 frame $top.buts
3425 frame $top.buts
3426 button $top.buts.gen -text "Create" -command mktaggo
3426 button $top.buts.gen -text "Create" -command mktaggo
3427 button $top.buts.can -text "Cancel" -command mktagcan
3427 button $top.buts.can -text "Cancel" -command mktagcan
3428 grid $top.buts.gen $top.buts.can
3428 grid $top.buts.gen $top.buts.can
3429 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3429 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3430 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3430 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3431 grid $top.buts - -pady 10 -sticky ew
3431 grid $top.buts - -pady 10 -sticky ew
3432 focus $top.tag
3432 focus $top.tag
3433 }
3433 }
3434
3434
3435 proc domktag {} {
3435 proc domktag {} {
3436 global mktagtop env tagids idtags
3436 global mktagtop env tagids idtags
3437
3437
3438 set id [$mktagtop.sha1 get]
3438 set id [$mktagtop.sha1 get]
3439 set tag [$mktagtop.tag get]
3439 set tag [$mktagtop.tag get]
3440 if {$tag == {}} {
3440 if {$tag == {}} {
3441 error_popup "No tag name specified"
3441 error_popup "No tag name specified"
3442 return
3442 return
3443 }
3443 }
3444 if {[info exists tagids($tag)]} {
3444 if {[info exists tagids($tag)]} {
3445 error_popup "Tag \"$tag\" already exists"
3445 error_popup "Tag \"$tag\" already exists"
3446 return
3446 return
3447 }
3447 }
3448 if {[catch {
3448 if {[catch {
3449 set out [exec hg tag $tag $id]
3449 set out [exec hg tag $tag $id]
3450 } err]} {
3450 } err]} {
3451 error_popup "Error creating tag: $err"
3451 error_popup "Error creating tag: $err"
3452 return
3452 return
3453 }
3453 }
3454
3454
3455 set tagids($tag) $id
3455 set tagids($tag) $id
3456 lappend idtags($id) $tag
3456 lappend idtags($id) $tag
3457 redrawtags $id
3457 redrawtags $id
3458 }
3458 }
3459
3459
3460 proc redrawtags {id} {
3460 proc redrawtags {id} {
3461 global canv linehtag idline idpos selectedline
3461 global canv linehtag idline idpos selectedline
3462
3462
3463 if {![info exists idline($id)]} return
3463 if {![info exists idline($id)]} return
3464 $canv delete tag.$id
3464 $canv delete tag.$id
3465 set xt [eval drawtags $id $idpos($id)]
3465 set xt [eval drawtags $id $idpos($id)]
3466 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3466 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3467 if {[info exists selectedline] && $selectedline == $idline($id)} {
3467 if {[info exists selectedline] && $selectedline == $idline($id)} {
3468 selectline $selectedline 0
3468 selectline $selectedline 0
3469 }
3469 }
3470 }
3470 }
3471
3471
3472 proc mktagcan {} {
3472 proc mktagcan {} {
3473 global mktagtop
3473 global mktagtop
3474
3474
3475 catch {destroy $mktagtop}
3475 catch {destroy $mktagtop}
3476 unset mktagtop
3476 unset mktagtop
3477 }
3477 }
3478
3478
3479 proc mktaggo {} {
3479 proc mktaggo {} {
3480 domktag
3480 domktag
3481 mktagcan
3481 mktagcan
3482 }
3482 }
3483
3483
3484 proc writecommit {} {
3484 proc writecommit {} {
3485 global rowmenuid wrcomtop commitinfo wrcomcmd
3485 global rowmenuid wrcomtop commitinfo wrcomcmd
3486
3486
3487 set top .writecommit
3487 set top .writecommit
3488 set wrcomtop $top
3488 set wrcomtop $top
3489 catch {destroy $top}
3489 catch {destroy $top}
3490 toplevel $top
3490 toplevel $top
3491 label $top.title -text "Write commit to file"
3491 label $top.title -text "Write commit to file"
3492 grid $top.title - -pady 10
3492 grid $top.title - -pady 10
3493 label $top.id -text "ID:"
3493 label $top.id -text "ID:"
3494 entry $top.sha1 -width 40 -relief flat
3494 entry $top.sha1 -width 40 -relief flat
3495 $top.sha1 insert 0 $rowmenuid
3495 $top.sha1 insert 0 $rowmenuid
3496 $top.sha1 conf -state readonly
3496 $top.sha1 conf -state readonly
3497 grid $top.id $top.sha1 -sticky w
3497 grid $top.id $top.sha1 -sticky w
3498 entry $top.head -width 60 -relief flat
3498 entry $top.head -width 60 -relief flat
3499 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3499 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3500 $top.head conf -state readonly
3500 $top.head conf -state readonly
3501 grid x $top.head -sticky w
3501 grid x $top.head -sticky w
3502 label $top.clab -text "Command:"
3502 label $top.clab -text "Command:"
3503 entry $top.cmd -width 60 -textvariable wrcomcmd
3503 entry $top.cmd -width 60 -textvariable wrcomcmd
3504 grid $top.clab $top.cmd -sticky w -pady 10
3504 grid $top.clab $top.cmd -sticky w -pady 10
3505 label $top.flab -text "Output file:"
3505 label $top.flab -text "Output file:"
3506 entry $top.fname -width 60
3506 entry $top.fname -width 60
3507 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3507 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3508 grid $top.flab $top.fname -sticky w
3508 grid $top.flab $top.fname -sticky w
3509 frame $top.buts
3509 frame $top.buts
3510 button $top.buts.gen -text "Write" -command wrcomgo
3510 button $top.buts.gen -text "Write" -command wrcomgo
3511 button $top.buts.can -text "Cancel" -command wrcomcan
3511 button $top.buts.can -text "Cancel" -command wrcomcan
3512 grid $top.buts.gen $top.buts.can
3512 grid $top.buts.gen $top.buts.can
3513 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3513 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3514 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3514 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3515 grid $top.buts - -pady 10 -sticky ew
3515 grid $top.buts - -pady 10 -sticky ew
3516 focus $top.fname
3516 focus $top.fname
3517 }
3517 }
3518
3518
3519 proc wrcomgo {} {
3519 proc wrcomgo {} {
3520 global wrcomtop
3520 global wrcomtop
3521
3521
3522 set id [$wrcomtop.sha1 get]
3522 set id [$wrcomtop.sha1 get]
3523 set cmd "echo $id | [$wrcomtop.cmd get]"
3523 set cmd "echo $id | [$wrcomtop.cmd get]"
3524 set fname [$wrcomtop.fname get]
3524 set fname [$wrcomtop.fname get]
3525 if {[catch {exec sh -c $cmd >$fname &} err]} {
3525 if {[catch {exec sh -c $cmd >$fname &} err]} {
3526 error_popup "Error writing commit: $err"
3526 error_popup "Error writing commit: $err"
3527 }
3527 }
3528 catch {destroy $wrcomtop}
3528 catch {destroy $wrcomtop}
3529 unset wrcomtop
3529 unset wrcomtop
3530 }
3530 }
3531
3531
3532 proc wrcomcan {} {
3532 proc wrcomcan {} {
3533 global wrcomtop
3533 global wrcomtop
3534
3534
3535 catch {destroy $wrcomtop}
3535 catch {destroy $wrcomtop}
3536 unset wrcomtop
3536 unset wrcomtop
3537 }
3537 }
3538
3538
3539 proc listrefs {id} {
3539 proc listrefs {id} {
3540 global idtags idheads idotherrefs
3540 global idtags idheads idotherrefs
3541
3541
3542 set x {}
3542 set x {}
3543 if {[info exists idtags($id)]} {
3543 if {[info exists idtags($id)]} {
3544 set x $idtags($id)
3544 set x $idtags($id)
3545 }
3545 }
3546 set y {}
3546 set y {}
3547 if {[info exists idheads($id)]} {
3547 if {[info exists idheads($id)]} {
3548 set y $idheads($id)
3548 set y $idheads($id)
3549 }
3549 }
3550 set z {}
3550 set z {}
3551 if {[info exists idotherrefs($id)]} {
3551 if {[info exists idotherrefs($id)]} {
3552 set z $idotherrefs($id)
3552 set z $idotherrefs($id)
3553 }
3553 }
3554 return [list $x $y $z]
3554 return [list $x $y $z]
3555 }
3555 }
3556
3556
3557 proc rereadrefs {} {
3557 proc rereadrefs {} {
3558 global idtags idheads idotherrefs
3558 global idtags idheads idotherrefs
3559 global tagids headids otherrefids
3559 global tagids headids otherrefids
3560
3560
3561 set refids [concat [array names idtags] \
3561 set refids [concat [array names idtags] \
3562 [array names idheads] [array names idotherrefs]]
3562 [array names idheads] [array names idotherrefs]]
3563 foreach id $refids {
3563 foreach id $refids {
3564 if {![info exists ref($id)]} {
3564 if {![info exists ref($id)]} {
3565 set ref($id) [listrefs $id]
3565 set ref($id) [listrefs $id]
3566 }
3566 }
3567 }
3567 }
3568 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3568 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3569 catch {unset $v}
3569 catch {unset $v}
3570 }
3570 }
3571 readrefs
3571 readrefs
3572 set refids [lsort -unique [concat $refids [array names idtags] \
3572 set refids [lsort -unique [concat $refids [array names idtags] \
3573 [array names idheads] [array names idotherrefs]]]
3573 [array names idheads] [array names idotherrefs]]]
3574 foreach id $refids {
3574 foreach id $refids {
3575 set v [listrefs $id]
3575 set v [listrefs $id]
3576 if {![info exists ref($id)] || $ref($id) != $v} {
3576 if {![info exists ref($id)] || $ref($id) != $v} {
3577 redrawtags $id
3577 redrawtags $id
3578 }
3578 }
3579 }
3579 }
3580 }
3580 }
3581
3581
3582 proc showtag {tag isnew} {
3582 proc showtag {tag isnew} {
3583 global ctext cflist tagcontents tagids linknum
3583 global ctext cflist tagcontents tagids linknum
3584
3584
3585 if {$isnew} {
3585 if {$isnew} {
3586 addtohistory [list showtag $tag 0]
3586 addtohistory [list showtag $tag 0]
3587 }
3587 }
3588 $ctext conf -state normal
3588 $ctext conf -state normal
3589 $ctext delete 0.0 end
3589 $ctext delete 0.0 end
3590 set linknum 0
3590 set linknum 0
3591 if {[info exists tagcontents($tag)]} {
3591 if {[info exists tagcontents($tag)]} {
3592 set text $tagcontents($tag)
3592 set text $tagcontents($tag)
3593 } else {
3593 } else {
3594 set text "Tag: $tag\nId: $tagids($tag)"
3594 set text "Tag: $tag\nId: $tagids($tag)"
3595 }
3595 }
3596 appendwithlinks $text
3596 appendwithlinks $text
3597 $ctext conf -state disabled
3597 $ctext conf -state disabled
3598 $cflist delete 0 end
3598 $cflist delete 0 end
3599 }
3599 }
3600
3600
3601 proc doquit {} {
3601 proc doquit {} {
3602 global stopped
3602 global stopped
3603 set stopped 100
3603 set stopped 100
3604 destroy .
3604 destroy .
3605 }
3605 }
3606
3606
3607 # defaults...
3607 # defaults...
3608 set datemode 0
3608 set datemode 0
3609 set boldnames 0
3609 set boldnames 0
3610 set diffopts "-U 5 -p"
3610 set diffopts "-U 5 -p"
3611 set wrcomcmd "hg debug-diff-tree --stdin -p --pretty"
3611 set wrcomcmd "hg debug-diff-tree --stdin -p --pretty"
3612
3612
3613 set mainfont {Helvetica 9}
3613 set mainfont {Helvetica 9}
3614 set textfont {Courier 9}
3614 set textfont {Courier 9}
3615 set findmergefiles 0
3615 set findmergefiles 0
3616 set gaudydiff 0
3616 set gaudydiff 0
3617 set maxgraphpct 50
3617 set maxgraphpct 50
3618 set maxwidth 16
3618 set maxwidth 16
3619
3619
3620 set colors {green red blue magenta darkgrey brown orange}
3620 set colors {green red blue magenta darkgrey brown orange}
3621
3621
3622 catch {source ~/.gitk}
3622 catch {source ~/.gitk}
3623
3623
3624 set namefont $mainfont
3624 set namefont $mainfont
3625 if {$boldnames} {
3625 if {$boldnames} {
3626 lappend namefont bold
3626 lappend namefont bold
3627 }
3627 }
3628
3628
3629 set revtreeargs {}
3629 set revtreeargs {}
3630 foreach arg $argv {
3630 foreach arg $argv {
3631 switch -regexp -- $arg {
3631 switch -regexp -- $arg {
3632 "^$" { }
3632 "^$" { }
3633 "^-b" { set boldnames 1 }
3633 "^-b" { set boldnames 1 }
3634 "^-d" { set datemode 1 }
3634 "^-d" { set datemode 1 }
3635 default {
3635 default {
3636 lappend revtreeargs $arg
3636 lappend revtreeargs $arg
3637 }
3637 }
3638 }
3638 }
3639 }
3639 }
3640
3640
3641 set history {}
3641 set history {}
3642 set historyindex 0
3642 set historyindex 0
3643
3643
3644 set stopped 0
3644 set stopped 0
3645 set redisplaying 0
3645 set redisplaying 0
3646 set stuffsaved 0
3646 set stuffsaved 0
3647 set patchnum 0
3647 set patchnum 0
3648 setcoords
3648 setcoords
3649 makewindow
3649 makewindow
3650 readrefs
3650 readrefs
3651 getcommits $revtreeargs
3651 getcommits $revtreeargs
@@ -1,341 +1,341 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # Minimal support for git commands on an hg repository
3 # Minimal support for git commands on an hg repository
4 #
4 #
5 # Copyright 2005 Chris Mason <mason@suse.com>
5 # Copyright 2005 Chris Mason <mason@suse.com>
6 #
6 #
7 # This software may be used and distributed according to the terms
7 # This software may be used and distributed according to the terms
8 # of the GNU General Public License, incorporated herein by reference.
8 # of the GNU General Public License, incorporated herein by reference.
9
9
10 import time, sys, signal, os
10 import time, sys, signal, os
11 from mercurial import hg, mdiff, fancyopts, commands, ui, util
11 from mercurial import hg, mdiff, fancyopts, commands, ui, util
12
12
13 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
13 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
14 changes=None, text=False):
14 changes=None, text=False):
15 def date(c):
15 def date(c):
16 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
16 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
17
17
18 if not changes:
18 if not changes:
19 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
19 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
20 else:
20 else:
21 (c, a, d, u) = changes
21 (c, a, d, u) = changes
22 if files:
22 if files:
23 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
23 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
24
24
25 if not c and not a and not d:
25 if not c and not a and not d:
26 return
26 return
27
27
28 if node2:
28 if node2:
29 change = repo.changelog.read(node2)
29 change = repo.changelog.read(node2)
30 mmap2 = repo.manifest.read(change[0])
30 mmap2 = repo.manifest.read(change[0])
31 date2 = date(change)
31 date2 = date(change)
32 def read(f):
32 def read(f):
33 return repo.file(f).read(mmap2[f])
33 return repo.file(f).read(mmap2[f])
34 else:
34 else:
35 date2 = time.asctime()
35 date2 = time.asctime()
36 if not node1:
36 if not node1:
37 node1 = repo.dirstate.parents()[0]
37 node1 = repo.dirstate.parents()[0]
38 def read(f):
38 def read(f):
39 return repo.wfile(f).read()
39 return repo.wfile(f).read()
40
40
41 change = repo.changelog.read(node1)
41 change = repo.changelog.read(node1)
42 mmap = repo.manifest.read(change[0])
42 mmap = repo.manifest.read(change[0])
43 date1 = date(change)
43 date1 = date(change)
44
44
45 for f in c:
45 for f in c:
46 to = None
46 to = None
47 if f in mmap:
47 if f in mmap:
48 to = repo.file(f).read(mmap[f])
48 to = repo.file(f).read(mmap[f])
49 tn = read(f)
49 tn = read(f)
50 fp.write("diff --git a/%s b/%s\n" % (f, f))
50 fp.write("diff --git a/%s b/%s\n" % (f, f))
51 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
51 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
52 for f in a:
52 for f in a:
53 to = None
53 to = None
54 tn = read(f)
54 tn = read(f)
55 fp.write("diff --git /dev/null b/%s\n" % (f))
55 fp.write("diff --git /dev/null b/%s\n" % (f))
56 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
56 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
57 for f in d:
57 for f in d:
58 to = repo.file(f).read(mmap[f])
58 to = repo.file(f).read(mmap[f])
59 tn = None
59 tn = None
60 fp.write("diff --git a/%s /dev/null\n" % (f))
60 fp.write("diff --git a/%s /dev/null\n" % (f))
61 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
61 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
62
62
63 def difftree(ui, repo, node1=None, node2=None, **opts):
63 def difftree(ui, repo, node1=None, node2=None, **opts):
64 """diff trees from two commits"""
64 """diff trees from two commits"""
65 def __difftree(repo, node1, node2):
65 def __difftree(repo, node1, node2):
66 def date(c):
66 def date(c):
67 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
67 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
68
68
69 if node2:
69 if node2:
70 change = repo.changelog.read(node2)
70 change = repo.changelog.read(node2)
71 mmap2 = repo.manifest.read(change[0])
71 mmap2 = repo.manifest.read(change[0])
72 (c, a, d, u) = repo.changes(node1, node2)
72 (c, a, d, u) = repo.changes(node1, node2)
73 def read(f): return repo.file(f).read(mmap2[f])
73 def read(f): return repo.file(f).read(mmap2[f])
74 date2 = date(change)
74 date2 = date(change)
75 else:
75 else:
76 date2 = time.asctime()
76 date2 = time.asctime()
77 (c, a, d, u) = repo.changes(node1, None)
77 (c, a, d, u) = repo.changes(node1, None)
78 if not node1:
78 if not node1:
79 node1 = repo.dirstate.parents()[0]
79 node1 = repo.dirstate.parents()[0]
80 def read(f): return file(os.path.join(repo.root, f)).read()
80 def read(f): return file(os.path.join(repo.root, f)).read()
81
81
82 change = repo.changelog.read(node1)
82 change = repo.changelog.read(node1)
83 mmap = repo.manifest.read(change[0])
83 mmap = repo.manifest.read(change[0])
84 date1 = date(change)
84 date1 = date(change)
85 empty = "0" * 40;
85 empty = "0" * 40;
86
86
87 for f in c:
87 for f in c:
88 # TODO get file permissions
88 # TODO get file permissions
89 print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
89 print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
90 hg.hex(mmap2[f]), f, f)
90 hg.hex(mmap2[f]), f, f)
91 for f in a:
91 for f in a:
92 print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
92 print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
93 for f in d:
93 for f in d:
94 print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
94 print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
95 ##
95 ##
96
96
97 while True:
97 while True:
98 if opts['stdin']:
98 if opts['stdin']:
99 try:
99 try:
100 line = raw_input().split(' ')
100 line = raw_input().split(' ')
101 node1 = line[0]
101 node1 = line[0]
102 if len(line) > 1:
102 if len(line) > 1:
103 node2 = line[1]
103 node2 = line[1]
104 else:
104 else:
105 node2 = None
105 node2 = None
106 except EOFError:
106 except EOFError:
107 break
107 break
108 node1 = repo.lookup(node1)
108 node1 = repo.lookup(node1)
109 if node2:
109 if node2:
110 node2 = repo.lookup(node2)
110 node2 = repo.lookup(node2)
111 else:
111 else:
112 node2 = node1
112 node2 = node1
113 node1 = repo.changelog.parents(node1)[0]
113 node1 = repo.changelog.parents(node1)[0]
114 if opts['patch']:
114 if opts['patch']:
115 if opts['pretty']:
115 if opts['pretty']:
116 catcommit(repo, node2, "")
116 catcommit(repo, node2, "")
117 dodiff(sys.stdout, ui, repo, node1, node2)
117 dodiff(sys.stdout, ui, repo, node1, node2)
118 else:
118 else:
119 __difftree(repo, node1, node2)
119 __difftree(repo, node1, node2)
120 if not opts['stdin']:
120 if not opts['stdin']:
121 break
121 break
122
122
123 def catcommit(repo, n, prefix, changes=None):
123 def catcommit(repo, n, prefix, changes=None):
124 nlprefix = '\n' + prefix;
124 nlprefix = '\n' + prefix;
125 (p1, p2) = repo.changelog.parents(n)
125 (p1, p2) = repo.changelog.parents(n)
126 (h, h1, h2) = map(hg.hex, (n, p1, p2))
126 (h, h1, h2) = map(hg.hex, (n, p1, p2))
127 (i1, i2) = map(repo.changelog.rev, (p1, p2))
127 (i1, i2) = map(repo.changelog.rev, (p1, p2))
128 if not changes:
128 if not changes:
129 changes = repo.changelog.read(n)
129 changes = repo.changelog.read(n)
130 print "tree %s" % (hg.hex(changes[0]))
130 print "tree %s" % (hg.hex(changes[0]))
131 if i1 != -1: print "parent %s" % (h1)
131 if i1 != -1: print "parent %s" % (h1)
132 if i2 != -1: print "parent %s" % (h2)
132 if i2 != -1: print "parent %s" % (h2)
133 date_ar = changes[2].split(' ')
133 date_ar = changes[2].split(' ')
134 date = int(float(date_ar[0]))
134 date = int(float(date_ar[0]))
135 lines = changes[4].splitlines()
135 lines = changes[4].splitlines()
136 if lines[-1].startswith('committer:'):
136 if lines[-1].startswith('committer:'):
137 committer = lines[-1].split(': ')[1].rstrip()
137 committer = lines[-1].split(': ')[1].rstrip()
138 else:
138 else:
139 committer = "%s %s %s" % (changes[1], date, date_ar[1])
139 committer = "%s %s %s" % (changes[1], date, date_ar[1])
140
140
141 print "author %s %s %s" % (changes[1], date, date_ar[1])
141 print "author %s %s %s" % (changes[1], date, date_ar[1])
142 print "committer %s" % (committer)
142 print "committer %s" % (committer)
143 print ""
143 print ""
144 if prefix != "":
144 if prefix != "":
145 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
145 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
146 else:
146 else:
147 print changes[4]
147 print changes[4]
148 if prefix:
148 if prefix:
149 sys.stdout.write('\0')
149 sys.stdout.write('\0')
150
150
151 def base(ui, repo, node1, node2):
151 def base(ui, repo, node1, node2):
152 """Output common ancestor information"""
152 """Output common ancestor information"""
153 node1 = repo.lookup(node1)
153 node1 = repo.lookup(node1)
154 node2 = repo.lookup(node2)
154 node2 = repo.lookup(node2)
155 n = repo.changelog.ancestor(node1, node2)
155 n = repo.changelog.ancestor(node1, node2)
156 print hg.hex(n)
156 print hg.hex(n)
157
157
158 def catfile(ui, repo, type=None, r=None, **opts):
158 def catfile(ui, repo, type=None, r=None, **opts):
159 """cat a specific revision"""
159 """cat a specific revision"""
160 # in stdin mode, every line except the commit is prefixed with two
160 # in stdin mode, every line except the commit is prefixed with two
161 # spaces. This way the our caller can find the commit without magic
161 # spaces. This way the our caller can find the commit without magic
162 # strings
162 # strings
163 #
163 #
164 prefix = ""
164 prefix = ""
165 if opts['stdin']:
165 if opts['stdin']:
166 try:
166 try:
167 (type, r) = raw_input().split(' ');
167 (type, r) = raw_input().split(' ');
168 prefix = " "
168 prefix = " "
169 except EOFError:
169 except EOFError:
170 return
170 return
171
171
172 else:
172 else:
173 if not type or not r:
173 if not type or not r:
174 ui.warn("cat-file: type or revision not supplied\n")
174 ui.warn("cat-file: type or revision not supplied\n")
175 commands.help_(ui, 'cat-file')
175 commands.help_(ui, 'cat-file')
176
176
177 while r:
177 while r:
178 if type != "commit":
178 if type != "commit":
179 sys.stderr.write("aborting hg cat-file only understands commits\n")
179 sys.stderr.write("aborting hg cat-file only understands commits\n")
180 sys.exit(1);
180 sys.exit(1);
181 n = repo.lookup(r)
181 n = repo.lookup(r)
182 catcommit(repo, n, prefix)
182 catcommit(repo, n, prefix)
183 if opts['stdin']:
183 if opts['stdin']:
184 try:
184 try:
185 (type, r) = raw_input().split(' ');
185 (type, r) = raw_input().split(' ');
186 except EOFError:
186 except EOFError:
187 break
187 break
188 else:
188 else:
189 break
189 break
190
190
191 # git rev-tree is a confusing thing. You can supply a number of
191 # git rev-tree is a confusing thing. You can supply a number of
192 # commit sha1s on the command line, and it walks the commit history
192 # commit sha1s on the command line, and it walks the commit history
193 # telling you which commits are reachable from the supplied ones via
193 # telling you which commits are reachable from the supplied ones via
194 # a bitmask based on arg position.
194 # a bitmask based on arg position.
195 # you can specify a commit to stop at by starting the sha1 with ^
195 # you can specify a commit to stop at by starting the sha1 with ^
196 def revtree(args, repo, full="tree", maxnr=0, parents=False):
196 def revtree(args, repo, full="tree", maxnr=0, parents=False):
197 def chlogwalk():
197 def chlogwalk():
198 ch = repo.changelog
198 ch = repo.changelog
199 count = ch.count()
199 count = ch.count()
200 i = count
200 i = count
201 l = [0] * 100
201 l = [0] * 100
202 chunk = 100
202 chunk = 100
203 while True:
203 while True:
204 if chunk > i:
204 if chunk > i:
205 chunk = i
205 chunk = i
206 i = 0
206 i = 0
207 else:
207 else:
208 i -= chunk
208 i -= chunk
209
209
210 for x in xrange(0, chunk):
210 for x in xrange(0, chunk):
211 if i + x >= count:
211 if i + x >= count:
212 l[chunk - x:] = [0] * (chunk - x)
212 l[chunk - x:] = [0] * (chunk - x)
213 break
213 break
214 if full != None:
214 if full != None:
215 l[x] = ch.read(ch.node(i + x))
215 l[x] = ch.read(ch.node(i + x))
216 else:
216 else:
217 l[x] = 1
217 l[x] = 1
218 for x in xrange(chunk-1, -1, -1):
218 for x in xrange(chunk-1, -1, -1):
219 if l[x] != 0:
219 if l[x] != 0:
220 yield (i + x, full != None and l[x] or None)
220 yield (i + x, full != None and l[x] or None)
221 if i == 0:
221 if i == 0:
222 break
222 break
223
223
224 # calculate and return the reachability bitmask for sha
224 # calculate and return the reachability bitmask for sha
225 def is_reachable(ar, reachable, sha):
225 def is_reachable(ar, reachable, sha):
226 if len(ar) == 0:
226 if len(ar) == 0:
227 return 1
227 return 1
228 mask = 0
228 mask = 0
229 for i in range(len(ar)):
229 for i in range(len(ar)):
230 if sha in reachable[i]:
230 if sha in reachable[i]:
231 mask |= 1 << i
231 mask |= 1 << i
232
232
233 return mask
233 return mask
234
234
235 reachable = []
235 reachable = []
236 stop_sha1 = []
236 stop_sha1 = []
237 want_sha1 = []
237 want_sha1 = []
238 count = 0
238 count = 0
239
239
240 # figure out which commits they are asking for and which ones they
240 # figure out which commits they are asking for and which ones they
241 # want us to stop on
241 # want us to stop on
242 for i in range(len(args)):
242 for i in range(len(args)):
243 if args[i].startswith('^'):
243 if args[i].startswith('^'):
244 s = repo.lookup(args[i][1:])
244 s = repo.lookup(args[i][1:])
245 stop_sha1.append(s)
245 stop_sha1.append(s)
246 want_sha1.append(s)
246 want_sha1.append(s)
247 elif args[i] != 'HEAD':
247 elif args[i] != 'HEAD':
248 want_sha1.append(repo.lookup(args[i]))
248 want_sha1.append(repo.lookup(args[i]))
249
249
250 # calculate the graph for the supplied commits
250 # calculate the graph for the supplied commits
251 for i in range(len(want_sha1)):
251 for i in range(len(want_sha1)):
252 reachable.append({});
252 reachable.append({});
253 n = want_sha1[i];
253 n = want_sha1[i];
254 visit = [n];
254 visit = [n];
255 reachable[i][n] = 1
255 reachable[i][n] = 1
256 while visit:
256 while visit:
257 n = visit.pop(0)
257 n = visit.pop(0)
258 if n in stop_sha1:
258 if n in stop_sha1:
259 continue
259 continue
260 for p in repo.changelog.parents(n):
260 for p in repo.changelog.parents(n):
261 if p not in reachable[i]:
261 if p not in reachable[i]:
262 reachable[i][p] = 1
262 reachable[i][p] = 1
263 visit.append(p)
263 visit.append(p)
264 if p in stop_sha1:
264 if p in stop_sha1:
265 continue
265 continue
266
266
267 # walk the repository looking for commits that are in our
267 # walk the repository looking for commits that are in our
268 # reachability graph
268 # reachability graph
269 #for i in range(repo.changelog.count()-1, -1, -1):
269 #for i in range(repo.changelog.count()-1, -1, -1):
270 for i, changes in chlogwalk():
270 for i, changes in chlogwalk():
271 n = repo.changelog.node(i)
271 n = repo.changelog.node(i)
272 mask = is_reachable(want_sha1, reachable, n)
272 mask = is_reachable(want_sha1, reachable, n)
273 if mask:
273 if mask:
274 parentstr = ""
274 parentstr = ""
275 if parents:
275 if parents:
276 pp = repo.changelog.parents(n)
276 pp = repo.changelog.parents(n)
277 if pp[0] != hg.nullid:
277 if pp[0] != hg.nullid:
278 parentstr += " " + hg.hex(pp[0])
278 parentstr += " " + hg.hex(pp[0])
279 if pp[1] != hg.nullid:
279 if pp[1] != hg.nullid:
280 parentstr += " " + hg.hex(pp[1])
280 parentstr += " " + hg.hex(pp[1])
281 if not full:
281 if not full:
282 print hg.hex(n) + parentstr
282 print hg.hex(n) + parentstr
283 elif full is "commit":
283 elif full is "commit":
284 print hg.hex(n) + parentstr
284 print hg.hex(n) + parentstr
285 catcommit(repo, n, ' ', changes)
285 catcommit(repo, n, ' ', changes)
286 else:
286 else:
287 (p1, p2) = repo.changelog.parents(n)
287 (p1, p2) = repo.changelog.parents(n)
288 (h, h1, h2) = map(hg.hex, (n, p1, p2))
288 (h, h1, h2) = map(hg.hex, (n, p1, p2))
289 (i1, i2) = map(repo.changelog.rev, (p1, p2))
289 (i1, i2) = map(repo.changelog.rev, (p1, p2))
290
290
291 date = changes[2].split(' ')[0]
291 date = changes[2].split(' ')[0]
292 print "%s %s:%s" % (date, h, mask),
292 print "%s %s:%s" % (date, h, mask),
293 mask = is_reachable(want_sha1, reachable, p1)
293 mask = is_reachable(want_sha1, reachable, p1)
294 if i1 != -1 and mask > 0:
294 if i1 != -1 and mask > 0:
295 print "%s:%s " % (h1, mask),
295 print "%s:%s " % (h1, mask),
296 mask = is_reachable(want_sha1, reachable, p2)
296 mask = is_reachable(want_sha1, reachable, p2)
297 if i2 != -1 and mask > 0:
297 if i2 != -1 and mask > 0:
298 print "%s:%s " % (h2, mask),
298 print "%s:%s " % (h2, mask),
299 print ""
299 print ""
300 if maxnr and count >= maxnr:
300 if maxnr and count >= maxnr:
301 break
301 break
302 count += 1
302 count += 1
303
303
304 # git rev-list tries to order things by date, and has the ability to stop
304 # git rev-list tries to order things by date, and has the ability to stop
305 # at a given commit without walking the whole repo. TODO add the stop
305 # at a given commit without walking the whole repo. TODO add the stop
306 # parameter
306 # parameter
307 def revlist(ui, repo, *revs, **opts):
307 def revlist(ui, repo, *revs, **opts):
308 """print revisions"""
308 """print revisions"""
309 if opts['header']:
309 if opts['header']:
310 full = "commit"
310 full = "commit"
311 else:
311 else:
312 full = None
312 full = None
313 copy = [x for x in revs]
313 copy = [x for x in revs]
314 revtree(copy, repo, full, opts['max_count'], opts['parents'])
314 revtree(copy, repo, full, opts['max_count'], opts['parents'])
315
315
316 def view(ui, repo, *etc):
316 def view(ui, repo, *etc):
317 "start interactive history viewer"
317 "start interactive history viewer"
318 os.chdir(repo.root)
318 os.chdir(repo.root)
319 os.system("hgk " + " ".join(etc))
319 os.system("hgk " + " ".join(etc))
320
320
321 cmdtable = {
321 cmdtable = {
322 "view": (view, [], 'hg view'),
322 "view": (view, [], 'hg view'),
323 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
323 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
324 ('r', 'recursive', None, 'recursive'),
324 ('r', 'recursive', None, 'recursive'),
325 ('P', 'pretty', None, 'pretty'),
325 ('P', 'pretty', None, 'pretty'),
326 ('s', 'stdin', None, 'stdin'),
326 ('s', 'stdin', None, 'stdin'),
327 ('C', 'copy', None, 'detect copies'),
327 ('C', 'copy', None, 'detect copies'),
328 ('S', 'search', "", 'search')],
328 ('S', 'search', "", 'search')],
329 "hg git-diff-tree [options] node1 node2"),
329 "hg git-diff-tree [options] node1 node2"),
330 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
330 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
331 "hg debug-cat-file [options] type file"),
331 "hg debug-cat-file [options] type file"),
332 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
332 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
333 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
333 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
334 ('t', 'topo-order', None, 'topo-order'),
334 ('t', 'topo-order', None, 'topo-order'),
335 ('p', 'parents', None, 'parents'),
335 ('p', 'parents', None, 'parents'),
336 ('n', 'max-count', 0, 'max-count')],
336 ('n', 'max-count', 0, 'max-count')],
337 "hg debug-rev-list [options] revs"),
337 "hg debug-rev-list [options] revs"),
338 }
338 }
339
339
340 def reposetup(ui, repo):
340 def reposetup(ui, repo):
341 pass
341 pass
@@ -1,1103 +1,1103 b''
1 ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM
1 ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM
2
2
3 ;; Copyright (C) 2005 Bryan O'Sullivan
3 ;; Copyright (C) 2005 Bryan O'Sullivan
4
4
5 ;; Author: Bryan O'Sullivan <bos@serpentine.com>
5 ;; Author: Bryan O'Sullivan <bos@serpentine.com>
6
6
7 ;; mercurial.el is free software; you can redistribute it and/or
7 ;; mercurial.el is free software; you can redistribute it and/or
8 ;; modify it under the terms of version 2 of the GNU General Public
8 ;; modify it under the terms of version 2 of the GNU General Public
9 ;; License as published by the Free Software Foundation.
9 ;; License as published by the Free Software Foundation.
10
10
11 ;; mercurial.el is distributed in the hope that it will be useful, but
11 ;; mercurial.el is distributed in the hope that it will be useful, but
12 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 ;; General Public License for more details.
14 ;; General Public License for more details.
15
15
16 ;; You should have received a copy of the GNU General Public License
16 ;; You should have received a copy of the GNU General Public License
17 ;; along with mercurial.el, GNU Emacs, or XEmacs; see the file COPYING
17 ;; along with mercurial.el, GNU Emacs, or XEmacs; see the file COPYING
18 ;; (`C-h C-l'). If not, write to the Free Software Foundation, Inc.,
18 ;; (`C-h C-l'). If not, write to the Free Software Foundation, Inc.,
19 ;; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 ;; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
20
21 ;;; Commentary:
21 ;;; Commentary:
22
22
23 ;; mercurial.el builds upon Emacs's VC mode to provide flexible
23 ;; mercurial.el builds upon Emacs's VC mode to provide flexible
24 ;; integration with the Mercurial distributed SCM tool.
24 ;; integration with the Mercurial distributed SCM tool.
25
25
26 ;; To get going as quickly as possible, load mercurial.el into Emacs and
26 ;; To get going as quickly as possible, load mercurial.el into Emacs and
27 ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful
27 ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful
28 ;; usage overview.
28 ;; usage overview.
29
29
30 ;; Much of the inspiration for mercurial.el comes from Rajesh
30 ;; Much of the inspiration for mercurial.el comes from Rajesh
31 ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough
31 ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough
32 ;; job for the commercial Perforce SCM product. In fact, substantial
32 ;; job for the commercial Perforce SCM product. In fact, substantial
33 ;; chunks of code are adapted from p4.el.
33 ;; chunks of code are adapted from p4.el.
34
34
35 ;; This code has been developed under XEmacs 21.5, and may not work as
35 ;; This code has been developed under XEmacs 21.5, and may not work as
36 ;; well under GNU Emacs (albeit tested under 21.4). Patches to
36 ;; well under GNU Emacs (albeit tested under 21.4). Patches to
37 ;; enhance the portability of this code, fix bugs, and add features
37 ;; enhance the portability of this code, fix bugs, and add features
38 ;; are most welcome. You can clone a Mercurial repository for this
38 ;; are most welcome. You can clone a Mercurial repository for this
39 ;; package from http://www.serpentine.com/hg/hg-emacs
39 ;; package from http://www.serpentine.com/hg/hg-emacs
40
40
41 ;; Please send problem reports and suggestions to bos@serpentine.com.
41 ;; Please send problem reports and suggestions to bos@serpentine.com.
42
42
43
43
44 ;;; Code:
44 ;;; Code:
45
45
46 (require 'advice)
46 (require 'advice)
47 (require 'cl)
47 (require 'cl)
48 (require 'diff-mode)
48 (require 'diff-mode)
49 (require 'easymenu)
49 (require 'easymenu)
50 (require 'executable)
50 (require 'executable)
51 (require 'vc)
51 (require 'vc)
52
52
53
53
54 ;;; XEmacs has view-less, while GNU Emacs has view. Joy.
54 ;;; XEmacs has view-less, while GNU Emacs has view. Joy.
55
55
56 (condition-case nil
56 (condition-case nil
57 (require 'view-less)
57 (require 'view-less)
58 (error nil))
58 (error nil))
59 (condition-case nil
59 (condition-case nil
60 (require 'view)
60 (require 'view)
61 (error nil))
61 (error nil))
62
62
63
63
64 ;;; Variables accessible through the custom system.
64 ;;; Variables accessible through the custom system.
65
65
66 (defgroup mercurial nil
66 (defgroup mercurial nil
67 "Mercurial distributed SCM."
67 "Mercurial distributed SCM."
68 :group 'tools)
68 :group 'tools)
69
69
70 (defcustom hg-binary
70 (defcustom hg-binary
71 (or (executable-find "hg")
71 (or (executable-find "hg")
72 (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg"))
72 (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg"))
73 (when (file-executable-p path)
73 (when (file-executable-p path)
74 (return path))))
74 (return path))))
75 "The path to Mercurial's hg executable."
75 "The path to Mercurial's hg executable."
76 :type '(file :must-match t)
76 :type '(file :must-match t)
77 :group 'mercurial)
77 :group 'mercurial)
78
78
79 (defcustom hg-mode-hook nil
79 (defcustom hg-mode-hook nil
80 "Hook run when a buffer enters hg-mode."
80 "Hook run when a buffer enters hg-mode."
81 :type 'sexp
81 :type 'sexp
82 :group 'mercurial)
82 :group 'mercurial)
83
83
84 (defcustom hg-commit-mode-hook nil
84 (defcustom hg-commit-mode-hook nil
85 "Hook run when a buffer is created to prepare a commit."
85 "Hook run when a buffer is created to prepare a commit."
86 :type 'sexp
86 :type 'sexp
87 :group 'mercurial)
87 :group 'mercurial)
88
88
89 (defcustom hg-pre-commit-hook nil
89 (defcustom hg-pre-commit-hook nil
90 "Hook run before a commit is performed.
90 "Hook run before a commit is performed.
91 If you want to prevent the commit from proceeding, raise an error."
91 If you want to prevent the commit from proceeding, raise an error."
92 :type 'sexp
92 :type 'sexp
93 :group 'mercurial)
93 :group 'mercurial)
94
94
95 (defcustom hg-log-mode-hook nil
95 (defcustom hg-log-mode-hook nil
96 "Hook run after a buffer is filled with log information."
96 "Hook run after a buffer is filled with log information."
97 :type 'sexp
97 :type 'sexp
98 :group 'mercurial)
98 :group 'mercurial)
99
99
100 (defcustom hg-global-prefix "\C-ch"
100 (defcustom hg-global-prefix "\C-ch"
101 "The global prefix for Mercurial keymap bindings."
101 "The global prefix for Mercurial keymap bindings."
102 :type 'sexp
102 :type 'sexp
103 :group 'mercurial)
103 :group 'mercurial)
104
104
105 (defcustom hg-commit-allow-empty-message nil
105 (defcustom hg-commit-allow-empty-message nil
106 "Whether to allow changes to be committed with empty descriptions."
106 "Whether to allow changes to be committed with empty descriptions."
107 :type 'boolean
107 :type 'boolean
108 :group 'mercurial)
108 :group 'mercurial)
109
109
110 (defcustom hg-commit-allow-empty-file-list nil
110 (defcustom hg-commit-allow-empty-file-list nil
111 "Whether to allow changes to be committed without any modified files."
111 "Whether to allow changes to be committed without any modified files."
112 :type 'boolean
112 :type 'boolean
113 :group 'mercurial)
113 :group 'mercurial)
114
114
115 (defcustom hg-rev-completion-limit 100
115 (defcustom hg-rev-completion-limit 100
116 "The maximum number of revisions that hg-read-rev will offer to complete.
116 "The maximum number of revisions that hg-read-rev will offer to complete.
117 This affects memory usage and performance when prompting for revisions
117 This affects memory usage and performance when prompting for revisions
118 in a repository with a lot of history."
118 in a repository with a lot of history."
119 :type 'integer
119 :type 'integer
120 :group 'mercurial)
120 :group 'mercurial)
121
121
122 (defcustom hg-log-limit 50
122 (defcustom hg-log-limit 50
123 "The maximum number of revisions that hg-log will display."
123 "The maximum number of revisions that hg-log will display."
124 :type 'integer
124 :type 'integer
125 :group 'mercurial)
125 :group 'mercurial)
126
126
127 (defcustom hg-update-modeline t
127 (defcustom hg-update-modeline t
128 "Whether to update the modeline with the status of a file after every save.
128 "Whether to update the modeline with the status of a file after every save.
129 Set this to nil on platforms with poor process management, such as Windows."
129 Set this to nil on platforms with poor process management, such as Windows."
130 :type 'boolean
130 :type 'boolean
131 :group 'mercurial)
131 :group 'mercurial)
132
132
133 (defcustom hg-incoming-repository "default"
133 (defcustom hg-incoming-repository "default"
134 "The repository from which changes are pulled from by default.
134 "The repository from which changes are pulled from by default.
135 This should be a symbolic repository name, since it is used for all
135 This should be a symbolic repository name, since it is used for all
136 repository-related commands."
136 repository-related commands."
137 :type 'string
137 :type 'string
138 :group 'mercurial)
138 :group 'mercurial)
139
139
140 (defcustom hg-outgoing-repository "default-push"
140 (defcustom hg-outgoing-repository "default-push"
141 "The repository to which changes are pushed to by default.
141 "The repository to which changes are pushed to by default.
142 This should be a symbolic repository name, since it is used for all
142 This should be a symbolic repository name, since it is used for all
143 repository-related commands."
143 repository-related commands."
144 :type 'string
144 :type 'string
145 :group 'mercurial)
145 :group 'mercurial)
146
146
147
147
148 ;;; Other variables.
148 ;;; Other variables.
149
149
150 (defconst hg-running-xemacs (string-match "XEmacs" emacs-version)
150 (defconst hg-running-xemacs (string-match "XEmacs" emacs-version)
151 "Is mercurial.el running under XEmacs?")
151 "Is mercurial.el running under XEmacs?")
152
152
153 (defvar hg-mode nil
153 (defvar hg-mode nil
154 "Is this file managed by Mercurial?")
154 "Is this file managed by Mercurial?")
155 (make-variable-buffer-local 'hg-mode)
155 (make-variable-buffer-local 'hg-mode)
156 (put 'hg-mode 'permanent-local t)
156 (put 'hg-mode 'permanent-local t)
157
157
158 (defvar hg-status nil)
158 (defvar hg-status nil)
159 (make-variable-buffer-local 'hg-status)
159 (make-variable-buffer-local 'hg-status)
160 (put 'hg-status 'permanent-local t)
160 (put 'hg-status 'permanent-local t)
161
161
162 (defvar hg-prev-buffer nil)
162 (defvar hg-prev-buffer nil)
163 (make-variable-buffer-local 'hg-prev-buffer)
163 (make-variable-buffer-local 'hg-prev-buffer)
164 (put 'hg-prev-buffer 'permanent-local t)
164 (put 'hg-prev-buffer 'permanent-local t)
165
165
166 (defvar hg-root nil)
166 (defvar hg-root nil)
167 (make-variable-buffer-local 'hg-root)
167 (make-variable-buffer-local 'hg-root)
168 (put 'hg-root 'permanent-local t)
168 (put 'hg-root 'permanent-local t)
169
169
170 (defvar hg-output-buffer-name "*Hg*"
170 (defvar hg-output-buffer-name "*Hg*"
171 "The name to use for Mercurial output buffers.")
171 "The name to use for Mercurial output buffers.")
172
172
173 (defvar hg-file-history nil)
173 (defvar hg-file-history nil)
174 (defvar hg-repo-history nil)
174 (defvar hg-repo-history nil)
175 (defvar hg-rev-history nil)
175 (defvar hg-rev-history nil)
176
176
177
177
178 ;;; Random constants.
178 ;;; Random constants.
179
179
180 (defconst hg-commit-message-start
180 (defconst hg-commit-message-start
181 "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
181 "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
182
182
183 (defconst hg-commit-message-end
183 (defconst hg-commit-message-end
184 "--- Files in bold will be committed. Click to toggle selection. ---\n")
184 "--- Files in bold will be committed. Click to toggle selection. ---\n")
185
185
186
186
187 ;;; hg-mode keymap.
187 ;;; hg-mode keymap.
188
188
189 (defvar hg-mode-map (make-sparse-keymap))
189 (defvar hg-mode-map (make-sparse-keymap))
190 (define-key hg-mode-map "\C-xv" 'hg-prefix-map)
190 (define-key hg-mode-map "\C-xv" 'hg-prefix-map)
191
191
192 (defvar hg-prefix-map
192 (defvar hg-prefix-map
193 (let ((map (copy-keymap vc-prefix-map)))
193 (let ((map (copy-keymap vc-prefix-map)))
194 (if (functionp 'set-keymap-name)
194 (if (functionp 'set-keymap-name)
195 (set-keymap-name map 'hg-prefix-map)); XEmacs
195 (set-keymap-name map 'hg-prefix-map)); XEmacs
196 map)
196 map)
197 "This keymap overrides some default vc-mode bindings.")
197 "This keymap overrides some default vc-mode bindings.")
198 (fset 'hg-prefix-map hg-prefix-map)
198 (fset 'hg-prefix-map hg-prefix-map)
199 (define-key hg-prefix-map "=" 'hg-diff)
199 (define-key hg-prefix-map "=" 'hg-diff)
200 (define-key hg-prefix-map "c" 'hg-undo)
200 (define-key hg-prefix-map "c" 'hg-undo)
201 (define-key hg-prefix-map "g" 'hg-annotate)
201 (define-key hg-prefix-map "g" 'hg-annotate)
202 (define-key hg-prefix-map "l" 'hg-log)
202 (define-key hg-prefix-map "l" 'hg-log)
203 (define-key hg-prefix-map "n" 'hg-commit-start)
203 (define-key hg-prefix-map "n" 'hg-commit-start)
204 ;; (define-key hg-prefix-map "r" 'hg-update)
204 ;; (define-key hg-prefix-map "r" 'hg-update)
205 (define-key hg-prefix-map "u" 'hg-revert-buffer)
205 (define-key hg-prefix-map "u" 'hg-revert-buffer)
206 (define-key hg-prefix-map "~" 'hg-version-other-window)
206 (define-key hg-prefix-map "~" 'hg-version-other-window)
207
207
208 (add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
208 (add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
209
209
210
210
211 ;;; Global keymap.
211 ;;; Global keymap.
212
212
213 (global-set-key "\C-xvi" 'hg-add)
213 (global-set-key "\C-xvi" 'hg-add)
214
214
215 (defvar hg-global-map (make-sparse-keymap))
215 (defvar hg-global-map (make-sparse-keymap))
216 (fset 'hg-global-map hg-global-map)
216 (fset 'hg-global-map hg-global-map)
217 (global-set-key hg-global-prefix 'hg-global-map)
217 (global-set-key hg-global-prefix 'hg-global-map)
218 (define-key hg-global-map "," 'hg-incoming)
218 (define-key hg-global-map "," 'hg-incoming)
219 (define-key hg-global-map "." 'hg-outgoing)
219 (define-key hg-global-map "." 'hg-outgoing)
220 (define-key hg-global-map "<" 'hg-pull)
220 (define-key hg-global-map "<" 'hg-pull)
221 (define-key hg-global-map "=" 'hg-diff-repo)
221 (define-key hg-global-map "=" 'hg-diff-repo)
222 (define-key hg-global-map ">" 'hg-push)
222 (define-key hg-global-map ">" 'hg-push)
223 (define-key hg-global-map "?" 'hg-help-overview)
223 (define-key hg-global-map "?" 'hg-help-overview)
224 (define-key hg-global-map "A" 'hg-addremove)
224 (define-key hg-global-map "A" 'hg-addremove)
225 (define-key hg-global-map "U" 'hg-revert)
225 (define-key hg-global-map "U" 'hg-revert)
226 (define-key hg-global-map "a" 'hg-add)
226 (define-key hg-global-map "a" 'hg-add)
227 (define-key hg-global-map "c" 'hg-commit-start)
227 (define-key hg-global-map "c" 'hg-commit-start)
228 (define-key hg-global-map "f" 'hg-forget)
228 (define-key hg-global-map "f" 'hg-forget)
229 (define-key hg-global-map "h" 'hg-help-overview)
229 (define-key hg-global-map "h" 'hg-help-overview)
230 (define-key hg-global-map "i" 'hg-init)
230 (define-key hg-global-map "i" 'hg-init)
231 (define-key hg-global-map "l" 'hg-log-repo)
231 (define-key hg-global-map "l" 'hg-log-repo)
232 (define-key hg-global-map "r" 'hg-root)
232 (define-key hg-global-map "r" 'hg-root)
233 (define-key hg-global-map "s" 'hg-status)
233 (define-key hg-global-map "s" 'hg-status)
234 (define-key hg-global-map "u" 'hg-update)
234 (define-key hg-global-map "u" 'hg-update)
235
235
236
236
237 ;;; View mode keymap.
237 ;;; View mode keymap.
238
238
239 (defvar hg-view-mode-map
239 (defvar hg-view-mode-map
240 (let ((map (copy-keymap (if (boundp 'view-minor-mode-map)
240 (let ((map (copy-keymap (if (boundp 'view-minor-mode-map)
241 view-minor-mode-map
241 view-minor-mode-map
242 view-mode-map))))
242 view-mode-map))))
243 (if (functionp 'set-keymap-name)
243 (if (functionp 'set-keymap-name)
244 (set-keymap-name map 'hg-view-mode-map)); XEmacs
244 (set-keymap-name map 'hg-view-mode-map)); XEmacs
245 map))
245 map))
246 (fset 'hg-view-mode-map hg-view-mode-map)
246 (fset 'hg-view-mode-map hg-view-mode-map)
247 (define-key hg-view-mode-map
247 (define-key hg-view-mode-map
248 (if hg-running-xemacs [button2] [mouse-2])
248 (if hg-running-xemacs [button2] [mouse-2])
249 'hg-buffer-mouse-clicked)
249 'hg-buffer-mouse-clicked)
250
250
251
251
252 ;;; Commit mode keymaps.
252 ;;; Commit mode keymaps.
253
253
254 (defvar hg-commit-mode-map (make-sparse-keymap))
254 (defvar hg-commit-mode-map (make-sparse-keymap))
255 (define-key hg-commit-mode-map "\C-c\C-c" 'hg-commit-finish)
255 (define-key hg-commit-mode-map "\C-c\C-c" 'hg-commit-finish)
256 (define-key hg-commit-mode-map "\C-c\C-k" 'hg-commit-kill)
256 (define-key hg-commit-mode-map "\C-c\C-k" 'hg-commit-kill)
257 (define-key hg-commit-mode-map "\C-xv=" 'hg-diff-repo)
257 (define-key hg-commit-mode-map "\C-xv=" 'hg-diff-repo)
258
258
259 (defvar hg-commit-mode-file-map (make-sparse-keymap))
259 (defvar hg-commit-mode-file-map (make-sparse-keymap))
260 (define-key hg-commit-mode-file-map
260 (define-key hg-commit-mode-file-map
261 (if hg-running-xemacs [button2] [mouse-2])
261 (if hg-running-xemacs [button2] [mouse-2])
262 'hg-commit-mouse-clicked)
262 'hg-commit-mouse-clicked)
263 (define-key hg-commit-mode-file-map " " 'hg-commit-toggle-file)
263 (define-key hg-commit-mode-file-map " " 'hg-commit-toggle-file)
264 (define-key hg-commit-mode-file-map "\r" 'hg-commit-toggle-file)
264 (define-key hg-commit-mode-file-map "\r" 'hg-commit-toggle-file)
265
265
266
266
267 ;;; Convenience functions.
267 ;;; Convenience functions.
268
268
269 (defsubst hg-binary ()
269 (defsubst hg-binary ()
270 (if hg-binary
270 (if hg-binary
271 hg-binary
271 hg-binary
272 (error "No `hg' executable found!")))
272 (error "No `hg' executable found!")))
273
273
274 (defsubst hg-replace-in-string (str regexp newtext &optional literal)
274 (defsubst hg-replace-in-string (str regexp newtext &optional literal)
275 "Replace all matches in STR for REGEXP with NEWTEXT string.
275 "Replace all matches in STR for REGEXP with NEWTEXT string.
276 Return the new string. Optional LITERAL non-nil means do a literal
276 Return the new string. Optional LITERAL non-nil means do a literal
277 replacement.
277 replacement.
278
278
279 This function bridges yet another pointless impedance gap between
279 This function bridges yet another pointless impedance gap between
280 XEmacs and GNU Emacs."
280 XEmacs and GNU Emacs."
281 (if (fboundp 'replace-in-string)
281 (if (fboundp 'replace-in-string)
282 (replace-in-string str regexp newtext literal)
282 (replace-in-string str regexp newtext literal)
283 (replace-regexp-in-string regexp newtext str nil literal)))
283 (replace-regexp-in-string regexp newtext str nil literal)))
284
284
285 (defsubst hg-strip (str)
285 (defsubst hg-strip (str)
286 "Strip leading and trailing blank lines from a string."
286 "Strip leading and trailing blank lines from a string."
287 (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "")
287 (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "")
288 "\\`[ \t\r\n]*[\r\n]" ""))
288 "\\`[ \t\r\n]*[\r\n]" ""))
289
289
290 (defsubst hg-chomp (str)
290 (defsubst hg-chomp (str)
291 "Strip trailing newlines from a string."
291 "Strip trailing newlines from a string."
292 (hg-replace-in-string str "[\r\n]+\'" ""))
292 (hg-replace-in-string str "[\r\n]+\'" ""))
293
293
294 (defun hg-run-command (command &rest args)
294 (defun hg-run-command (command &rest args)
295 "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
295 "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
296 The list ARGS contains a list of arguments to pass to the command."
296 The list ARGS contains a list of arguments to pass to the command."
297 (let* (exit-code
297 (let* (exit-code
298 (output
298 (output
299 (with-output-to-string
299 (with-output-to-string
300 (with-current-buffer
300 (with-current-buffer
301 standard-output
301 standard-output
302 (setq exit-code
302 (setq exit-code
303 (apply 'call-process command nil t nil args))))))
303 (apply 'call-process command nil t nil args))))))
304 (cons exit-code output)))
304 (cons exit-code output)))
305
305
306 (defun hg-run (command &rest args)
306 (defun hg-run (command &rest args)
307 "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)."
307 "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)."
308 (apply 'hg-run-command (hg-binary) command args))
308 (apply 'hg-run-command (hg-binary) command args))
309
309
310 (defun hg-run0 (command &rest args)
310 (defun hg-run0 (command &rest args)
311 "Run the Mercurial command COMMAND, returning its output.
311 "Run the Mercurial command COMMAND, returning its output.
312 If the command does not exit with a zero status code, raise an error."
312 If the command does not exit with a zero status code, raise an error."
313 (let ((res (apply 'hg-run-command (hg-binary) command args)))
313 (let ((res (apply 'hg-run-command (hg-binary) command args)))
314 (if (not (eq (car res) 0))
314 (if (not (eq (car res) 0))
315 (error "Mercurial command failed %s - exit code %s"
315 (error "Mercurial command failed %s - exit code %s"
316 (cons command args)
316 (cons command args)
317 (car res))
317 (car res))
318 (cdr res))))
318 (cdr res))))
319
319
320 (defun hg-sync-buffers (path)
320 (defun hg-sync-buffers (path)
321 "Sync buffers visiting PATH with their on-disk copies.
321 "Sync buffers visiting PATH with their on-disk copies.
322 If PATH is not being visited, but is under the repository root, sync
322 If PATH is not being visited, but is under the repository root, sync
323 all buffers visiting files in the repository."
323 all buffers visiting files in the repository."
324 (let ((buf (find-buffer-visiting path)))
324 (let ((buf (find-buffer-visiting path)))
325 (if buf
325 (if buf
326 (with-current-buffer buf
326 (with-current-buffer buf
327 (vc-buffer-sync))
327 (vc-buffer-sync))
328 (hg-do-across-repo path
328 (hg-do-across-repo path
329 (vc-buffer-sync)))))
329 (vc-buffer-sync)))))
330
330
331 (defun hg-buffer-commands (pnt)
331 (defun hg-buffer-commands (pnt)
332 "Use the properties of a character to do something sensible."
332 "Use the properties of a character to do something sensible."
333 (interactive "d")
333 (interactive "d")
334 (let ((rev (get-char-property pnt 'rev))
334 (let ((rev (get-char-property pnt 'rev))
335 (file (get-char-property pnt 'file))
335 (file (get-char-property pnt 'file))
336 (date (get-char-property pnt 'date))
336 (date (get-char-property pnt 'date))
337 (user (get-char-property pnt 'user))
337 (user (get-char-property pnt 'user))
338 (host (get-char-property pnt 'host))
338 (host (get-char-property pnt 'host))
339 (prev-buf (current-buffer)))
339 (prev-buf (current-buffer)))
340 (cond
340 (cond
341 (file
341 (file
342 (find-file-other-window file))
342 (find-file-other-window file))
343 (rev
343 (rev
344 (hg-diff hg-view-file-name rev rev prev-buf))
344 (hg-diff hg-view-file-name rev rev prev-buf))
345 ((message "I don't know how to do that yet")))))
345 ((message "I don't know how to do that yet")))))
346
346
347 (defsubst hg-event-point (event)
347 (defsubst hg-event-point (event)
348 "Return the character position of the mouse event EVENT."
348 "Return the character position of the mouse event EVENT."
349 (if hg-running-xemacs
349 (if hg-running-xemacs
350 (event-point event)
350 (event-point event)
351 (posn-point (event-start event))))
351 (posn-point (event-start event))))
352
352
353 (defsubst hg-event-window (event)
353 (defsubst hg-event-window (event)
354 "Return the window over which mouse event EVENT occurred."
354 "Return the window over which mouse event EVENT occurred."
355 (if hg-running-xemacs
355 (if hg-running-xemacs
356 (event-window event)
356 (event-window event)
357 (posn-window (event-start event))))
357 (posn-window (event-start event))))
358
358
359 (defun hg-buffer-mouse-clicked (event)
359 (defun hg-buffer-mouse-clicked (event)
360 "Translate the mouse clicks in a HG log buffer to character events.
360 "Translate the mouse clicks in a HG log buffer to character events.
361 These are then handed off to `hg-buffer-commands'.
361 These are then handed off to `hg-buffer-commands'.
362
362
363 Handle frickin' frackin' gratuitous event-related incompatibilities."
363 Handle frickin' frackin' gratuitous event-related incompatibilities."
364 (interactive "e")
364 (interactive "e")
365 (select-window (hg-event-window event))
365 (select-window (hg-event-window event))
366 (hg-buffer-commands (hg-event-point event)))
366 (hg-buffer-commands (hg-event-point event)))
367
367
368 (unless (fboundp 'view-minor-mode)
368 (unless (fboundp 'view-minor-mode)
369 (defun view-minor-mode (prev-buffer exit-func)
369 (defun view-minor-mode (prev-buffer exit-func)
370 (view-mode)))
370 (view-mode)))
371
371
372 (defsubst hg-abbrev-file-name (file)
372 (defsubst hg-abbrev-file-name (file)
373 "Portable wrapper around abbreviate-file-name."
373 "Portable wrapper around abbreviate-file-name."
374 (if hg-running-xemacs
374 (if hg-running-xemacs
375 (abbreviate-file-name file t)
375 (abbreviate-file-name file t)
376 (abbreviate-file-name file)))
376 (abbreviate-file-name file)))
377
377
378 (defun hg-read-file-name (&optional prompt default)
378 (defun hg-read-file-name (&optional prompt default)
379 "Read a file or directory name, or a pattern, to use with a command."
379 "Read a file or directory name, or a pattern, to use with a command."
380 (save-excursion
380 (save-excursion
381 (while hg-prev-buffer
381 (while hg-prev-buffer
382 (set-buffer hg-prev-buffer))
382 (set-buffer hg-prev-buffer))
383 (let ((path (or default (buffer-file-name))))
383 (let ((path (or default (buffer-file-name))))
384 (if (or (not path) current-prefix-arg)
384 (if (or (not path) current-prefix-arg)
385 (expand-file-name
385 (expand-file-name
386 (read-file-name (format "File, directory or pattern%s: "
386 (read-file-name (format "File, directory or pattern%s: "
387 (or prompt ""))
387 (or prompt ""))
388 (and path (file-name-directory path))
388 (and path (file-name-directory path))
389 nil nil
389 nil nil
390 (and path (file-name-nondirectory path))
390 (and path (file-name-nondirectory path))
391 'hg-file-history))
391 'hg-file-history))
392 path))))
392 path))))
393
393
394 (defun hg-read-config ()
394 (defun hg-read-config ()
395 "Return an alist of (key . value) pairs of Mercurial config data.
395 "Return an alist of (key . value) pairs of Mercurial config data.
396 Each key is of the form (section . name)."
396 Each key is of the form (section . name)."
397 (let (items)
397 (let (items)
398 (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
398 (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
399 (string-match "^\\([^=]*\\)=\\(.*\\)" line)
399 (string-match "^\\([^=]*\\)=\\(.*\\)" line)
400 (let* ((left (substring line (match-beginning 1) (match-end 1)))
400 (let* ((left (substring line (match-beginning 1) (match-end 1)))
401 (right (substring line (match-beginning 2) (match-end 2)))
401 (right (substring line (match-beginning 2) (match-end 2)))
402 (key (split-string left "\\."))
402 (key (split-string left "\\."))
403 (value (hg-replace-in-string right "\\\\n" "\n" t)))
403 (value (hg-replace-in-string right "\\\\n" "\n" t)))
404 (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
404 (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
405
405
406 (defun hg-config-section (section config)
406 (defun hg-config-section (section config)
407 "Return an alist of (name . value) pairs for SECTION of CONFIG."
407 "Return an alist of (name . value) pairs for SECTION of CONFIG."
408 (let (items)
408 (let (items)
409 (dolist (item config items)
409 (dolist (item config items)
410 (when (equal (caar item) section)
410 (when (equal (caar item) section)
411 (setq items (cons (cons (cdar item) (cdr item)) items))))))
411 (setq items (cons (cons (cdar item) (cdr item)) items))))))
412
412
413 (defun hg-string-starts-with (sub str)
413 (defun hg-string-starts-with (sub str)
414 "Indicate whether string STR starts with the substring or character SUB."
414 "Indicate whether string STR starts with the substring or character SUB."
415 (if (not (stringp sub))
415 (if (not (stringp sub))
416 (and (> (length str) 0) (equal (elt str 0) sub))
416 (and (> (length str) 0) (equal (elt str 0) sub))
417 (let ((sub-len (length sub)))
417 (let ((sub-len (length sub)))
418 (and (<= sub-len (length str))
418 (and (<= sub-len (length str))
419 (string= sub (substring str 0 sub-len))))))
419 (string= sub (substring str 0 sub-len))))))
420
420
421 (defun hg-complete-repo (string predicate all)
421 (defun hg-complete-repo (string predicate all)
422 "Attempt to complete a repository name.
422 "Attempt to complete a repository name.
423 We complete on either symbolic names from Mercurial's config or real
423 We complete on either symbolic names from Mercurial's config or real
424 directory names from the file system. We do not penalise URLs."
424 directory names from the file system. We do not penalise URLs."
425 (or (if all
425 (or (if all
426 (all-completions string hg-repo-completion-table predicate)
426 (all-completions string hg-repo-completion-table predicate)
427 (try-completion string hg-repo-completion-table predicate))
427 (try-completion string hg-repo-completion-table predicate))
428 (let* ((str (expand-file-name string))
428 (let* ((str (expand-file-name string))
429 (dir (file-name-directory str))
429 (dir (file-name-directory str))
430 (file (file-name-nondirectory str)))
430 (file (file-name-nondirectory str)))
431 (if all
431 (if all
432 (let (completions)
432 (let (completions)
433 (dolist (name (delete "./" (file-name-all-completions file dir))
433 (dolist (name (delete "./" (file-name-all-completions file dir))
434 completions)
434 completions)
435 (let ((path (concat dir name)))
435 (let ((path (concat dir name)))
436 (when (file-directory-p path)
436 (when (file-directory-p path)
437 (setq completions (cons name completions))))))
437 (setq completions (cons name completions))))))
438 (let ((comp (file-name-completion file dir)))
438 (let ((comp (file-name-completion file dir)))
439 (if comp
439 (if comp
440 (hg-abbrev-file-name (concat dir comp))))))))
440 (hg-abbrev-file-name (concat dir comp))))))))
441
441
442 (defun hg-read-repo-name (&optional prompt initial-contents default)
442 (defun hg-read-repo-name (&optional prompt initial-contents default)
443 "Read the location of a repository."
443 "Read the location of a repository."
444 (save-excursion
444 (save-excursion
445 (while hg-prev-buffer
445 (while hg-prev-buffer
446 (set-buffer hg-prev-buffer))
446 (set-buffer hg-prev-buffer))
447 (let (hg-repo-completion-table)
447 (let (hg-repo-completion-table)
448 (if current-prefix-arg
448 (if current-prefix-arg
449 (progn
449 (progn
450 (dolist (path (hg-config-section "paths" (hg-read-config)))
450 (dolist (path (hg-config-section "paths" (hg-read-config)))
451 (setq hg-repo-completion-table
451 (setq hg-repo-completion-table
452 (cons (cons (car path) t) hg-repo-completion-table))
452 (cons (cons (car path) t) hg-repo-completion-table))
453 (unless (hg-string-starts-with directory-sep-char (cdr path))
453 (unless (hg-string-starts-with directory-sep-char (cdr path))
454 (setq hg-repo-completion-table
454 (setq hg-repo-completion-table
455 (cons (cons (cdr path) t) hg-repo-completion-table))))
455 (cons (cons (cdr path) t) hg-repo-completion-table))))
456 (completing-read (format "Repository%s: " (or prompt ""))
456 (completing-read (format "Repository%s: " (or prompt ""))
457 'hg-complete-repo
457 'hg-complete-repo
458 nil
458 nil
459 nil
459 nil
460 initial-contents
460 initial-contents
461 'hg-repo-history
461 'hg-repo-history
462 default))
462 default))
463 default))))
463 default))))
464
464
465 (defun hg-read-rev (&optional prompt default)
465 (defun hg-read-rev (&optional prompt default)
466 "Read a revision or tag, offering completions."
466 "Read a revision or tag, offering completions."
467 (save-excursion
467 (save-excursion
468 (while hg-prev-buffer
468 (while hg-prev-buffer
469 (set-buffer hg-prev-buffer))
469 (set-buffer hg-prev-buffer))
470 (let ((rev (or default "tip")))
470 (let ((rev (or default "tip")))
471 (if current-prefix-arg
471 (if current-prefix-arg
472 (let ((revs (split-string
472 (let ((revs (split-string
473 (hg-chomp
473 (hg-chomp
474 (hg-run0 "-q" "log" "-r"
474 (hg-run0 "-q" "log" "-r"
475 (format "-%d:tip" hg-rev-completion-limit)))
475 (format "-%d:tip" hg-rev-completion-limit)))
476 "[\n:]")))
476 "[\n:]")))
477 (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
477 (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
478 (setq revs (cons (car (split-string line "\\s-")) revs)))
478 (setq revs (cons (car (split-string line "\\s-")) revs)))
479 (completing-read (format "Revision%s (%s): "
479 (completing-read (format "Revision%s (%s): "
480 (or prompt "")
480 (or prompt "")
481 (or default "tip"))
481 (or default "tip"))
482 (map 'list 'cons revs revs)
482 (map 'list 'cons revs revs)
483 nil
483 nil
484 nil
484 nil
485 nil
485 nil
486 'hg-rev-history
486 'hg-rev-history
487 (or default "tip")))
487 (or default "tip")))
488 rev))))
488 rev))))
489
489
490 (defmacro hg-do-across-repo (path &rest body)
490 (defmacro hg-do-across-repo (path &rest body)
491 (let ((root-name (gensym "root-"))
491 (let ((root-name (gensym "root-"))
492 (buf-name (gensym "buf-")))
492 (buf-name (gensym "buf-")))
493 `(let ((,root-name (hg-root ,path)))
493 `(let ((,root-name (hg-root ,path)))
494 (save-excursion
494 (save-excursion
495 (dolist (,buf-name (buffer-list))
495 (dolist (,buf-name (buffer-list))
496 (set-buffer ,buf-name)
496 (set-buffer ,buf-name)
497 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
497 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
498 ,@body))))))
498 ,@body))))))
499
499
500 (put 'hg-do-across-repo 'lisp-indent-function 1)
500 (put 'hg-do-across-repo 'lisp-indent-function 1)
501
501
502
502
503 ;;; View mode bits.
503 ;;; View mode bits.
504
504
505 (defun hg-exit-view-mode (buf)
505 (defun hg-exit-view-mode (buf)
506 "Exit from hg-view-mode.
506 "Exit from hg-view-mode.
507 We delete the current window if entering hg-view-mode split the
507 We delete the current window if entering hg-view-mode split the
508 current frame."
508 current frame."
509 (when (and (eq buf (current-buffer))
509 (when (and (eq buf (current-buffer))
510 (> (length (window-list)) 1))
510 (> (length (window-list)) 1))
511 (delete-window))
511 (delete-window))
512 (when (buffer-live-p buf)
512 (when (buffer-live-p buf)
513 (kill-buffer buf)))
513 (kill-buffer buf)))
514
514
515 (defun hg-view-mode (prev-buffer &optional file-name)
515 (defun hg-view-mode (prev-buffer &optional file-name)
516 (goto-char (point-min))
516 (goto-char (point-min))
517 (set-buffer-modified-p nil)
517 (set-buffer-modified-p nil)
518 (toggle-read-only t)
518 (toggle-read-only t)
519 (view-minor-mode prev-buffer 'hg-exit-view-mode)
519 (view-minor-mode prev-buffer 'hg-exit-view-mode)
520 (use-local-map hg-view-mode-map)
520 (use-local-map hg-view-mode-map)
521 (setq truncate-lines t)
521 (setq truncate-lines t)
522 (when file-name
522 (when file-name
523 (set (make-local-variable 'hg-view-file-name)
523 (set (make-local-variable 'hg-view-file-name)
524 (hg-abbrev-file-name file-name))))
524 (hg-abbrev-file-name file-name))))
525
525
526 (defun hg-file-status (file)
526 (defun hg-file-status (file)
527 "Return status of FILE, or nil if FILE does not exist or is unmanaged."
527 "Return status of FILE, or nil if FILE does not exist or is unmanaged."
528 (let* ((s (hg-run "status" file))
528 (let* ((s (hg-run "status" file))
529 (exit (car s))
529 (exit (car s))
530 (output (cdr s)))
530 (output (cdr s)))
531 (if (= exit 0)
531 (if (= exit 0)
532 (let ((state (assoc (substring output 0 (min (length output) 2))
532 (let ((state (assoc (substring output 0 (min (length output) 2))
533 '(("M " . modified)
533 '(("M " . modified)
534 ("A " . added)
534 ("A " . added)
535 ("R " . removed)
535 ("R " . removed)
536 ("? " . nil)))))
536 ("? " . nil)))))
537 (if state
537 (if state
538 (cdr state)
538 (cdr state)
539 'normal)))))
539 'normal)))))
540
540
541 (defun hg-tip ()
541 (defun hg-tip ()
542 (split-string (hg-chomp (hg-run0 "-q" "tip")) ":"))
542 (split-string (hg-chomp (hg-run0 "-q" "tip")) ":"))
543
543
544 (defmacro hg-view-output (args &rest body)
544 (defmacro hg-view-output (args &rest body)
545 "Execute BODY in a clean buffer, then quickly display that buffer.
545 "Execute BODY in a clean buffer, then quickly display that buffer.
546 If the buffer contains one line, its contents are displayed in the
546 If the buffer contains one line, its contents are displayed in the
547 minibuffer. Otherwise, the buffer is displayed in view-mode.
547 minibuffer. Otherwise, the buffer is displayed in view-mode.
548 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
548 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
549 the name of the buffer to create, and FILE is the name of the file
549 the name of the buffer to create, and FILE is the name of the file
550 being viewed."
550 being viewed."
551 (let ((prev-buf (gensym "prev-buf-"))
551 (let ((prev-buf (gensym "prev-buf-"))
552 (v-b-name (car args))
552 (v-b-name (car args))
553 (v-m-rest (cdr args)))
553 (v-m-rest (cdr args)))
554 `(let ((view-buf-name ,v-b-name)
554 `(let ((view-buf-name ,v-b-name)
555 (,prev-buf (current-buffer)))
555 (,prev-buf (current-buffer)))
556 (get-buffer-create view-buf-name)
556 (get-buffer-create view-buf-name)
557 (kill-buffer view-buf-name)
557 (kill-buffer view-buf-name)
558 (get-buffer-create view-buf-name)
558 (get-buffer-create view-buf-name)
559 (set-buffer view-buf-name)
559 (set-buffer view-buf-name)
560 (save-excursion
560 (save-excursion
561 ,@body)
561 ,@body)
562 (case (count-lines (point-min) (point-max))
562 (case (count-lines (point-min) (point-max))
563 ((0)
563 ((0)
564 (kill-buffer view-buf-name)
564 (kill-buffer view-buf-name)
565 (message "(No output)"))
565 (message "(No output)"))
566 ((1)
566 ((1)
567 (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
567 (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
568 (kill-buffer view-buf-name)
568 (kill-buffer view-buf-name)
569 (message "%s" msg)))
569 (message "%s" msg)))
570 (t
570 (t
571 (pop-to-buffer view-buf-name)
571 (pop-to-buffer view-buf-name)
572 (setq hg-prev-buffer ,prev-buf)
572 (setq hg-prev-buffer ,prev-buf)
573 (hg-view-mode ,prev-buf ,@v-m-rest))))))
573 (hg-view-mode ,prev-buf ,@v-m-rest))))))
574
574
575 (put 'hg-view-output 'lisp-indent-function 1)
575 (put 'hg-view-output 'lisp-indent-function 1)
576
576
577 ;;; Context save and restore across revert.
577 ;;; Context save and restore across revert.
578
578
579 (defun hg-position-context (pos)
579 (defun hg-position-context (pos)
580 "Return information to help find the given position again."
580 "Return information to help find the given position again."
581 (let* ((end (min (point-max) (+ pos 98))))
581 (let* ((end (min (point-max) (+ pos 98))))
582 (list pos
582 (list pos
583 (buffer-substring (max (point-min) (- pos 2)) end)
583 (buffer-substring (max (point-min) (- pos 2)) end)
584 (- end pos))))
584 (- end pos))))
585
585
586 (defun hg-buffer-context ()
586 (defun hg-buffer-context ()
587 "Return information to help restore a user's editing context.
587 "Return information to help restore a user's editing context.
588 This is useful across reverts and merges, where a context is likely
588 This is useful across reverts and merges, where a context is likely
589 to have moved a little, but not really changed."
589 to have moved a little, but not really changed."
590 (let ((point-context (hg-position-context (point)))
590 (let ((point-context (hg-position-context (point)))
591 (mark-context (let ((mark (mark-marker)))
591 (mark-context (let ((mark (mark-marker)))
592 (and mark (hg-position-context mark)))))
592 (and mark (hg-position-context mark)))))
593 (list point-context mark-context)))
593 (list point-context mark-context)))
594
594
595 (defun hg-find-context (ctx)
595 (defun hg-find-context (ctx)
596 "Attempt to find a context in the given buffer.
596 "Attempt to find a context in the given buffer.
597 Always returns a valid, hopefully sane, position."
597 Always returns a valid, hopefully sane, position."
598 (let ((pos (nth 0 ctx))
598 (let ((pos (nth 0 ctx))
599 (str (nth 1 ctx))
599 (str (nth 1 ctx))
600 (fixup (nth 2 ctx)))
600 (fixup (nth 2 ctx)))
601 (save-excursion
601 (save-excursion
602 (goto-char (max (point-min) (- pos 15000)))
602 (goto-char (max (point-min) (- pos 15000)))
603 (if (and (not (equal str ""))
603 (if (and (not (equal str ""))
604 (search-forward str nil t))
604 (search-forward str nil t))
605 (- (point) fixup)
605 (- (point) fixup)
606 (max pos (point-min))))))
606 (max pos (point-min))))))
607
607
608 (defun hg-restore-context (ctx)
608 (defun hg-restore-context (ctx)
609 "Attempt to restore the user's editing context."
609 "Attempt to restore the user's editing context."
610 (let ((point-context (nth 0 ctx))
610 (let ((point-context (nth 0 ctx))
611 (mark-context (nth 1 ctx)))
611 (mark-context (nth 1 ctx)))
612 (goto-char (hg-find-context point-context))
612 (goto-char (hg-find-context point-context))
613 (when mark-context
613 (when mark-context
614 (set-mark (hg-find-context mark-context)))))
614 (set-mark (hg-find-context mark-context)))))
615
615
616
616
617 ;;; Hooks.
617 ;;; Hooks.
618
618
619 (defun hg-mode-line (&optional force)
619 (defun hg-mode-line (&optional force)
620 "Update the modeline with the current status of a file.
620 "Update the modeline with the current status of a file.
621 An update occurs if optional argument FORCE is non-nil,
621 An update occurs if optional argument FORCE is non-nil,
622 hg-update-modeline is non-nil, or we have not yet checked the state of
622 hg-update-modeline is non-nil, or we have not yet checked the state of
623 the file."
623 the file."
624 (when (and (hg-root) (or force hg-update-modeline (not hg-mode)))
624 (when (and (hg-root) (or force hg-update-modeline (not hg-mode)))
625 (let ((status (hg-file-status buffer-file-name)))
625 (let ((status (hg-file-status buffer-file-name)))
626 (setq hg-status status
626 (setq hg-status status
627 hg-mode (and status (concat " Hg:"
627 hg-mode (and status (concat " Hg:"
628 (car (hg-tip))
628 (car (hg-tip))
629 (cdr (assq status
629 (cdr (assq status
630 '((normal . "")
630 '((normal . "")
631 (removed . "r")
631 (removed . "r")
632 (added . "a")
632 (added . "a")
633 (modified . "m")))))))
633 (modified . "m")))))))
634 status)))
634 status)))
635
635
636 (defun hg-mode ()
636 (defun hg-mode ()
637 "Minor mode for Mercurial distributed SCM integration.
637 "Minor mode for Mercurial distributed SCM integration.
638
638
639 The Mercurial mode user interface is based on that of VC mode, so if
639 The Mercurial mode user interface is based on that of VC mode, so if
640 you're already familiar with VC, the same keybindings and functions
640 you're already familiar with VC, the same keybindings and functions
641 will generally work.
641 will generally work.
642
642
643 Below is a list of many common SCM tasks. In the list, `G/L'
643 Below is a list of many common SCM tasks. In the list, `G/L'
644 indicates whether a key binding is global (G) to a repository or local
644 indicates whether a key binding is global (G) to a repository or local
645 (L) to a file. Many commands take a prefix argument.
645 (L) to a file. Many commands take a prefix argument.
646
646
647 SCM Task G/L Key Binding Command Name
647 SCM Task G/L Key Binding Command Name
648 -------- --- ----------- ------------
648 -------- --- ----------- ------------
649 Help overview (what you are reading) G C-c h h hg-help-overview
649 Help overview (what you are reading) G C-c h h hg-help-overview
650
650
651 Tell Mercurial to manage a file G C-c h a hg-add
651 Tell Mercurial to manage a file G C-c h a hg-add
652 Commit changes to current file only L C-x v n hg-commit-start
652 Commit changes to current file only L C-x v n hg-commit-start
653 Undo changes to file since commit L C-x v u hg-revert-buffer
653 Undo changes to file since commit L C-x v u hg-revert-buffer
654
654
655 Diff file vs last checkin L C-x v = hg-diff
655 Diff file vs last checkin L C-x v = hg-diff
656
656
657 View file change history L C-x v l hg-log
657 View file change history L C-x v l hg-log
658 View annotated file L C-x v a hg-annotate
658 View annotated file L C-x v a hg-annotate
659
659
660 Diff repo vs last checkin G C-c h = hg-diff-repo
660 Diff repo vs last checkin G C-c h = hg-diff-repo
661 View status of files in repo G C-c h s hg-status
661 View status of files in repo G C-c h s hg-status
662 Commit all changes G C-c h c hg-commit-start
662 Commit all changes G C-c h c hg-commit-start
663
663
664 Undo all changes since last commit G C-c h U hg-revert
664 Undo all changes since last commit G C-c h U hg-revert
665 View repo change history G C-c h l hg-log-repo
665 View repo change history G C-c h l hg-log-repo
666
666
667 See changes that can be pulled G C-c h , hg-incoming
667 See changes that can be pulled G C-c h , hg-incoming
668 Pull changes G C-c h < hg-pull
668 Pull changes G C-c h < hg-pull
669 Update working directory after pull G C-c h u hg-update
669 Update working directory after pull G C-c h u hg-update
670 See changes that can be pushed G C-c h . hg-outgoing
670 See changes that can be pushed G C-c h . hg-outgoing
671 Push changes G C-c h > hg-push"
671 Push changes G C-c h > hg-push"
672 (run-hooks 'hg-mode-hook))
672 (run-hooks 'hg-mode-hook))
673
673
674 (defun hg-find-file-hook ()
674 (defun hg-find-file-hook ()
675 (when (hg-mode-line)
675 (when (hg-mode-line)
676 (hg-mode)))
676 (hg-mode)))
677
677
678 (add-hook 'find-file-hooks 'hg-find-file-hook)
678 (add-hook 'find-file-hooks 'hg-find-file-hook)
679
679
680 (defun hg-after-save-hook ()
680 (defun hg-after-save-hook ()
681 (let ((old-status hg-status))
681 (let ((old-status hg-status))
682 (hg-mode-line)
682 (hg-mode-line)
683 (if (and (not old-status) hg-status)
683 (if (and (not old-status) hg-status)
684 (hg-mode))))
684 (hg-mode))))
685
685
686 (add-hook 'after-save-hook 'hg-after-save-hook)
686 (add-hook 'after-save-hook 'hg-after-save-hook)
687
687
688
688
689 ;;; User interface functions.
689 ;;; User interface functions.
690
690
691 (defun hg-help-overview ()
691 (defun hg-help-overview ()
692 "This is an overview of the Mercurial SCM mode for Emacs.
692 "This is an overview of the Mercurial SCM mode for Emacs.
693
693
694 You can find the source code, license (GPL v2), and credits for this
694 You can find the source code, license (GPL v2), and credits for this
695 code by typing `M-x find-library mercurial RET'."
695 code by typing `M-x find-library mercurial RET'."
696 (interactive)
696 (interactive)
697 (hg-view-output ("Mercurial Help Overview")
697 (hg-view-output ("Mercurial Help Overview")
698 (insert (documentation 'hg-help-overview))
698 (insert (documentation 'hg-help-overview))
699 (let ((pos (point)))
699 (let ((pos (point)))
700 (insert (documentation 'hg-mode))
700 (insert (documentation 'hg-mode))
701 (goto-char pos)
701 (goto-char pos)
702 (kill-line))))
702 (kill-line))))
703
703
704 (defun hg-add (path)
704 (defun hg-add (path)
705 "Add PATH to the Mercurial repository on the next commit.
705 "Add PATH to the Mercurial repository on the next commit.
706 With a prefix argument, prompt for the path to add."
706 With a prefix argument, prompt for the path to add."
707 (interactive (list (hg-read-file-name " to add")))
707 (interactive (list (hg-read-file-name " to add")))
708 (let ((buf (current-buffer))
708 (let ((buf (current-buffer))
709 (update (equal buffer-file-name path)))
709 (update (equal buffer-file-name path)))
710 (hg-view-output (hg-output-buffer-name)
710 (hg-view-output (hg-output-buffer-name)
711 (apply 'call-process (hg-binary) nil t nil (list "add" path)))
711 (apply 'call-process (hg-binary) nil t nil (list "add" path)))
712 (when update
712 (when update
713 (with-current-buffer buf
713 (with-current-buffer buf
714 (hg-mode-line)))))
714 (hg-mode-line)))))
715
715
716 (defun hg-addremove ()
716 (defun hg-addremove ()
717 (interactive)
717 (interactive)
718 (error "not implemented"))
718 (error "not implemented"))
719
719
720 (defun hg-annotate ()
720 (defun hg-annotate ()
721 (interactive)
721 (interactive)
722 (error "not implemented"))
722 (error "not implemented"))
723
723
724 (defun hg-commit-toggle-file (pos)
724 (defun hg-commit-toggle-file (pos)
725 "Toggle whether or not the file at POS will be committed."
725 "Toggle whether or not the file at POS will be committed."
726 (interactive "d")
726 (interactive "d")
727 (save-excursion
727 (save-excursion
728 (goto-char pos)
728 (goto-char pos)
729 (let ((face (get-text-property pos 'face))
729 (let ((face (get-text-property pos 'face))
730 (inhibit-read-only t)
730 (inhibit-read-only t)
731 bol)
731 bol)
732 (beginning-of-line)
732 (beginning-of-line)
733 (setq bol (+ (point) 4))
733 (setq bol (+ (point) 4))
734 (end-of-line)
734 (end-of-line)
735 (if (eq face 'bold)
735 (if (eq face 'bold)
736 (progn
736 (progn
737 (remove-text-properties bol (point) '(face nil))
737 (remove-text-properties bol (point) '(face nil))
738 (message "%s will not be committed"
738 (message "%s will not be committed"
739 (buffer-substring bol (point))))
739 (buffer-substring bol (point))))
740 (add-text-properties bol (point) '(face bold))
740 (add-text-properties bol (point) '(face bold))
741 (message "%s will be committed"
741 (message "%s will be committed"
742 (buffer-substring bol (point)))))))
742 (buffer-substring bol (point)))))))
743
743
744 (defun hg-commit-mouse-clicked (event)
744 (defun hg-commit-mouse-clicked (event)
745 "Toggle whether or not the file at POS will be committed."
745 "Toggle whether or not the file at POS will be committed."
746 (interactive "@e")
746 (interactive "@e")
747 (hg-commit-toggle-file (hg-event-point event)))
747 (hg-commit-toggle-file (hg-event-point event)))
748
748
749 (defun hg-commit-kill ()
749 (defun hg-commit-kill ()
750 "Kill the commit currently being prepared."
750 "Kill the commit currently being prepared."
751 (interactive)
751 (interactive)
752 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
752 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
753 (let ((buf hg-prev-buffer))
753 (let ((buf hg-prev-buffer))
754 (kill-buffer nil)
754 (kill-buffer nil)
755 (switch-to-buffer buf))))
755 (switch-to-buffer buf))))
756
756
757 (defun hg-commit-finish ()
757 (defun hg-commit-finish ()
758 "Finish preparing a commit, and perform the actual commit.
758 "Finish preparing a commit, and perform the actual commit.
759 The hook hg-pre-commit-hook is run before anything else is done. If
759 The hook hg-pre-commit-hook is run before anything else is done. If
760 the commit message is empty and hg-commit-allow-empty-message is nil,
760 the commit message is empty and hg-commit-allow-empty-message is nil,
761 an error is raised. If the list of files to commit is empty and
761 an error is raised. If the list of files to commit is empty and
762 hg-commit-allow-empty-file-list is nil, an error is raised."
762 hg-commit-allow-empty-file-list is nil, an error is raised."
763 (interactive)
763 (interactive)
764 (let ((root hg-root))
764 (let ((root hg-root))
765 (save-excursion
765 (save-excursion
766 (run-hooks 'hg-pre-commit-hook)
766 (run-hooks 'hg-pre-commit-hook)
767 (goto-char (point-min))
767 (goto-char (point-min))
768 (search-forward hg-commit-message-start)
768 (search-forward hg-commit-message-start)
769 (let (message files)
769 (let (message files)
770 (let ((start (point)))
770 (let ((start (point)))
771 (goto-char (point-max))
771 (goto-char (point-max))
772 (search-backward hg-commit-message-end)
772 (search-backward hg-commit-message-end)
773 (setq message (hg-strip (buffer-substring start (point)))))
773 (setq message (hg-strip (buffer-substring start (point)))))
774 (when (and (= (length message) 0)
774 (when (and (= (length message) 0)
775 (not hg-commit-allow-empty-message))
775 (not hg-commit-allow-empty-message))
776 (error "Cannot proceed - commit message is empty"))
776 (error "Cannot proceed - commit message is empty"))
777 (forward-line 1)
777 (forward-line 1)
778 (beginning-of-line)
778 (beginning-of-line)
779 (while (< (point) (point-max))
779 (while (< (point) (point-max))
780 (let ((pos (+ (point) 4)))
780 (let ((pos (+ (point) 4)))
781 (end-of-line)
781 (end-of-line)
782 (when (eq (get-text-property pos 'face) 'bold)
782 (when (eq (get-text-property pos 'face) 'bold)
783 (end-of-line)
783 (end-of-line)
784 (setq files (cons (buffer-substring pos (point)) files))))
784 (setq files (cons (buffer-substring pos (point)) files))))
785 (forward-line 1))
785 (forward-line 1))
786 (when (and (= (length files) 0)
786 (when (and (= (length files) 0)
787 (not hg-commit-allow-empty-file-list))
787 (not hg-commit-allow-empty-file-list))
788 (error "Cannot proceed - no files to commit"))
788 (error "Cannot proceed - no files to commit"))
789 (setq message (concat message "\n"))
789 (setq message (concat message "\n"))
790 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
790 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
791 (let ((buf hg-prev-buffer))
791 (let ((buf hg-prev-buffer))
792 (kill-buffer nil)
792 (kill-buffer nil)
793 (switch-to-buffer buf))
793 (switch-to-buffer buf))
794 (hg-do-across-repo root
794 (hg-do-across-repo root
795 (hg-mode-line)))))
795 (hg-mode-line)))))
796
796
797 (defun hg-commit-mode ()
797 (defun hg-commit-mode ()
798 "Mode for describing a commit of changes to a Mercurial repository.
798 "Mode for describing a commit of changes to a Mercurial repository.
799 This involves two actions: describing the changes with a commit
799 This involves two actions: describing the changes with a commit
800 message, and choosing the files to commit.
800 message, and choosing the files to commit.
801
801
802 To describe the commit, simply type some text in the designated area.
802 To describe the commit, simply type some text in the designated area.
803
803
804 By default, all modified, added and removed files are selected for
804 By default, all modified, added and removed files are selected for
805 committing. Files that will be committed are displayed in bold face\;
805 committing. Files that will be committed are displayed in bold face\;
806 those that will not are displayed in normal face.
806 those that will not are displayed in normal face.
807
807
808 To toggle whether a file will be committed, move the cursor over a
808 To toggle whether a file will be committed, move the cursor over a
809 particular file and hit space or return. Alternatively, middle click
809 particular file and hit space or return. Alternatively, middle click
810 on the file.
810 on the file.
811
811
812 Key bindings
812 Key bindings
813 ------------
813 ------------
814 \\[hg-commit-finish] proceed with commit
814 \\[hg-commit-finish] proceed with commit
815 \\[hg-commit-kill] kill commit
815 \\[hg-commit-kill] kill commit
816
816
817 \\[hg-diff-repo] view diff of pending changes"
817 \\[hg-diff-repo] view diff of pending changes"
818 (interactive)
818 (interactive)
819 (use-local-map hg-commit-mode-map)
819 (use-local-map hg-commit-mode-map)
820 (set-syntax-table text-mode-syntax-table)
820 (set-syntax-table text-mode-syntax-table)
821 (setq local-abbrev-table text-mode-abbrev-table
821 (setq local-abbrev-table text-mode-abbrev-table
822 major-mode 'hg-commit-mode
822 major-mode 'hg-commit-mode
823 mode-name "Hg-Commit")
823 mode-name "Hg-Commit")
824 (set-buffer-modified-p nil)
824 (set-buffer-modified-p nil)
825 (setq buffer-undo-list nil)
825 (setq buffer-undo-list nil)
826 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
826 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
827
827
828 (defun hg-commit-start ()
828 (defun hg-commit-start ()
829 "Prepare a commit of changes to the repository containing the current file."
829 "Prepare a commit of changes to the repository containing the current file."
830 (interactive)
830 (interactive)
831 (while hg-prev-buffer
831 (while hg-prev-buffer
832 (set-buffer hg-prev-buffer))
832 (set-buffer hg-prev-buffer))
833 (let ((root (hg-root))
833 (let ((root (hg-root))
834 (prev-buffer (current-buffer))
834 (prev-buffer (current-buffer))
835 modified-files)
835 modified-files)
836 (unless root
836 (unless root
837 (error "Cannot commit outside a repository!"))
837 (error "Cannot commit outside a repository!"))
838 (hg-sync-buffers root)
838 (hg-sync-buffers root)
839 (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
839 (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
840 (when (and (= (length modified-files) 0)
840 (when (and (= (length modified-files) 0)
841 (not hg-commit-allow-empty-file-list))
841 (not hg-commit-allow-empty-file-list))
842 (error "No pending changes to commit"))
842 (error "No pending changes to commit"))
843 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
843 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
844 (pop-to-buffer (get-buffer-create buf-name))
844 (pop-to-buffer (get-buffer-create buf-name))
845 (when (= (point-min) (point-max))
845 (when (= (point-min) (point-max))
846 (set (make-local-variable 'hg-root) root)
846 (set (make-local-variable 'hg-root) root)
847 (setq hg-prev-buffer prev-buffer)
847 (setq hg-prev-buffer prev-buffer)
848 (insert "\n")
848 (insert "\n")
849 (let ((bol (point)))
849 (let ((bol (point)))
850 (insert hg-commit-message-end)
850 (insert hg-commit-message-end)
851 (add-text-properties bol (point) '(face bold-italic)))
851 (add-text-properties bol (point) '(face bold-italic)))
852 (let ((file-area (point)))
852 (let ((file-area (point)))
853 (insert modified-files)
853 (insert modified-files)
854 (goto-char file-area)
854 (goto-char file-area)
855 (while (< (point) (point-max))
855 (while (< (point) (point-max))
856 (let ((bol (point)))
856 (let ((bol (point)))
857 (forward-char 1)
857 (forward-char 1)
858 (insert " ")
858 (insert " ")
859 (end-of-line)
859 (end-of-line)
860 (add-text-properties (+ bol 4) (point)
860 (add-text-properties (+ bol 4) (point)
861 '(face bold mouse-face highlight)))
861 '(face bold mouse-face highlight)))
862 (forward-line 1))
862 (forward-line 1))
863 (goto-char file-area)
863 (goto-char file-area)
864 (add-text-properties (point) (point-max)
864 (add-text-properties (point) (point-max)
865 `(keymap ,hg-commit-mode-file-map))
865 `(keymap ,hg-commit-mode-file-map))
866 (goto-char (point-min))
866 (goto-char (point-min))
867 (insert hg-commit-message-start)
867 (insert hg-commit-message-start)
868 (add-text-properties (point-min) (point) '(face bold-italic))
868 (add-text-properties (point-min) (point) '(face bold-italic))
869 (insert "\n\n")
869 (insert "\n\n")
870 (forward-line -1)
870 (forward-line -1)
871 (save-excursion
871 (save-excursion
872 (goto-char (point-max))
872 (goto-char (point-max))
873 (search-backward hg-commit-message-end)
873 (search-backward hg-commit-message-end)
874 (add-text-properties (match-beginning 0) (point-max)
874 (add-text-properties (match-beginning 0) (point-max)
875 '(read-only t))
875 '(read-only t))
876 (goto-char (point-min))
876 (goto-char (point-min))
877 (search-forward hg-commit-message-start)
877 (search-forward hg-commit-message-start)
878 (add-text-properties (match-beginning 0) (match-end 0)
878 (add-text-properties (match-beginning 0) (match-end 0)
879 '(read-only t)))
879 '(read-only t)))
880 (hg-commit-mode))))))
880 (hg-commit-mode))))))
881
881
882 (defun hg-diff (path &optional rev1 rev2)
882 (defun hg-diff (path &optional rev1 rev2)
883 "Show the differences between REV1 and REV2 of PATH.
883 "Show the differences between REV1 and REV2 of PATH.
884 When called interactively, the default behaviour is to treat REV1 as
884 When called interactively, the default behaviour is to treat REV1 as
885 the tip revision, REV2 as the current edited version of the file, and
885 the tip revision, REV2 as the current edited version of the file, and
886 PATH as the file edited in the current buffer.
886 PATH as the file edited in the current buffer.
887 With a prefix argument, prompt for all of these."
887 With a prefix argument, prompt for all of these."
888 (interactive (list (hg-read-file-name " to diff")
888 (interactive (list (hg-read-file-name " to diff")
889 (hg-read-rev " to start with")
889 (hg-read-rev " to start with")
890 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
890 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
891 (and (not (eq rev2 'working-dir)) rev2))))
891 (and (not (eq rev2 'working-dir)) rev2))))
892 (hg-sync-buffers path)
892 (hg-sync-buffers path)
893 (let ((a-path (hg-abbrev-file-name path))
893 (let ((a-path (hg-abbrev-file-name path))
894 (r1 (or rev1 "tip"))
894 (r1 (or rev1 "tip"))
895 diff)
895 diff)
896 (hg-view-output ((cond
896 (hg-view-output ((cond
897 ((and (equal r1 "tip") (not rev2))
897 ((and (equal r1 "tip") (not rev2))
898 (format "Mercurial: Diff against tip of %s" a-path))
898 (format "Mercurial: Diff against tip of %s" a-path))
899 ((equal r1 rev2)
899 ((equal r1 rev2)
900 (format "Mercurial: Diff of rev %s of %s" r1 a-path))
900 (format "Mercurial: Diff of rev %s of %s" r1 a-path))
901 (t
901 (t
902 (format "Mercurial: Diff from rev %s to %s of %s"
902 (format "Mercurial: Diff from rev %s to %s of %s"
903 r1 (or rev2 "Current") a-path))))
903 r1 (or rev2 "Current") a-path))))
904 (if rev2
904 (if rev2
905 (call-process (hg-binary) nil t nil "diff" "-r" r1 "-r" rev2 path)
905 (call-process (hg-binary) nil t nil "diff" "-r" r1 "-r" rev2 path)
906 (call-process (hg-binary) nil t nil "diff" "-r" r1 path))
906 (call-process (hg-binary) nil t nil "diff" "-r" r1 path))
907 (diff-mode)
907 (diff-mode)
908 (setq diff (not (= (point-min) (point-max))))
908 (setq diff (not (= (point-min) (point-max))))
909 (font-lock-fontify-buffer))
909 (font-lock-fontify-buffer))
910 diff))
910 diff))
911
911
912 (defun hg-diff-repo ()
912 (defun hg-diff-repo ()
913 "Show the differences between the working copy and the tip revision."
913 "Show the differences between the working copy and the tip revision."
914 (interactive)
914 (interactive)
915 (hg-diff (hg-root)))
915 (hg-diff (hg-root)))
916
916
917 (defun hg-forget (path)
917 (defun hg-forget (path)
918 "Lose track of PATH, which has been added, but not yet committed.
918 "Lose track of PATH, which has been added, but not yet committed.
919 This will prevent the file from being incorporated into the Mercurial
919 This will prevent the file from being incorporated into the Mercurial
920 repository on the next commit.
920 repository on the next commit.
921 With a prefix argument, prompt for the path to forget."
921 With a prefix argument, prompt for the path to forget."
922 (interactive (list (hg-read-file-name " to forget")))
922 (interactive (list (hg-read-file-name " to forget")))
923 (let ((buf (current-buffer))
923 (let ((buf (current-buffer))
924 (update (equal buffer-file-name path)))
924 (update (equal buffer-file-name path)))
925 (hg-view-output (hg-output-buffer-name)
925 (hg-view-output (hg-output-buffer-name)
926 (apply 'call-process (hg-binary) nil t nil (list "forget" path)))
926 (apply 'call-process (hg-binary) nil t nil (list "forget" path)))
927 (when update
927 (when update
928 (with-current-buffer buf
928 (with-current-buffer buf
929 (hg-mode-line)))))
929 (hg-mode-line)))))
930
930
931 (defun hg-incoming (&optional repo)
931 (defun hg-incoming (&optional repo)
932 "Display changesets present in REPO that are not present locally."
932 "Display changesets present in REPO that are not present locally."
933 (interactive (list (hg-read-repo-name " where changes would come from")))
933 (interactive (list (hg-read-repo-name " where changes would come from")))
934 (hg-view-output ((format "Mercurial: Incoming from %s to %s"
934 (hg-view-output ((format "Mercurial: Incoming from %s to %s"
935 (hg-abbrev-file-name (hg-root))
935 (hg-abbrev-file-name (hg-root))
936 (hg-abbrev-file-name
936 (hg-abbrev-file-name
937 (or repo hg-incoming-repository))))
937 (or repo hg-incoming-repository))))
938 (call-process (hg-binary) nil t nil "incoming"
938 (call-process (hg-binary) nil t nil "incoming"
939 (or repo hg-incoming-repository))
939 (or repo hg-incoming-repository))
940 (hg-log-mode)))
940 (hg-log-mode)))
941
941
942 (defun hg-init ()
942 (defun hg-init ()
943 (interactive)
943 (interactive)
944 (error "not implemented"))
944 (error "not implemented"))
945
945
946 (defun hg-log-mode ()
946 (defun hg-log-mode ()
947 "Mode for viewing a Mercurial change log."
947 "Mode for viewing a Mercurial change log."
948 (goto-char (point-min))
948 (goto-char (point-min))
949 (when (looking-at "^searching for changes")
949 (when (looking-at "^searching for changes")
950 (kill-entire-line))
950 (kill-entire-line))
951 (run-hooks 'hg-log-mode-hook))
951 (run-hooks 'hg-log-mode-hook))
952
952
953 (defun hg-log (path &optional rev1 rev2)
953 (defun hg-log (path &optional rev1 rev2)
954 "Display the revision history of PATH, between REV1 and REV2.
954 "Display the revision history of PATH, between REV1 and REV2.
955 REV1 defaults to hg-log-limit changes from the tip revision, while
955 REV1 defaults to hg-log-limit changes from the tip revision, while
956 REV2 defaults to the tip.
956 REV2 defaults to the tip.
957 With a prefix argument, prompt for each parameter."
957 With a prefix argument, prompt for each parameter."
958 (interactive (list (hg-read-file-name " to log")
958 (interactive (list (hg-read-file-name " to log")
959 (hg-read-rev " to start with" "-1")
959 (hg-read-rev " to start with" "-1")
960 (hg-read-rev " to end with" (format "-%d" hg-log-limit))))
960 (hg-read-rev " to end with" (format "-%d" hg-log-limit))))
961 (let ((a-path (hg-abbrev-file-name path))
961 (let ((a-path (hg-abbrev-file-name path))
962 (r1 (or rev1 (format "-%d" hg-log-limit)))
962 (r1 (or rev1 (format "-%d" hg-log-limit)))
963 (r2 (or rev2 rev1 "-1")))
963 (r2 (or rev2 rev1 "-1")))
964 (hg-view-output ((if (equal r1 r2)
964 (hg-view-output ((if (equal r1 r2)
965 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
965 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
966 (format "Mercurial: Log from rev %s to %s of %s"
966 (format "Mercurial: Log from rev %s to %s of %s"
967 r1 r2 a-path)))
967 r1 r2 a-path)))
968 (let ((revs (format "%s:%s" r1 r2)))
968 (let ((revs (format "%s:%s" r1 r2)))
969 (if (> (length path) (length (hg-root path)))
969 (if (> (length path) (length (hg-root path)))
970 (call-process (hg-binary) nil t nil "log" "-r" revs path)
970 (call-process (hg-binary) nil t nil "log" "-r" revs path)
971 (call-process (hg-binary) nil t nil "log" "-r" revs)))
971 (call-process (hg-binary) nil t nil "log" "-r" revs)))
972 (hg-log-mode))))
972 (hg-log-mode))))
973
973
974 (defun hg-log-repo (path &optional rev1 rev2)
974 (defun hg-log-repo (path &optional rev1 rev2)
975 "Display the revision history of the repository containing PATH.
975 "Display the revision history of the repository containing PATH.
976 History is displayed between REV1, which defaults to the tip, and
976 History is displayed between REV1, which defaults to the tip, and
977 REV2, which defaults to the initial revision.
977 REV2, which defaults to the initial revision.
978 Variable hg-log-limit controls the number of log entries displayed."
978 Variable hg-log-limit controls the number of log entries displayed."
979 (interactive (list (hg-read-file-name " to log")
979 (interactive (list (hg-read-file-name " to log")
980 (hg-read-rev " to start with" "tip")
980 (hg-read-rev " to start with" "tip")
981 (hg-read-rev " to end with" (format "-%d" hg-log-limit))))
981 (hg-read-rev " to end with" (format "-%d" hg-log-limit))))
982 (hg-log (hg-root path) rev1 rev2))
982 (hg-log (hg-root path) rev1 rev2))
983
983
984 (defun hg-outgoing (&optional repo)
984 (defun hg-outgoing (&optional repo)
985 "Display changesets present locally that are not present in REPO."
985 "Display changesets present locally that are not present in REPO."
986 (interactive (list (hg-read-repo-name " where changes would go to" nil
986 (interactive (list (hg-read-repo-name " where changes would go to" nil
987 hg-outgoing-repository)))
987 hg-outgoing-repository)))
988 (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
988 (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
989 (hg-abbrev-file-name (hg-root))
989 (hg-abbrev-file-name (hg-root))
990 (hg-abbrev-file-name
990 (hg-abbrev-file-name
991 (or repo hg-outgoing-repository))))
991 (or repo hg-outgoing-repository))))
992 (call-process (hg-binary) nil t nil "outgoing"
992 (call-process (hg-binary) nil t nil "outgoing"
993 (or repo hg-outgoing-repository))
993 (or repo hg-outgoing-repository))
994 (hg-log-mode)))
994 (hg-log-mode)))
995
995
996 (defun hg-pull (&optional repo)
996 (defun hg-pull (&optional repo)
997 "Pull changes from repository REPO.
997 "Pull changes from repository REPO.
998 This does not update the working directory."
998 This does not update the working directory."
999 (interactive (list (hg-read-repo-name " to pull from")))
999 (interactive (list (hg-read-repo-name " to pull from")))
1000 (hg-view-output ((format "Mercurial: Pull to %s from %s"
1000 (hg-view-output ((format "Mercurial: Pull to %s from %s"
1001 (hg-abbrev-file-name (hg-root))
1001 (hg-abbrev-file-name (hg-root))
1002 (hg-abbrev-file-name
1002 (hg-abbrev-file-name
1003 (or repo hg-incoming-repository))))
1003 (or repo hg-incoming-repository))))
1004 (call-process (hg-binary) nil t nil "pull"
1004 (call-process (hg-binary) nil t nil "pull"
1005 (or repo hg-incoming-repository))))
1005 (or repo hg-incoming-repository))))
1006
1006
1007 (defun hg-push (&optional repo)
1007 (defun hg-push (&optional repo)
1008 "Push changes to repository REPO."
1008 "Push changes to repository REPO."
1009 (interactive (list (hg-read-repo-name " to push to")))
1009 (interactive (list (hg-read-repo-name " to push to")))
1010 (hg-view-output ((format "Mercurial: Push from %s to %s"
1010 (hg-view-output ((format "Mercurial: Push from %s to %s"
1011 (hg-abbrev-file-name (hg-root))
1011 (hg-abbrev-file-name (hg-root))
1012 (hg-abbrev-file-name
1012 (hg-abbrev-file-name
1013 (or repo hg-outgoing-repository))))
1013 (or repo hg-outgoing-repository))))
1014 (call-process (hg-binary) nil t nil "push"
1014 (call-process (hg-binary) nil t nil "push"
1015 (or repo hg-outgoing-repository))))
1015 (or repo hg-outgoing-repository))))
1016
1016
1017 (defun hg-revert-buffer-internal ()
1017 (defun hg-revert-buffer-internal ()
1018 (let ((ctx (hg-buffer-context)))
1018 (let ((ctx (hg-buffer-context)))
1019 (message "Reverting %s..." buffer-file-name)
1019 (message "Reverting %s..." buffer-file-name)
1020 (hg-run0 "revert" buffer-file-name)
1020 (hg-run0 "revert" buffer-file-name)
1021 (revert-buffer t t t)
1021 (revert-buffer t t t)
1022 (hg-restore-context ctx)
1022 (hg-restore-context ctx)
1023 (hg-mode-line)
1023 (hg-mode-line)
1024 (message "Reverting %s...done" buffer-file-name)))
1024 (message "Reverting %s...done" buffer-file-name)))
1025
1025
1026 (defun hg-revert-buffer ()
1026 (defun hg-revert-buffer ()
1027 "Revert current buffer's file back to the latest committed version.
1027 "Revert current buffer's file back to the latest committed version.
1028 If the file has not changed, nothing happens. Otherwise, this
1028 If the file has not changed, nothing happens. Otherwise, this
1029 displays a diff and asks for confirmation before reverting."
1029 displays a diff and asks for confirmation before reverting."
1030 (interactive)
1030 (interactive)
1031 (let ((vc-suppress-confirm nil)
1031 (let ((vc-suppress-confirm nil)
1032 (obuf (current-buffer))
1032 (obuf (current-buffer))
1033 diff)
1033 diff)
1034 (vc-buffer-sync)
1034 (vc-buffer-sync)
1035 (unwind-protect
1035 (unwind-protect
1036 (setq diff (hg-diff buffer-file-name))
1036 (setq diff (hg-diff buffer-file-name))
1037 (when diff
1037 (when diff
1038 (unless (yes-or-no-p "Discard changes? ")
1038 (unless (yes-or-no-p "Discard changes? ")
1039 (error "Revert cancelled")))
1039 (error "Revert cancelled")))
1040 (when diff
1040 (when diff
1041 (let ((buf (current-buffer)))
1041 (let ((buf (current-buffer)))
1042 (delete-window (selected-window))
1042 (delete-window (selected-window))
1043 (kill-buffer buf))))
1043 (kill-buffer buf))))
1044 (set-buffer obuf)
1044 (set-buffer obuf)
1045 (when diff
1045 (when diff
1046 (hg-revert-buffer-internal))))
1046 (hg-revert-buffer-internal))))
1047
1047
1048 (defun hg-root (&optional path)
1048 (defun hg-root (&optional path)
1049 "Return the root of the repository that contains the given path.
1049 "Return the root of the repository that contains the given path.
1050 If the path is outside a repository, return nil.
1050 If the path is outside a repository, return nil.
1051 When called interactively, the root is printed. A prefix argument
1051 When called interactively, the root is printed. A prefix argument
1052 prompts for a path to check."
1052 prompts for a path to check."
1053 (interactive (list (hg-read-file-name)))
1053 (interactive (list (hg-read-file-name)))
1054 (if (or path (not hg-root))
1054 (if (or path (not hg-root))
1055 (let ((root (do ((prev nil dir)
1055 (let ((root (do ((prev nil dir)
1056 (dir (file-name-directory (or path buffer-file-name ""))
1056 (dir (file-name-directory (or path buffer-file-name ""))
1057 (file-name-directory (directory-file-name dir))))
1057 (file-name-directory (directory-file-name dir))))
1058 ((equal prev dir))
1058 ((equal prev dir))
1059 (when (file-directory-p (concat dir ".hg"))
1059 (when (file-directory-p (concat dir ".hg"))
1060 (return dir)))))
1060 (return dir)))))
1061 (when (interactive-p)
1061 (when (interactive-p)
1062 (if root
1062 (if root
1063 (message "The root of this repository is `%s'." root)
1063 (message "The root of this repository is `%s'." root)
1064 (message "The path `%s' is not in a Mercurial repository."
1064 (message "The path `%s' is not in a Mercurial repository."
1065 (hg-abbrev-file-name path))))
1065 (hg-abbrev-file-name path))))
1066 root)
1066 root)
1067 hg-root))
1067 hg-root))
1068
1068
1069 (defun hg-status (path)
1069 (defun hg-status (path)
1070 "Print revision control status of a file or directory.
1070 "Print revision control status of a file or directory.
1071 With prefix argument, prompt for the path to give status for.
1071 With prefix argument, prompt for the path to give status for.
1072 Names are displayed relative to the repository root."
1072 Names are displayed relative to the repository root."
1073 (interactive (list (hg-read-file-name " for status" (hg-root))))
1073 (interactive (list (hg-read-file-name " for status" (hg-root))))
1074 (let ((root (hg-root)))
1074 (let ((root (hg-root)))
1075 (hg-view-output ((format "Mercurial: Status of %s in %s"
1075 (hg-view-output ((format "Mercurial: Status of %s in %s"
1076 (let ((name (substring (expand-file-name path)
1076 (let ((name (substring (expand-file-name path)
1077 (length root))))
1077 (length root))))
1078 (if (> (length name) 0)
1078 (if (> (length name) 0)
1079 name
1079 name
1080 "*"))
1080 "*"))
1081 (hg-abbrev-file-name root)))
1081 (hg-abbrev-file-name root)))
1082 (apply 'call-process (hg-binary) nil t nil
1082 (apply 'call-process (hg-binary) nil t nil
1083 (list "--cwd" root "status" path)))))
1083 (list "--cwd" root "status" path)))))
1084
1084
1085 (defun hg-undo ()
1085 (defun hg-undo ()
1086 (interactive)
1086 (interactive)
1087 (error "not implemented"))
1087 (error "not implemented"))
1088
1088
1089 (defun hg-update ()
1089 (defun hg-update ()
1090 (interactive)
1090 (interactive)
1091 (error "not implemented"))
1091 (error "not implemented"))
1092
1092
1093 (defun hg-version-other-window ()
1093 (defun hg-version-other-window ()
1094 (interactive)
1094 (interactive)
1095 (error "not implemented"))
1095 (error "not implemented"))
1096
1096
1097
1097
1098 (provide 'mercurial)
1098 (provide 'mercurial)
1099
1099
1100
1100
1101 ;;; Local Variables:
1101 ;;; Local Variables:
1102 ;;; prompt-to-byte-compile: nil
1102 ;;; prompt-to-byte-compile: nil
1103 ;;; end:
1103 ;;; end:
@@ -1,42 +1,42 b''
1 #
1 #
2 # tcsh completion for Mercurial
2 # tcsh completion for Mercurial
3 #
3 #
4 # This file has been auto-generated by tcsh_completion_build.sh for
4 # This file has been auto-generated by tcsh_completion_build.sh for
5 # Mercurial Distributed SCM (version 325c07fd2ebd)
5 # Mercurial Distributed SCM (version 325c07fd2ebd)
6 #
6 #
7 # Copyright (C) 2005 TK Soh.
7 # Copyright (C) 2005 TK Soh.
8 #
8 #
9 # This is free software; you can redistribute it and/or modify it under
9 # This is free software; you can redistribute it and/or modify it under
10 # the terms of the GNU General Public License as published by the Free
10 # the terms of the GNU General Public License as published by the Free
11 # Software Foundation; either version 2 of the License, or (at your
11 # Software Foundation; either version 2 of the License, or (at your
12 # option) any later version.
12 # option) any later version.
13 #
13 #
14
14
15 complete hg \
15 complete hg \
16 'n/--cwd/d/' 'n/-R/d/' 'n/--repository/d/' \
16 'n/--cwd/d/' 'n/-R/d/' 'n/--repository/d/' \
17 'C/-/( -R --repository \
17 'C/-/( -R --repository \
18 --cwd \
18 --cwd \
19 -y --noninteractive \
19 -y --noninteractive \
20 -q --quiet \
20 -q --quiet \
21 -v --verbose \
21 -v --verbose \
22 --debug \
22 --debug \
23 --debugger \
23 --debugger \
24 --traceback \
24 --traceback \
25 --time \
25 --time \
26 --profile \
26 --profile \
27 --version \
27 --version \
28 -h --help)/' \
28 -h --help)/' \
29 'p/1/(add addremove annotate bundle cat \
29 'p/1/(add addremove annotate bundle cat \
30 clone commit ci copy cp \
30 clone commit ci copy cp \
31 debugancestor debugcheckstate debugconfig debugdata debugindex \
31 debugancestor debugcheckstate debugconfig debugdata debugindex \
32 debugindexdot debugrename debugstate debugwalk diff \
32 debugindexdot debugrename debugstate debugwalk diff \
33 export forget grep heads help \
33 export forget grep heads help \
34 identify id import patch incoming \
34 identify id import patch incoming \
35 in init locate log history \
35 in init locate log history \
36 manifest outgoing out parents paths \
36 manifest outgoing out parents paths \
37 pull push rawcommit recover remove \
37 pull push rawcommit recover remove \
38 rm rename mv revert root \
38 rm rename mv revert root \
39 serve status tag tags tip \
39 serve status tag tags tip \
40 unbundle undo update up checkout \
40 unbundle undo update up checkout \
41 co verify version)/'
41 co verify version)/'
42
42
@@ -1,73 +1,73 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 #
3 #
4 # tcsh_completion_build.sh - script to generate tcsh completion
4 # tcsh_completion_build.sh - script to generate tcsh completion
5 #
5 #
6 #
6 #
7 # Copyright (C) 2005 TK Soh.
7 # Copyright (C) 2005 TK Soh.
8 #
8 #
9 # This is free software; you can redistribute it and/or modify it under
9 # This is free software; you can redistribute it and/or modify it under
10 # the terms of the GNU General Public License as published by the Free
10 # the terms of the GNU General Public License as published by the Free
11 # Software Foundation; either version 2 of the License, or (at your
11 # Software Foundation; either version 2 of the License, or (at your
12 # option) any later version.
12 # option) any later version.
13 #
13 #
14 #
14 #
15 # Description
15 # Description
16 # -----------
16 # -----------
17 # This script generates a tcsh source file to support completion
17 # This script generates a tcsh source file to support completion
18 # of Mercurial commands and options.
18 # of Mercurial commands and options.
19 #
19 #
20 # Instruction:
20 # Instruction:
21 # -----------
21 # -----------
22 # Run this script to generate the tcsh source file, and source
22 # Run this script to generate the tcsh source file, and source
23 # the file to add command completion support for Mercurial.
23 # the file to add command completion support for Mercurial.
24 #
24 #
25 # tcsh% tcsh_completion.sh FILE
25 # tcsh% tcsh_completion.sh FILE
26 # tcsh% source FILE
26 # tcsh% source FILE
27 #
27 #
28 # If FILE is not specified, tcsh_completion will be generated.
28 # If FILE is not specified, tcsh_completion will be generated.
29 #
29 #
30 # Bugs:
30 # Bugs:
31 # ----
31 # ----
32 # 1. command specific options are not supported
32 # 1. command specific options are not supported
33 # 2. hg commands must be specified immediately after 'hg'.
33 # 2. hg commands must be specified immediately after 'hg'.
34 #
34 #
35
35
36 tcsh_file=${1-tcsh_completion}
36 tcsh_file=${1-tcsh_completion}
37
37
38 hg_commands=`hg --debug help | \
38 hg_commands=`hg --debug help | \
39 sed -e '1,/^list of commands:/d' \
39 sed -e '1,/^list of commands:/d' \
40 -e '/^global options:/,$d' \
40 -e '/^global options:/,$d' \
41 -e '/^ [^ ]/!d; s/[,:]//g;' | \
41 -e '/^ [^ ]/!d; s/[,:]//g;' | \
42 xargs -n5 | \
42 xargs -n5 | \
43 sed -e '$!s/$/ \\\\/g; 2,$s/^ */ /g'`
43 sed -e '$!s/$/ \\\\/g; 2,$s/^ */ /g'`
44
44
45 hg_global_options=`hg -v help | \
45 hg_global_options=`hg -v help | \
46 sed -e '1,/global/d;/^ *-/!d; s/ [^- ].*//' | \
46 sed -e '1,/global/d;/^ *-/!d; s/ [^- ].*//' | \
47 sed -e 's/ *$//; $!s/$/ \\\\/g; 2,$s/^ */ /g'`
47 sed -e 's/ *$//; $!s/$/ \\\\/g; 2,$s/^ */ /g'`
48
48
49 hg_version=`hg version | sed -e '1q'`
49 hg_version=`hg version | sed -e '1q'`
50
50
51 script_name=`basename $0`
51 script_name=`basename $0`
52
52
53 cat > $tcsh_file <<END
53 cat > $tcsh_file <<END
54 #
54 #
55 # tcsh completion for Mercurial
55 # tcsh completion for Mercurial
56 #
56 #
57 # This file has been auto-generated by $script_name for
57 # This file has been auto-generated by $script_name for
58 # $hg_version
58 # $hg_version
59 #
59 #
60 # Copyright (C) 2005 TK Soh.
60 # Copyright (C) 2005 TK Soh.
61 #
61 #
62 # This is free software; you can redistribute it and/or modify it under
62 # This is free software; you can redistribute it and/or modify it under
63 # the terms of the GNU General Public License as published by the Free
63 # the terms of the GNU General Public License as published by the Free
64 # Software Foundation; either version 2 of the License, or (at your
64 # Software Foundation; either version 2 of the License, or (at your
65 # option) any later version.
65 # option) any later version.
66 #
66 #
67
67
68 complete hg \\
68 complete hg \\
69 'n/--cwd/d/' 'n/-R/d/' 'n/--repository/d/' \\
69 'n/--cwd/d/' 'n/-R/d/' 'n/--repository/d/' \\
70 'C/-/($hg_global_options)/' \\
70 'C/-/($hg_global_options)/' \\
71 'p/1/($hg_commands)/'
71 'p/1/($hg_commands)/'
72
72
73 END
73 END
@@ -1,807 +1,807 b''
1 HG(1)
1 HG(1)
2 =====
2 =====
3 Matt Mackall <mpm@selenic.com>
3 Matt Mackall <mpm@selenic.com>
4
4
5 NAME
5 NAME
6 ----
6 ----
7 hg - Mercurial source code management system
7 hg - Mercurial source code management system
8
8
9 SYNOPSIS
9 SYNOPSIS
10 --------
10 --------
11 'hg' [-v -d -q -y] <command> [command options] [files]
11 'hg' [-v -d -q -y] <command> [command options] [files]
12
12
13 DESCRIPTION
13 DESCRIPTION
14 -----------
14 -----------
15 The hg(1) command provides a command line interface to the Mercurial system.
15 The hg(1) command provides a command line interface to the Mercurial system.
16
16
17 OPTIONS
17 OPTIONS
18 -------
18 -------
19
19
20 -R, --repository::
20 -R, --repository::
21 repository root directory
21 repository root directory
22
22
23 --cwd::
23 --cwd::
24 change working directory
24 change working directory
25
25
26 -y, --noninteractive::
26 -y, --noninteractive::
27 do not prompt, assume 'yes' for any required answers
27 do not prompt, assume 'yes' for any required answers
28
28
29 -q, --quiet::
29 -q, --quiet::
30 suppress output
30 suppress output
31
31
32 -v, --verbose::
32 -v, --verbose::
33 enable additional output
33 enable additional output
34
34
35 --debug::
35 --debug::
36 enable debugging output
36 enable debugging output
37
37
38 --traceback::
38 --traceback::
39 print traceback on exception
39 print traceback on exception
40
40
41 --time::
41 --time::
42 time how long the command takes
42 time how long the command takes
43
43
44 --profile::
44 --profile::
45 print command execution profile
45 print command execution profile
46
46
47 --version::
47 --version::
48 output version information and exit
48 output version information and exit
49
49
50 -h, --help::
50 -h, --help::
51 display help and exit
51 display help and exit
52
52
53 COMMAND ELEMENTS
53 COMMAND ELEMENTS
54 ----------------
54 ----------------
55
55
56 files ...::
56 files ...::
57 indicates one or more filename or relative path filenames; see
57 indicates one or more filename or relative path filenames; see
58 "FILE NAME PATTERNS" for information on pattern matching
58 "FILE NAME PATTERNS" for information on pattern matching
59
59
60 path::
60 path::
61 indicates a path on the local machine
61 indicates a path on the local machine
62
62
63 revision::
63 revision::
64 indicates a changeset which can be specified as a changeset revision
64 indicates a changeset which can be specified as a changeset revision
65 number, a tag, or a unique substring of the changeset hash value
65 number, a tag, or a unique substring of the changeset hash value
66
66
67 repository path::
67 repository path::
68 either the pathname of a local repository or the URI of a remote
68 either the pathname of a local repository or the URI of a remote
69 repository. There are two available URI protocols, http:// which is
69 repository. There are two available URI protocols, http:// which is
70 fast and the old-http:// protocol which is much slower but does not
70 fast and the old-http:// protocol which is much slower but does not
71 require a special server on the web host.
71 require a special server on the web host.
72
72
73 COMMANDS
73 COMMANDS
74 --------
74 --------
75
75
76 add [options] [files ...]::
76 add [options] [files ...]::
77 Schedule files to be version controlled and added to the repository.
77 Schedule files to be version controlled and added to the repository.
78
78
79 The files will be added to the repository at the next commit.
79 The files will be added to the repository at the next commit.
80
80
81 If no names are given, add all files in the current directory and
81 If no names are given, add all files in the current directory and
82 its subdirectories.
82 its subdirectories.
83
83
84 addremove [options] [files ...]::
84 addremove [options] [files ...]::
85 Add all new files and remove all missing files from the repository.
85 Add all new files and remove all missing files from the repository.
86
86
87 New files are ignored if they match any of the patterns in .hgignore. As
87 New files are ignored if they match any of the patterns in .hgignore. As
88 with add, these changes take effect at the next commit.
88 with add, these changes take effect at the next commit.
89
89
90 annotate [-r <rev> -u -n -c] [files ...]::
90 annotate [-r <rev> -u -n -c] [files ...]::
91 List changes in files, showing the revision id responsible for each line
91 List changes in files, showing the revision id responsible for each line
92
92
93 This command is useful to discover who did a change or when a change took
93 This command is useful to discover who did a change or when a change took
94 place.
94 place.
95
95
96 Without the -a option, annotate will avoid processing files it
96 Without the -a option, annotate will avoid processing files it
97 detects as binary. With -a, annotate will generate an annotation
97 detects as binary. With -a, annotate will generate an annotation
98 anyway, probably with undesirable results.
98 anyway, probably with undesirable results.
99
99
100 options:
100 options:
101 -a, --text treat all files as text
101 -a, --text treat all files as text
102 -I, --include <pat> include names matching the given patterns
102 -I, --include <pat> include names matching the given patterns
103 -X, --exclude <pat> exclude names matching the given patterns
103 -X, --exclude <pat> exclude names matching the given patterns
104 -r, --revision <rev> annotate the specified revision
104 -r, --revision <rev> annotate the specified revision
105 -u, --user list the author
105 -u, --user list the author
106 -c, --changeset list the changeset
106 -c, --changeset list the changeset
107 -n, --number list the revision number (default)
107 -n, --number list the revision number (default)
108
108
109 bundle <file> <other>::
109 bundle <file> <other>::
110 (EXPERIMENTAL)
110 (EXPERIMENTAL)
111
111
112 Generate a compressed changegroup file collecting all changesets
112 Generate a compressed changegroup file collecting all changesets
113 not found in the other repository.
113 not found in the other repository.
114
114
115 This file can then be transferred using conventional means and
115 This file can then be transferred using conventional means and
116 applied to another repository with the unbundle command. This is
116 applied to another repository with the unbundle command. This is
117 useful when native push and pull are not available or when
117 useful when native push and pull are not available or when
118 exporting an entire repository is undesirable. The standard file
118 exporting an entire repository is undesirable. The standard file
119 extension is ".hg".
119 extension is ".hg".
120
120
121 Unlike import/export, this exactly preserves all changeset
121 Unlike import/export, this exactly preserves all changeset
122 contents including permissions, rename data, and revision history.
122 contents including permissions, rename data, and revision history.
123
123
124 cat [options] <file ...>::
124 cat [options] <file ...>::
125 Print the specified files as they were at the given revision.
125 Print the specified files as they were at the given revision.
126 If no revision is given then the tip is used.
126 If no revision is given then the tip is used.
127
127
128 Output may be to a file, in which case the name of the file is
128 Output may be to a file, in which case the name of the file is
129 given using a format string. The formatting rules are the same as
129 given using a format string. The formatting rules are the same as
130 for the export command, with the following additions:
130 for the export command, with the following additions:
131
131
132 %s basename of file being printed
132 %s basename of file being printed
133 %d dirname of file being printed, or '.' if in repo root
133 %d dirname of file being printed, or '.' if in repo root
134 %p root-relative path name of file being printed
134 %p root-relative path name of file being printed
135
135
136 options:
136 options:
137 -I, --include <pat> include names matching the given patterns
137 -I, --include <pat> include names matching the given patterns
138 -X, --exclude <pat> exclude names matching the given patterns
138 -X, --exclude <pat> exclude names matching the given patterns
139 -o, --output <filespec> print output to file with formatted name
139 -o, --output <filespec> print output to file with formatted name
140 -r, --rev <rev> print the given revision
140 -r, --rev <rev> print the given revision
141
141
142 clone [options] <source> [dest]::
142 clone [options] <source> [dest]::
143 Create a copy of an existing repository in a new directory.
143 Create a copy of an existing repository in a new directory.
144
144
145 If no destination directory name is specified, it defaults to the
145 If no destination directory name is specified, it defaults to the
146 basename of the source.
146 basename of the source.
147
147
148 The location of the source is added to the new repository's
148 The location of the source is added to the new repository's
149 .hg/hgrc file, as the default to be used for future pulls.
149 .hg/hgrc file, as the default to be used for future pulls.
150
150
151 For efficiency, hardlinks are used for cloning whenever the source
151 For efficiency, hardlinks are used for cloning whenever the source
152 and destination are on the same filesystem. Some filesystems,
152 and destination are on the same filesystem. Some filesystems,
153 such as AFS, implement hardlinking incorrectly, but do not report
153 such as AFS, implement hardlinking incorrectly, but do not report
154 errors. In these cases, use the --pull option to avoid
154 errors. In these cases, use the --pull option to avoid
155 hardlinking.
155 hardlinking.
156
156
157 options:
157 options:
158 -U, --noupdate do not update the new working directory
158 -U, --noupdate do not update the new working directory
159 --pull use pull protocol to copy metadata
159 --pull use pull protocol to copy metadata
160 -e, --ssh specify ssh command to use
160 -e, --ssh specify ssh command to use
161 --remotecmd specify hg command to run on the remote side
161 --remotecmd specify hg command to run on the remote side
162
162
163 commit [options] [files...]::
163 commit [options] [files...]::
164 Commit changes to the given files into the repository.
164 Commit changes to the given files into the repository.
165
165
166 If a list of files is omitted, all changes reported by "hg status"
166 If a list of files is omitted, all changes reported by "hg status"
167 from the root of the repository will be commited.
167 from the root of the repository will be commited.
168
168
169 The HGEDITOR or EDITOR environment variables are used to start an
169 The HGEDITOR or EDITOR environment variables are used to start an
170 editor to add a commit comment.
170 editor to add a commit comment.
171
171
172 Options:
172 Options:
173
173
174 -A, --addremove run addremove during commit
174 -A, --addremove run addremove during commit
175 -I, --include <pat> include names matching the given patterns
175 -I, --include <pat> include names matching the given patterns
176 -X, --exclude <pat> exclude names matching the given patterns
176 -X, --exclude <pat> exclude names matching the given patterns
177 -m, --message <text> use <text> as commit message
177 -m, --message <text> use <text> as commit message
178 -l, --logfile <file> read the commit message from <file>
178 -l, --logfile <file> read the commit message from <file>
179 -d, --date <datecode> record datecode as commit date
179 -d, --date <datecode> record datecode as commit date
180 -u, --user <user> record user as commiter
180 -u, --user <user> record user as commiter
181
181
182 aliases: ci
182 aliases: ci
183
183
184 copy <source ...> <dest>::
184 copy <source ...> <dest>::
185 Mark dest as having copies of source files. If dest is a
185 Mark dest as having copies of source files. If dest is a
186 directory, copies are put in that directory. If dest is a file,
186 directory, copies are put in that directory. If dest is a file,
187 there can only be one source.
187 there can only be one source.
188
188
189 By default, this command copies the contents of files as they
189 By default, this command copies the contents of files as they
190 stand in the working directory. If invoked with --after, the
190 stand in the working directory. If invoked with --after, the
191 operation is recorded, but no copying is performed.
191 operation is recorded, but no copying is performed.
192
192
193 This command takes effect in the next commit.
193 This command takes effect in the next commit.
194
194
195 NOTE: This command should be treated as experimental. While it
195 NOTE: This command should be treated as experimental. While it
196 should properly record copied files, this information is not yet
196 should properly record copied files, this information is not yet
197 fully used by merge, nor fully reported by log.
197 fully used by merge, nor fully reported by log.
198
198
199 Options:
199 Options:
200 -A, --after record a copy that has already occurred
200 -A, --after record a copy that has already occurred
201 -I, --include <pat> include names matching the given patterns
201 -I, --include <pat> include names matching the given patterns
202 -X, --exclude <pat> exclude names matching the given patterns
202 -X, --exclude <pat> exclude names matching the given patterns
203 -f, --force forcibly copy over an existing managed file
203 -f, --force forcibly copy over an existing managed file
204 -p, --parents append source path to dest
204 -p, --parents append source path to dest
205
205
206 aliases: cp
206 aliases: cp
207
207
208 diff [-a] [-r revision] [-r revision] [files ...]::
208 diff [-a] [-r revision] [-r revision] [files ...]::
209 Show differences between revisions for the specified files.
209 Show differences between revisions for the specified files.
210
210
211 Differences between files are shown using the unified diff format.
211 Differences between files are shown using the unified diff format.
212
212
213 When two revision arguments are given, then changes are shown
213 When two revision arguments are given, then changes are shown
214 between those revisions. If only one revision is specified then
214 between those revisions. If only one revision is specified then
215 that revision is compared to the working directory, and, when no
215 that revision is compared to the working directory, and, when no
216 revisions are specified, the working directory files are compared
216 revisions are specified, the working directory files are compared
217 to its parent.
217 to its parent.
218
218
219 Without the -a option, diff will avoid generating diffs of files
219 Without the -a option, diff will avoid generating diffs of files
220 it detects as binary. With -a, diff will generate a diff anyway,
220 it detects as binary. With -a, diff will generate a diff anyway,
221 probably with undesirable results.
221 probably with undesirable results.
222
222
223 options:
223 options:
224 -a, --text treat all files as text
224 -a, --text treat all files as text
225 -I, --include <pat> include names matching the given patterns
225 -I, --include <pat> include names matching the given patterns
226 -X, --exclude <pat> exclude names matching the given patterns
226 -X, --exclude <pat> exclude names matching the given patterns
227
227
228 export [-o filespec] [revision] ...::
228 export [-o filespec] [revision] ...::
229 Print the changeset header and diffs for one or more revisions.
229 Print the changeset header and diffs for one or more revisions.
230
230
231 The information shown in the changeset header is: author,
231 The information shown in the changeset header is: author,
232 changeset hash, parent and commit comment.
232 changeset hash, parent and commit comment.
233
233
234 Output may be to a file, in which case the name of the file is
234 Output may be to a file, in which case the name of the file is
235 given using a format string. The formatting rules are as follows:
235 given using a format string. The formatting rules are as follows:
236
236
237 %% literal "%" character
237 %% literal "%" character
238 %H changeset hash (40 bytes of hexadecimal)
238 %H changeset hash (40 bytes of hexadecimal)
239 %N number of patches being generated
239 %N number of patches being generated
240 %R changeset revision number
240 %R changeset revision number
241 %b basename of the exporting repository
241 %b basename of the exporting repository
242 %h short-form changeset hash (12 bytes of hexadecimal)
242 %h short-form changeset hash (12 bytes of hexadecimal)
243 %n zero-padded sequence number, starting at 1
243 %n zero-padded sequence number, starting at 1
244 %r zero-padded changeset revision number
244 %r zero-padded changeset revision number
245
245
246 Without the -a option, export will avoid generating diffs of files
246 Without the -a option, export will avoid generating diffs of files
247 it detects as binary. With -a, export will generate a diff anyway,
247 it detects as binary. With -a, export will generate a diff anyway,
248 probably with undesirable results.
248 probably with undesirable results.
249
249
250 options:
250 options:
251 -a, --text treat all files as text
251 -a, --text treat all files as text
252 -o, --output <filespec> print output to file with formatted name
252 -o, --output <filespec> print output to file with formatted name
253
253
254 forget [options] [files]::
254 forget [options] [files]::
255 Undo an 'hg add' scheduled for the next commit.
255 Undo an 'hg add' scheduled for the next commit.
256
256
257 options:
257 options:
258 -I, --include <pat> include names matching the given patterns
258 -I, --include <pat> include names matching the given patterns
259 -X, --exclude <pat> exclude names matching the given patterns
259 -X, --exclude <pat> exclude names matching the given patterns
260
260
261 grep [options] pattern [files]::
261 grep [options] pattern [files]::
262 Search revisions of files for a regular expression.
262 Search revisions of files for a regular expression.
263
263
264 This command behaves differently than Unix grep. It only accepts
264 This command behaves differently than Unix grep. It only accepts
265 Python/Perl regexps. It searches repository history, not the
265 Python/Perl regexps. It searches repository history, not the
266 working directory. It always prints the revision number in which
266 working directory. It always prints the revision number in which
267 a match appears.
267 a match appears.
268
268
269 By default, grep only prints output for the first revision of a
269 By default, grep only prints output for the first revision of a
270 file in which it finds a match. To get it to print every revision
270 file in which it finds a match. To get it to print every revision
271 that contains a change in match status ("-" for a match that
271 that contains a change in match status ("-" for a match that
272 becomes a non-match, or "+" for a non-match that becomes a match),
272 becomes a non-match, or "+" for a non-match that becomes a match),
273 use the --all flag.
273 use the --all flag.
274
274
275 options:
275 options:
276 -0, --print0 end fields with NUL
276 -0, --print0 end fields with NUL
277 -I, --include <pat> include names matching the given patterns
277 -I, --include <pat> include names matching the given patterns
278 -X, --exclude <pat> exclude names matching the given patterns
278 -X, --exclude <pat> exclude names matching the given patterns
279 --all print all revisions that match
279 --all print all revisions that match
280 -i, --ignore-case ignore case when matching
280 -i, --ignore-case ignore case when matching
281 -l, --files-with-matches print only filenames and revs that match
281 -l, --files-with-matches print only filenames and revs that match
282 -n, --line-number print matching line numbers
282 -n, --line-number print matching line numbers
283 -r <rev>, --rev <rev> search in given revision range
283 -r <rev>, --rev <rev> search in given revision range
284 -u, --user print user who committed change
284 -u, --user print user who committed change
285
285
286 heads::
286 heads::
287 Show all repository head changesets.
287 Show all repository head changesets.
288
288
289 Repository "heads" are changesets that don't have children
289 Repository "heads" are changesets that don't have children
290 changesets. They are where development generally takes place and
290 changesets. They are where development generally takes place and
291 are the usual targets for update and merge operations.
291 are the usual targets for update and merge operations.
292
292
293 identify::
293 identify::
294 Print a short summary of the current state of the repo.
294 Print a short summary of the current state of the repo.
295
295
296 This summary identifies the repository state using one or two parent
296 This summary identifies the repository state using one or two parent
297 hash identifiers, followed by a "+" if there are uncommitted changes
297 hash identifiers, followed by a "+" if there are uncommitted changes
298 in the working directory, followed by a list of tags for this revision.
298 in the working directory, followed by a list of tags for this revision.
299
299
300 aliases: id
300 aliases: id
301
301
302 import [-p <n> -b <base> -f] <patches>::
302 import [-p <n> -b <base> -f] <patches>::
303 Import a list of patches and commit them individually.
303 Import a list of patches and commit them individually.
304
304
305 If there are outstanding changes in the working directory, import
305 If there are outstanding changes in the working directory, import
306 will abort unless given the -f flag.
306 will abort unless given the -f flag.
307
307
308 If a patch looks like a mail message (its first line starts with
308 If a patch looks like a mail message (its first line starts with
309 "From " or looks like an RFC822 header), it will not be applied
309 "From " or looks like an RFC822 header), it will not be applied
310 unless the -f option is used. The importer neither parses nor
310 unless the -f option is used. The importer neither parses nor
311 discards mail headers, so use -f only to override the "mailness"
311 discards mail headers, so use -f only to override the "mailness"
312 safety check, not to import a real mail message.
312 safety check, not to import a real mail message.
313
313
314 options:
314 options:
315 -p, --strip <n> directory strip option for patch. This has the same
315 -p, --strip <n> directory strip option for patch. This has the same
316 meaning as the corresponding patch option
316 meaning as the corresponding patch option
317 -b <path> base directory to read patches from
317 -b <path> base directory to read patches from
318 -f, --force skip check for outstanding uncommitted changes
318 -f, --force skip check for outstanding uncommitted changes
319
319
320 aliases: patch
320 aliases: patch
321
321
322 incoming [-p] [source]::
322 incoming [-p] [source]::
323 Show new changesets found in the specified repo or the default
323 Show new changesets found in the specified repo or the default
324 pull repo. These are the changesets that would be pulled if a pull
324 pull repo. These are the changesets that would be pulled if a pull
325 was requested.
325 was requested.
326
326
327 Currently only local repositories are supported.
327 Currently only local repositories are supported.
328
328
329 options:
329 options:
330 -p, --patch show patch
330 -p, --patch show patch
331
331
332 aliases: in
332 aliases: in
333
333
334 init [dest]::
334 init [dest]::
335 Initialize a new repository in the given directory. If the given
335 Initialize a new repository in the given directory. If the given
336 directory does not exist, it is created.
336 directory does not exist, it is created.
337
337
338 If no directory is given, the current directory is used.
338 If no directory is given, the current directory is used.
339
339
340 locate [options] [files]::
340 locate [options] [files]::
341 Print all files under Mercurial control whose names match the
341 Print all files under Mercurial control whose names match the
342 given patterns.
342 given patterns.
343
343
344 This command searches the current directory and its
344 This command searches the current directory and its
345 subdirectories. To search an entire repository, move to the root
345 subdirectories. To search an entire repository, move to the root
346 of the repository.
346 of the repository.
347
347
348 If no patterns are given to match, this command prints all file
348 If no patterns are given to match, this command prints all file
349 names.
349 names.
350
350
351 If you want to feed the output of this command into the "xargs"
351 If you want to feed the output of this command into the "xargs"
352 command, use the "-0" option to both this command and "xargs".
352 command, use the "-0" option to both this command and "xargs".
353 This will avoid the problem of "xargs" treating single filenames
353 This will avoid the problem of "xargs" treating single filenames
354 that contain white space as multiple filenames.
354 that contain white space as multiple filenames.
355
355
356 options:
356 options:
357
357
358 -0, --print0 end filenames with NUL, for use with xargs
358 -0, --print0 end filenames with NUL, for use with xargs
359 -f, --fullpath print complete paths from the filesystem root
359 -f, --fullpath print complete paths from the filesystem root
360 -I, --include <pat> include names matching the given patterns
360 -I, --include <pat> include names matching the given patterns
361 -r, --rev <rev> search the repository as it stood at rev
361 -r, --rev <rev> search the repository as it stood at rev
362 -X, --exclude <pat> exclude names matching the given patterns
362 -X, --exclude <pat> exclude names matching the given patterns
363
363
364 log [-r revision ...] [-p] [files]::
364 log [-r revision ...] [-p] [files]::
365 Print the revision history of the specified files or the entire project.
365 Print the revision history of the specified files or the entire project.
366
366
367 By default this command outputs: changeset id and hash, tags,
367 By default this command outputs: changeset id and hash, tags,
368 parents, user, date and time, and a summary for each commit. The
368 parents, user, date and time, and a summary for each commit. The
369 -v switch adds some more detail, such as changed files, manifest
369 -v switch adds some more detail, such as changed files, manifest
370 hashes or message signatures.
370 hashes or message signatures.
371
371
372 options:
372 options:
373 -I, --include <pat> include names matching the given patterns
373 -I, --include <pat> include names matching the given patterns
374 -X, --exclude <pat> exclude names matching the given patterns
374 -X, --exclude <pat> exclude names matching the given patterns
375 -r, --rev <A> show the specified revision or range
375 -r, --rev <A> show the specified revision or range
376 -p, --patch show patch
376 -p, --patch show patch
377
377
378 aliases: history
378 aliases: history
379
379
380 manifest [revision]::
380 manifest [revision]::
381 Print a list of version controlled files for the given revision.
381 Print a list of version controlled files for the given revision.
382
382
383 The manifest is the list of files being version controlled. If no revision
383 The manifest is the list of files being version controlled. If no revision
384 is given then the tip is used.
384 is given then the tip is used.
385
385
386 outgoing [-p] [dest]::
386 outgoing [-p] [dest]::
387 Show changesets not found in the specified destination repo or the
387 Show changesets not found in the specified destination repo or the
388 default push repo. These are the changesets that would be pushed
388 default push repo. These are the changesets that would be pushed
389 if a push was requested.
389 if a push was requested.
390
390
391 options:
391 options:
392 -p, --patch show patch
392 -p, --patch show patch
393
393
394 aliases: out
394 aliases: out
395
395
396 parents::
396 parents::
397 Print the working directory's parent revisions.
397 Print the working directory's parent revisions.
398
398
399 paths [NAME]::
399 paths [NAME]::
400 Show definition of symbolic path name NAME. If no name is given, show
400 Show definition of symbolic path name NAME. If no name is given, show
401 definition of available names.
401 definition of available names.
402
402
403 Path names are defined in the [paths] section of /etc/mercurial/hgrc
403 Path names are defined in the [paths] section of /etc/mercurial/hgrc
404 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
404 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
405
405
406 pull <repository path>::
406 pull <repository path>::
407 Pull changes from a remote repository to a local one.
407 Pull changes from a remote repository to a local one.
408
408
409 This finds all changes from the repository at the specified path
409 This finds all changes from the repository at the specified path
410 or URL and adds them to the local repository. By default, this
410 or URL and adds them to the local repository. By default, this
411 does not update the copy of the project in the working directory.
411 does not update the copy of the project in the working directory.
412
412
413 Valid URLs are of the form:
413 Valid URLs are of the form:
414
414
415 local/filesystem/path
415 local/filesystem/path
416 http://[user@]host[:port][/path]
416 http://[user@]host[:port][/path]
417 https://[user@]host[:port][/path]
417 https://[user@]host[:port][/path]
418 ssh://[user@]host[:port][/path]
418 ssh://[user@]host[:port][/path]
419
419
420 SSH requires an accessible shell account on the destination machine
420 SSH requires an accessible shell account on the destination machine
421 and a copy of hg in the remote path. With SSH, paths are relative
421 and a copy of hg in the remote path. With SSH, paths are relative
422 to the remote user's home directory by default; use two slashes at
422 to the remote user's home directory by default; use two slashes at
423 the start of a path to specify it as relative to the filesystem root.
423 the start of a path to specify it as relative to the filesystem root.
424
424
425 options:
425 options:
426 -u, --update update the working directory to tip after pull
426 -u, --update update the working directory to tip after pull
427 -e, --ssh specify ssh command to use
427 -e, --ssh specify ssh command to use
428 --remotecmd specify hg command to run on the remote side
428 --remotecmd specify hg command to run on the remote side
429
429
430 push <destination>::
430 push <destination>::
431 Push changes from the local repository to the given destination.
431 Push changes from the local repository to the given destination.
432
432
433 This is the symmetrical operation for pull. It helps to move
433 This is the symmetrical operation for pull. It helps to move
434 changes from the current repository to a different one. If the
434 changes from the current repository to a different one. If the
435 destination is local this is identical to a pull in that directory
435 destination is local this is identical to a pull in that directory
436 from the current one.
436 from the current one.
437
437
438 By default, push will refuse to run if it detects the result would
438 By default, push will refuse to run if it detects the result would
439 increase the number of remote heads. This generally indicates the
439 increase the number of remote heads. This generally indicates the
440 the client has forgotten to sync and merge before pushing.
440 the client has forgotten to sync and merge before pushing.
441
441
442 Valid URLs are of the form:
442 Valid URLs are of the form:
443
443
444 local/filesystem/path
444 local/filesystem/path
445 ssh://[user@]host[:port][/path]
445 ssh://[user@]host[:port][/path]
446
446
447 SSH requires an accessible shell account on the destination
447 SSH requires an accessible shell account on the destination
448 machine and a copy of hg in the remote path.
448 machine and a copy of hg in the remote path.
449
449
450 options:
450 options:
451
451
452 -f, --force force update
452 -f, --force force update
453 -e, --ssh specify ssh command to use
453 -e, --ssh specify ssh command to use
454 --remotecmd specify hg command to run on the remote side
454 --remotecmd specify hg command to run on the remote side
455
455
456 rawcommit [-p -d -u -F -m -l]::
456 rawcommit [-p -d -u -F -m -l]::
457 Lowlevel commit, for use in helper scripts.
457 Lowlevel commit, for use in helper scripts.
458
458
459 This command is not intended to be used by normal users, as it is
459 This command is not intended to be used by normal users, as it is
460 primarily useful for importing from other SCMs.
460 primarily useful for importing from other SCMs.
461
461
462 recover::
462 recover::
463 Recover from an interrupted commit or pull.
463 Recover from an interrupted commit or pull.
464
464
465 This command tries to fix the repository status after an interrupted
465 This command tries to fix the repository status after an interrupted
466 operation. It should only be necessary when Mercurial suggests it.
466 operation. It should only be necessary when Mercurial suggests it.
467
467
468 remove [options] [files ...]::
468 remove [options] [files ...]::
469 Schedule the indicated files for removal from the repository.
469 Schedule the indicated files for removal from the repository.
470
470
471 This command schedules the files to be removed at the next commit.
471 This command schedules the files to be removed at the next commit.
472 This only removes files from the current branch, not from the
472 This only removes files from the current branch, not from the
473 entire project history. If the files still exist in the working
473 entire project history. If the files still exist in the working
474 directory, they will be deleted from it.
474 directory, they will be deleted from it.
475
475
476 aliases: rm
476 aliases: rm
477
477
478 rename <source ...> <dest>::
478 rename <source ...> <dest>::
479 Mark dest as copies of sources; mark sources for deletion. If
479 Mark dest as copies of sources; mark sources for deletion. If
480 dest is a directory, copies are put in that directory. If dest is
480 dest is a directory, copies are put in that directory. If dest is
481 a file, there can only be one source.
481 a file, there can only be one source.
482
482
483 By default, this command copies the contents of files as they
483 By default, this command copies the contents of files as they
484 stand in the working directory. If invoked with --after, the
484 stand in the working directory. If invoked with --after, the
485 operation is recorded, but no copying is performed.
485 operation is recorded, but no copying is performed.
486
486
487 This command takes effect in the next commit.
487 This command takes effect in the next commit.
488
488
489 NOTE: This command should be treated as experimental. While it
489 NOTE: This command should be treated as experimental. While it
490 should properly record rename files, this information is not yet
490 should properly record rename files, this information is not yet
491 fully used by merge, nor fully reported by log.
491 fully used by merge, nor fully reported by log.
492
492
493 Options:
493 Options:
494 -A, --after record a rename that has already occurred
494 -A, --after record a rename that has already occurred
495 -f, --force forcibly copy over an existing managed file
495 -f, --force forcibly copy over an existing managed file
496 -p, --parents append source path to dest
496 -p, --parents append source path to dest
497
497
498 aliases: mv
498 aliases: mv
499
499
500 revert [names ...]::
500 revert [names ...]::
501 Revert any uncommitted modifications made to the named files or
501 Revert any uncommitted modifications made to the named files or
502 directories. This restores the contents of the affected files to
502 directories. This restores the contents of the affected files to
503 an unmodified state.
503 an unmodified state.
504
504
505 If a file has been deleted, it is recreated. If the executable
505 If a file has been deleted, it is recreated. If the executable
506 mode of a file was changed, it is reset.
506 mode of a file was changed, it is reset.
507
507
508 If a directory is given, all files in that directory and its
508 If a directory is given, all files in that directory and its
509 subdirectories are reverted.
509 subdirectories are reverted.
510
510
511 If no arguments are given, all files in the current directory and
511 If no arguments are given, all files in the current directory and
512 its subdirectories are reverted.
512 its subdirectories are reverted.
513
513
514 options:
514 options:
515 -r, --rev <rev> revision to revert to
515 -r, --rev <rev> revision to revert to
516 -n, --nonrecursive do not recurse into subdirectories
516 -n, --nonrecursive do not recurse into subdirectories
517
517
518 root::
518 root::
519 Print the root directory of the current repository.
519 Print the root directory of the current repository.
520
520
521 serve [options]::
521 serve [options]::
522 Start a local HTTP repository browser and pull server.
522 Start a local HTTP repository browser and pull server.
523
523
524 By default, the server logs accesses to stdout and errors to
524 By default, the server logs accesses to stdout and errors to
525 stderr. Use the "-A" and "-E" options to log to files.
525 stderr. Use the "-A" and "-E" options to log to files.
526
526
527 options:
527 options:
528 -A, --accesslog <file> name of access log file to write to
528 -A, --accesslog <file> name of access log file to write to
529 -E, --errorlog <file> name of error log file to write to
529 -E, --errorlog <file> name of error log file to write to
530 -a, --address <addr> address to use
530 -a, --address <addr> address to use
531 -p, --port <n> port to use (default: 8000)
531 -p, --port <n> port to use (default: 8000)
532 -n, --name <name> name to show in web pages (default: working dir)
532 -n, --name <name> name to show in web pages (default: working dir)
533 -t, --templatedir <path> web templates to use
533 -t, --templatedir <path> web templates to use
534 -6, --ipv6 use IPv6 in addition to IPv4
534 -6, --ipv6 use IPv6 in addition to IPv4
535
535
536 status [options] [files]::
536 status [options] [files]::
537 Show changed files in the working directory. If no names are
537 Show changed files in the working directory. If no names are
538 given, all files are shown. Otherwise, only files matching the
538 given, all files are shown. Otherwise, only files matching the
539 given names are shown.
539 given names are shown.
540
540
541 The codes used to show the status of files are:
541 The codes used to show the status of files are:
542
542
543 M = changed
543 M = changed
544 A = added
544 A = added
545 R = removed
545 R = removed
546 ? = not tracked
546 ? = not tracked
547
547
548 options:
548 options:
549
549
550 -m, --modified show only modified files
550 -m, --modified show only modified files
551 -a, --added show only added files
551 -a, --added show only added files
552 -r, --removed show only removed files
552 -r, --removed show only removed files
553 -u, --unknown show only unknown (not tracked) files
553 -u, --unknown show only unknown (not tracked) files
554 -n, --no-status hide status prefix
554 -n, --no-status hide status prefix
555 -0, --print0 end filenames with NUL, for use with xargs
555 -0, --print0 end filenames with NUL, for use with xargs
556 -I, --include <pat> include names matching the given patterns
556 -I, --include <pat> include names matching the given patterns
557 -X, --exclude <pat> exclude names matching the given patterns
557 -X, --exclude <pat> exclude names matching the given patterns
558
558
559 tag [-l -m <text> -d <datecode> -u <user>] <name> [revision]::
559 tag [-l -m <text> -d <datecode> -u <user>] <name> [revision]::
560 Name a particular revision using <name>.
560 Name a particular revision using <name>.
561
561
562 Tags are used to name particular revisions of the repository and are
562 Tags are used to name particular revisions of the repository and are
563 very useful to compare different revision, to go back to significant
563 very useful to compare different revision, to go back to significant
564 earlier versions or to mark branch points as releases, etc.
564 earlier versions or to mark branch points as releases, etc.
565
565
566 If no revision is given, the tip is used.
566 If no revision is given, the tip is used.
567
567
568 To facilitate version control, distribution, and merging of tags,
568 To facilitate version control, distribution, and merging of tags,
569 they are stored as a file named ".hgtags" which is managed
569 they are stored as a file named ".hgtags" which is managed
570 similarly to other project files and can be hand-edited if
570 similarly to other project files and can be hand-edited if
571 necessary.
571 necessary.
572
572
573 options:
573 options:
574 -l, --local make the tag local
574 -l, --local make the tag local
575 -m, --message <text> message for tag commit log entry
575 -m, --message <text> message for tag commit log entry
576 -d, --date <datecode> datecode for commit
576 -d, --date <datecode> datecode for commit
577 -u, --user <user> user for commit
577 -u, --user <user> user for commit
578
578
579 Note: Local tags are not version-controlled or distributed and are
579 Note: Local tags are not version-controlled or distributed and are
580 stored in the .hg/localtags file. If there exists a local tag and
580 stored in the .hg/localtags file. If there exists a local tag and
581 a public tag with the same name, local tag is used.
581 a public tag with the same name, local tag is used.
582
582
583 tags::
583 tags::
584 List the repository tags.
584 List the repository tags.
585
585
586 This lists both regular and local tags.
586 This lists both regular and local tags.
587
587
588 tip::
588 tip::
589 Show the tip revision.
589 Show the tip revision.
590
590
591 unbundle <file>::
591 unbundle <file>::
592 (EXPERIMENTAL)
592 (EXPERIMENTAL)
593
593
594 Apply a compressed changegroup file generated by the bundle
594 Apply a compressed changegroup file generated by the bundle
595 command.
595 command.
596
596
597 undo::
597 undo::
598 Undo the last commit or pull transaction.
598 Undo the last commit or pull transaction.
599
599
600 Roll back the last pull or commit transaction on the
600 Roll back the last pull or commit transaction on the
601 repository, restoring the project to its earlier state.
601 repository, restoring the project to its earlier state.
602
602
603 This command should be used with care. There is only one level of
603 This command should be used with care. There is only one level of
604 undo and there is no redo.
604 undo and there is no redo.
605
605
606 This command is not intended for use on public repositories. Once
606 This command is not intended for use on public repositories. Once
607 a change is visible for pull by other users, undoing it locally is
607 a change is visible for pull by other users, undoing it locally is
608 ineffective.
608 ineffective.
609
609
610 update [-m -C] [revision]::
610 update [-m -C] [revision]::
611 Update the working directory to the specified revision.
611 Update the working directory to the specified revision.
612
612
613 By default, update will refuse to run if doing so would require
613 By default, update will refuse to run if doing so would require
614 merging or discarding local changes.
614 merging or discarding local changes.
615
615
616 With the -m option, a merge will be performed.
616 With the -m option, a merge will be performed.
617
617
618 With the -C option, local changes will be lost.
618 With the -C option, local changes will be lost.
619
619
620 options:
620 options:
621 -m, --merge allow merging of branches
621 -m, --merge allow merging of branches
622 -C, --clean overwrite locally modified files
622 -C, --clean overwrite locally modified files
623
623
624 aliases: up checkout co
624 aliases: up checkout co
625
625
626 verify::
626 verify::
627 Verify the integrity of the current repository.
627 Verify the integrity of the current repository.
628
628
629 This will perform an extensive check of the repository's
629 This will perform an extensive check of the repository's
630 integrity, validating the hashes and checksums of each entry in
630 integrity, validating the hashes and checksums of each entry in
631 the changelog, manifest, and tracked files, as well as the
631 the changelog, manifest, and tracked files, as well as the
632 integrity of their crosslinks and indices.
632 integrity of their crosslinks and indices.
633
633
634 FILE NAME PATTERNS
634 FILE NAME PATTERNS
635 ------------------
635 ------------------
636
636
637 Mercurial accepts several notations for identifying one or more
637 Mercurial accepts several notations for identifying one or more
638 files at a time.
638 files at a time.
639
639
640 By default, Mercurial treats filenames as shell-style extended
640 By default, Mercurial treats filenames as shell-style extended
641 glob patterns.
641 glob patterns.
642
642
643 Alternate pattern notations must be specified explicitly.
643 Alternate pattern notations must be specified explicitly.
644
644
645 To use a plain path name without any pattern matching, start a
645 To use a plain path name without any pattern matching, start a
646 name with "path:". These path names must match completely, from
646 name with "path:". These path names must match completely, from
647 the root of the current repository.
647 the root of the current repository.
648
648
649 To use an extended glob, start a name with "glob:". Globs are
649 To use an extended glob, start a name with "glob:". Globs are
650 rooted at the current directory; a glob such as "*.c" will match
650 rooted at the current directory; a glob such as "*.c" will match
651 files ending in ".c" in the current directory only.
651 files ending in ".c" in the current directory only.
652
652
653 The supported glob syntax extensions are "**" to match any string
653 The supported glob syntax extensions are "**" to match any string
654 across path separators, and "{a,b}" to mean "a or b".
654 across path separators, and "{a,b}" to mean "a or b".
655
655
656 To use a Perl/Python regular expression, start a name with "re:".
656 To use a Perl/Python regular expression, start a name with "re:".
657 Regexp pattern matching is anchored at the root of the repository.
657 Regexp pattern matching is anchored at the root of the repository.
658
658
659 Plain examples:
659 Plain examples:
660
660
661 path:foo/bar a name bar in a directory named foo in the root of
661 path:foo/bar a name bar in a directory named foo in the root of
662 the repository
662 the repository
663 path:path:name a file or directory named "path:name"
663 path:path:name a file or directory named "path:name"
664
664
665 Glob examples:
665 Glob examples:
666
666
667 glob:*.c any name ending in ".c" in the current directory
667 glob:*.c any name ending in ".c" in the current directory
668 *.c any name ending in ".c" in the current directory
668 *.c any name ending in ".c" in the current directory
669 **.c any name ending in ".c" in the current directory, or
669 **.c any name ending in ".c" in the current directory, or
670 any subdirectory
670 any subdirectory
671 foo/*.c any name ending in ".c" in the directory foo
671 foo/*.c any name ending in ".c" in the directory foo
672 foo/**.c any name ending in ".c" in the directory foo, or any
672 foo/**.c any name ending in ".c" in the directory foo, or any
673 subdirectory
673 subdirectory
674
674
675 Regexp examples:
675 Regexp examples:
676
676
677 re:.*\.c$ any name ending in ".c", anywhere in the repository
677 re:.*\.c$ any name ending in ".c", anywhere in the repository
678
678
679
679
680 SPECIFYING SINGLE REVISIONS
680 SPECIFYING SINGLE REVISIONS
681 ---------------------------
681 ---------------------------
682
682
683 Mercurial accepts several notations for identifying individual
683 Mercurial accepts several notations for identifying individual
684 revisions.
684 revisions.
685
685
686 A plain integer is treated as a revision number. Negative
686 A plain integer is treated as a revision number. Negative
687 integers are treated as offsets from the tip, with -1 denoting the
687 integers are treated as offsets from the tip, with -1 denoting the
688 tip.
688 tip.
689
689
690 A 40-digit hexadecimal string is treated as a unique revision
690 A 40-digit hexadecimal string is treated as a unique revision
691 identifier.
691 identifier.
692
692
693 A hexadecimal string less than 40 characters long is treated as a
693 A hexadecimal string less than 40 characters long is treated as a
694 unique revision identifier, and referred to as a short-form
694 unique revision identifier, and referred to as a short-form
695 identifier. A short-form identifier is only valid if it is the
695 identifier. A short-form identifier is only valid if it is the
696 prefix of one full-length identifier.
696 prefix of one full-length identifier.
697
697
698 Any other string is treated as a tag name, which is a symbolic
698 Any other string is treated as a tag name, which is a symbolic
699 name associated with a revision identifier. Tag names may not
699 name associated with a revision identifier. Tag names may not
700 contain the ":" character.
700 contain the ":" character.
701
701
702 The reserved name "tip" is a special tag that always identifies
702 The reserved name "tip" is a special tag that always identifies
703 the most recent revision.
703 the most recent revision.
704
704
705 SPECIFYING MULTIPLE REVISIONS
705 SPECIFYING MULTIPLE REVISIONS
706 -----------------------------
706 -----------------------------
707
707
708 When Mercurial accepts more than one revision, they may be
708 When Mercurial accepts more than one revision, they may be
709 specified individually, or provided as a continuous range,
709 specified individually, or provided as a continuous range,
710 separated by the ":" character.
710 separated by the ":" character.
711
711
712 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
712 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
713 are revision identifiers. Both BEGIN and END are optional. If
713 are revision identifiers. Both BEGIN and END are optional. If
714 BEGIN is not specified, it defaults to revision number 0. If END
714 BEGIN is not specified, it defaults to revision number 0. If END
715 is not specified, it defaults to the tip. The range ":" thus
715 is not specified, it defaults to the tip. The range ":" thus
716 means "all revisions".
716 means "all revisions".
717
717
718 If BEGIN is greater than END, revisions are treated in reverse
718 If BEGIN is greater than END, revisions are treated in reverse
719 order.
719 order.
720
720
721 A range acts as a closed interval. This means that a range of 3:5
721 A range acts as a closed interval. This means that a range of 3:5
722 gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2.
722 gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2.
723
723
724 ENVIRONMENT VARIABLES
724 ENVIRONMENT VARIABLES
725 ---------------------
725 ---------------------
726
726
727 HGEDITOR::
727 HGEDITOR::
728 This is the name of the editor to use when committing. Defaults to the
728 This is the name of the editor to use when committing. Defaults to the
729 value of EDITOR.
729 value of EDITOR.
730
730
731 (deprecated, use .hgrc)
731 (deprecated, use .hgrc)
732
732
733 HGMERGE::
733 HGMERGE::
734 An executable to use for resolving merge conflicts. The program
734 An executable to use for resolving merge conflicts. The program
735 will be executed with three arguments: local file, remote file,
735 will be executed with three arguments: local file, remote file,
736 ancestor file.
736 ancestor file.
737
737
738 The default program is "hgmerge", which is a shell script provided
738 The default program is "hgmerge", which is a shell script provided
739 by Mercurial with some sensible defaults.
739 by Mercurial with some sensible defaults.
740
740
741 (deprecated, use .hgrc)
741 (deprecated, use .hgrc)
742
742
743 HGUSER::
743 HGUSER::
744 This is the string used for the author of a commit.
744 This is the string used for the author of a commit.
745
745
746 (deprecated, use .hgrc)
746 (deprecated, use .hgrc)
747
747
748 EMAIL::
748 EMAIL::
749 If HGUSER is not set, this will be used as the author for a commit.
749 If HGUSER is not set, this will be used as the author for a commit.
750
750
751 LOGNAME::
751 LOGNAME::
752 If neither HGUSER nor EMAIL is set, LOGNAME will be used (with
752 If neither HGUSER nor EMAIL is set, LOGNAME will be used (with
753 '@hostname' appended) as the author value for a commit.
753 '@hostname' appended) as the author value for a commit.
754
754
755 EDITOR::
755 EDITOR::
756 This is the name of the editor used in the hgmerge script. It will be
756 This is the name of the editor used in the hgmerge script. It will be
757 used for commit messages if HGEDITOR isn't set. Defaults to 'vi'.
757 used for commit messages if HGEDITOR isn't set. Defaults to 'vi'.
758
758
759 PYTHONPATH::
759 PYTHONPATH::
760 This is used by Python to find imported modules and may need to be set
760 This is used by Python to find imported modules and may need to be set
761 appropriately if Mercurial is not installed system-wide.
761 appropriately if Mercurial is not installed system-wide.
762
762
763 FILES
763 FILES
764 -----
764 -----
765 .hgignore::
765 .hgignore::
766 This file contains regular expressions (one per line) that describe file
766 This file contains regular expressions (one per line) that describe file
767 names that should be ignored by hg.
767 names that should be ignored by hg.
768
768
769 .hgtags::
769 .hgtags::
770 This file contains changeset hash values and text tag names (one of each
770 This file contains changeset hash values and text tag names (one of each
771 separated by spaces) that correspond to tagged versions of the repository
771 separated by spaces) that correspond to tagged versions of the repository
772 contents.
772 contents.
773
773
774 /etc/mercurial/hgrc, $HOME/.hgrc, .hg/hgrc::
774 /etc/mercurial/hgrc, $HOME/.hgrc, .hg/hgrc::
775 This file contains defaults and configuration. Values in .hg/hgrc
775 This file contains defaults and configuration. Values in .hg/hgrc
776 override those in $HOME/.hgrc, and these override settings made in the
776 override those in $HOME/.hgrc, and these override settings made in the
777 global /etc/mercurial/hgrc configuration. See hgrc(5) for details of
777 global /etc/mercurial/hgrc configuration. See hgrc(5) for details of
778 the contents and format of these files.
778 the contents and format of these files.
779
779
780 BUGS
780 BUGS
781 ----
781 ----
782 Probably lots, please post them to the mailing list (See Resources below)
782 Probably lots, please post them to the mailing list (See Resources below)
783 when you find them.
783 when you find them.
784
784
785 SEE ALSO
785 SEE ALSO
786 --------
786 --------
787 hgrc(5)
787 hgrc(5)
788
788
789 AUTHOR
789 AUTHOR
790 ------
790 ------
791 Written by Matt Mackall <mpm@selenic.com>
791 Written by Matt Mackall <mpm@selenic.com>
792
792
793 RESOURCES
793 RESOURCES
794 ---------
794 ---------
795 http://selenic.com/mercurial[Main Web Site]
795 http://selenic.com/mercurial[Main Web Site]
796
796
797 http://www.serpentine.com/mercurial[Wiki site]
797 http://www.serpentine.com/mercurial[Wiki site]
798
798
799 http://selenic.com/hg[Source code repository]
799 http://selenic.com/hg[Source code repository]
800
800
801 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
801 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
802
802
803 COPYING
803 COPYING
804 -------
804 -------
805 Copyright (C) 2005 Matt Mackall.
805 Copyright (C) 2005 Matt Mackall.
806 Free use of this software is granted under the terms of the GNU General
806 Free use of this software is granted under the terms of the GNU General
807 Public License (GPL).
807 Public License (GPL).
@@ -1,192 +1,192 b''
1 HGRC(5)
1 HGRC(5)
2 =======
2 =======
3 Bryan O'Sullivan <bos@serpentine.com>
3 Bryan O'Sullivan <bos@serpentine.com>
4
4
5 NAME
5 NAME
6 ----
6 ----
7 hgrc - configuration files for Mercurial
7 hgrc - configuration files for Mercurial
8
8
9 SYNOPSIS
9 SYNOPSIS
10 --------
10 --------
11
11
12 The Mercurial system uses a set of configuration files to control
12 The Mercurial system uses a set of configuration files to control
13 aspects of its behaviour.
13 aspects of its behaviour.
14
14
15 FILES
15 FILES
16 -----
16 -----
17
17
18 Mercurial reads configuration data from three files:
18 Mercurial reads configuration data from three files:
19
19
20 /etc/mercurial/hgrc::
20 /etc/mercurial/hgrc::
21 Options in this global configuration file apply to all Mercurial
21 Options in this global configuration file apply to all Mercurial
22 commands executed by any user in any directory.
22 commands executed by any user in any directory.
23
23
24 $HOME/.hgrc::
24 $HOME/.hgrc::
25 Per-user configuration options that apply to all Mercurial commands,
25 Per-user configuration options that apply to all Mercurial commands,
26 no matter from which directory they are run. Values in this file
26 no matter from which directory they are run. Values in this file
27 override global settings.
27 override global settings.
28
28
29 <repo>/.hg/hgrc::
29 <repo>/.hg/hgrc::
30 Per-repository configuration options that only apply in a
30 Per-repository configuration options that only apply in a
31 particular repository. This file is not version-controlled, and
31 particular repository. This file is not version-controlled, and
32 will not get transferred during a "clone" operation. Values in
32 will not get transferred during a "clone" operation. Values in
33 this file override global and per-user settings.
33 this file override global and per-user settings.
34
34
35 SYNTAX
35 SYNTAX
36 ------
36 ------
37
37
38 A configuration file consists of sections, led by a "[section]" header
38 A configuration file consists of sections, led by a "[section]" header
39 and followed by "name: value" entries; "name=value" is also accepted.
39 and followed by "name: value" entries; "name=value" is also accepted.
40
40
41 [spam]
41 [spam]
42 eggs=ham
42 eggs=ham
43 green=
43 green=
44 eggs
44 eggs
45
45
46 Each line contains one entry. If the lines that follow are indented,
46 Each line contains one entry. If the lines that follow are indented,
47 they are treated as continuations of that entry.
47 they are treated as continuations of that entry.
48
48
49 Leading whitespace is removed from values. Empty lines are skipped.
49 Leading whitespace is removed from values. Empty lines are skipped.
50
50
51 The optional values can contain format strings which refer to other
51 The optional values can contain format strings which refer to other
52 values in the same section, or values in a special DEFAULT section.
52 values in the same section, or values in a special DEFAULT section.
53
53
54 Lines beginning with "#" or ";" are ignored and may be used to provide
54 Lines beginning with "#" or ";" are ignored and may be used to provide
55 comments.
55 comments.
56
56
57 SECTIONS
57 SECTIONS
58 --------
58 --------
59
59
60 This section describes the different sections that may appear in a
60 This section describes the different sections that may appear in a
61 Mercurial "hgrc" file, the purpose of each section, its possible
61 Mercurial "hgrc" file, the purpose of each section, its possible
62 keys, and their possible values.
62 keys, and their possible values.
63
63
64 decode/encode::
64 decode/encode::
65 Filters for transforming files on checkout/checkin. This would
65 Filters for transforming files on checkout/checkin. This would
66 typically be used for newline processing or other
66 typically be used for newline processing or other
67 localization/canonicalization of files.
67 localization/canonicalization of files.
68
68
69 Filters consist of a filter pattern followed by a filter command.
69 Filters consist of a filter pattern followed by a filter command.
70 The command must accept data on stdin and return the transformed
70 The command must accept data on stdin and return the transformed
71 data on stdout.
71 data on stdout.
72
72
73 Example:
73 Example:
74
74
75 [encode]
75 [encode]
76 # uncompress gzip files on checkin to improve delta compression
76 # uncompress gzip files on checkin to improve delta compression
77 # note: not necessarily a good idea, just an example
77 # note: not necessarily a good idea, just an example
78 *.gz = gunzip
78 *.gz = gunzip
79
79
80 [decode]
80 [decode]
81 # recompress gzip files when writing them to the working dir
81 # recompress gzip files when writing them to the working dir
82 *.gz = gzip
82 *.gz = gzip
83
83
84 hooks::
84 hooks::
85 Commands that get automatically executed by various actions such as
85 Commands that get automatically executed by various actions such as
86 starting or finishing a commit.
86 starting or finishing a commit.
87 changegroup;;
87 changegroup;;
88 Run after a changegroup has been added via push or pull.
88 Run after a changegroup has been added via push or pull.
89 commit;;
89 commit;;
90 Run after a changeset has been created. Passed the ID of the newly
90 Run after a changeset has been created. Passed the ID of the newly
91 created changeset.
91 created changeset.
92 precommit;;
92 precommit;;
93 Run before starting a commit. Exit status 0 allows the commit to
93 Run before starting a commit. Exit status 0 allows the commit to
94 proceed. Non-zero status will cause the commit to fail.
94 proceed. Non-zero status will cause the commit to fail.
95
95
96 http_proxy::
96 http_proxy::
97 Used to access web-based Mercurial repositories through a HTTP
97 Used to access web-based Mercurial repositories through a HTTP
98 proxy.
98 proxy.
99 host;;
99 host;;
100 Host name and (optional) port of the proxy server, for example
100 Host name and (optional) port of the proxy server, for example
101 "myproxy:8000".
101 "myproxy:8000".
102 no;;
102 no;;
103 Optional. Comma-separated list of host names that should bypass
103 Optional. Comma-separated list of host names that should bypass
104 the proxy.
104 the proxy.
105 passwd;;
105 passwd;;
106 Optional. Password to authenticate with at the proxy server.
106 Optional. Password to authenticate with at the proxy server.
107 user;;
107 user;;
108 Optional. User name to authenticate with at the proxy server.
108 Optional. User name to authenticate with at the proxy server.
109
109
110 paths::
110 paths::
111 Assigns symbolic names to repositories. The left side is the
111 Assigns symbolic names to repositories. The left side is the
112 symbolic name, and the right gives the directory or URL that is the
112 symbolic name, and the right gives the directory or URL that is the
113 location of the repository.
113 location of the repository.
114
114
115 ui::
115 ui::
116 User interface controls.
116 User interface controls.
117 debug;;
117 debug;;
118 Print debugging information. True or False. Default is False.
118 Print debugging information. True or False. Default is False.
119 editor;;
119 editor;;
120 The editor to use during a commit. Default is $EDITOR or "vi".
120 The editor to use during a commit. Default is $EDITOR or "vi".
121 interactive;;
121 interactive;;
122 Allow to prompt the user. True or False. Default is True.
122 Allow to prompt the user. True or False. Default is True.
123 merge;;
123 merge;;
124 The conflict resolution program to use during a manual merge.
124 The conflict resolution program to use during a manual merge.
125 Default is "hgmerge".
125 Default is "hgmerge".
126 quiet;;
126 quiet;;
127 Reduce the amount of output printed. True or False. Default is False.
127 Reduce the amount of output printed. True or False. Default is False.
128 remotecmd;;
128 remotecmd;;
129 remote command to use for clone/push/pull operations. Default is 'hg'.
129 remote command to use for clone/push/pull operations. Default is 'hg'.
130 ssh;;
130 ssh;;
131 command to use for SSH connections. Default is 'ssh'.
131 command to use for SSH connections. Default is 'ssh'.
132 username;;
132 username;;
133 The committer of a changeset created when running "commit".
133 The committer of a changeset created when running "commit".
134 Typically a person's name and email address, e.g. "Fred Widget
134 Typically a person's name and email address, e.g. "Fred Widget
135 <fred@example.com>". Default is $EMAIL or username@hostname.
135 <fred@example.com>". Default is $EMAIL or username@hostname.
136 verbose;;
136 verbose;;
137 Increase the amount of output printed. True or False. Default is False.
137 Increase the amount of output printed. True or False. Default is False.
138
138
139
139
140 web::
140 web::
141 Web interface configuration.
141 Web interface configuration.
142 accesslog;;
142 accesslog;;
143 Where to output the access log. Default is stdout.
143 Where to output the access log. Default is stdout.
144 address;;
144 address;;
145 Interface address to bind to. Default is all.
145 Interface address to bind to. Default is all.
146 allowbz2;;
146 allowbz2;;
147 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
147 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
148 allowgz;;
148 allowgz;;
149 Whether to allow .tar.gz downloading of repo revisions. Default is false.
149 Whether to allow .tar.gz downloading of repo revisions. Default is false.
150 allowpull;;
150 allowpull;;
151 Whether to allow pulling from the repository. Default is true.
151 Whether to allow pulling from the repository. Default is true.
152 allowzip;;
152 allowzip;;
153 Whether to allow .zip downloading of repo revisions. Default is false.
153 Whether to allow .zip downloading of repo revisions. Default is false.
154 This feature creates temporary files.
154 This feature creates temporary files.
155 description;;
155 description;;
156 Textual description of the repository's purpose or contents.
156 Textual description of the repository's purpose or contents.
157 Default is "unknown".
157 Default is "unknown".
158 errorlog;;
158 errorlog;;
159 Where to output the error log. Default is stderr.
159 Where to output the error log. Default is stderr.
160 ipv6;;
160 ipv6;;
161 Whether to use IPv6. Default is false.
161 Whether to use IPv6. Default is false.
162 name;;
162 name;;
163 Repository name to use in the web interface. Default is current
163 Repository name to use in the web interface. Default is current
164 working directory.
164 working directory.
165 maxchanges;;
165 maxchanges;;
166 Maximum number of changes to list on the changelog. Default is 10.
166 Maximum number of changes to list on the changelog. Default is 10.
167 maxfiles;;
167 maxfiles;;
168 Maximum number of files to list per changeset. Default is 10.
168 Maximum number of files to list per changeset. Default is 10.
169 port;;
169 port;;
170 Port to listen on. Default is 8000.
170 Port to listen on. Default is 8000.
171 style;;
171 style;;
172 Which template map style to use.
172 Which template map style to use.
173 templates;;
173 templates;;
174 Where to find the HTML templates. Default is install path.
174 Where to find the HTML templates. Default is install path.
175
175
176
176
177 AUTHOR
177 AUTHOR
178 ------
178 ------
179 Bryan O'Sullivan <bos@serpentine.com>.
179 Bryan O'Sullivan <bos@serpentine.com>.
180
180
181 Mercurial was written by Matt Mackall <mpm@selenic.com>.
181 Mercurial was written by Matt Mackall <mpm@selenic.com>.
182
182
183 SEE ALSO
183 SEE ALSO
184 --------
184 --------
185 hg(1)
185 hg(1)
186
186
187 COPYING
187 COPYING
188 -------
188 -------
189 This manual page is copyright 2005 Bryan O'Sullivan.
189 This manual page is copyright 2005 Bryan O'Sullivan.
190 Mercurial is copyright 2005 Matt Mackall.
190 Mercurial is copyright 2005 Matt Mackall.
191 Free use of this software is granted under the terms of the GNU General
191 Free use of this software is granted under the terms of the GNU General
192 Public License (GPL).
192 Public License (GPL).
@@ -1,987 +1,987 b''
1 # hgweb.py - web interface to a mercurial repository
1 # hgweb.py - web interface to a mercurial repository
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, cgi, sys
9 import os, cgi, sys
10 from demandload import demandload
10 from demandload import demandload
11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer")
12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer")
13 from node import *
13 from node import *
14
14
15 def templatepath():
15 def templatepath():
16 for f in "templates", "../templates":
16 for f in "templates", "../templates":
17 p = os.path.join(os.path.dirname(__file__), f)
17 p = os.path.join(os.path.dirname(__file__), f)
18 if os.path.isdir(p):
18 if os.path.isdir(p):
19 return p
19 return p
20
20
21 def age(t):
21 def age(t):
22 def plural(t, c):
22 def plural(t, c):
23 if c == 1:
23 if c == 1:
24 return t
24 return t
25 return t + "s"
25 return t + "s"
26 def fmt(t, c):
26 def fmt(t, c):
27 return "%d %s" % (c, plural(t, c))
27 return "%d %s" % (c, plural(t, c))
28
28
29 now = time.time()
29 now = time.time()
30 delta = max(1, int(now - t))
30 delta = max(1, int(now - t))
31
31
32 scales = [["second", 1],
32 scales = [["second", 1],
33 ["minute", 60],
33 ["minute", 60],
34 ["hour", 3600],
34 ["hour", 3600],
35 ["day", 3600 * 24],
35 ["day", 3600 * 24],
36 ["week", 3600 * 24 * 7],
36 ["week", 3600 * 24 * 7],
37 ["month", 3600 * 24 * 30],
37 ["month", 3600 * 24 * 30],
38 ["year", 3600 * 24 * 365]]
38 ["year", 3600 * 24 * 365]]
39
39
40 scales.reverse()
40 scales.reverse()
41
41
42 for t, s in scales:
42 for t, s in scales:
43 n = delta / s
43 n = delta / s
44 if n >= 2 or s == 1:
44 if n >= 2 or s == 1:
45 return fmt(t, n)
45 return fmt(t, n)
46
46
47 def nl2br(text):
47 def nl2br(text):
48 return text.replace('\n', '<br/>\n')
48 return text.replace('\n', '<br/>\n')
49
49
50 def obfuscate(text):
50 def obfuscate(text):
51 return ''.join(['&#%d;' % ord(c) for c in text])
51 return ''.join(['&#%d;' % ord(c) for c in text])
52
52
53 def up(p):
53 def up(p):
54 if p[0] != "/":
54 if p[0] != "/":
55 p = "/" + p
55 p = "/" + p
56 if p[-1] == "/":
56 if p[-1] == "/":
57 p = p[:-1]
57 p = p[:-1]
58 up = os.path.dirname(p)
58 up = os.path.dirname(p)
59 if up == "/":
59 if up == "/":
60 return "/"
60 return "/"
61 return up + "/"
61 return up + "/"
62
62
63 class hgrequest:
63 class hgrequest:
64 def __init__(self, inp=None, out=None, env=None):
64 def __init__(self, inp=None, out=None, env=None):
65 self.inp = inp or sys.stdin
65 self.inp = inp or sys.stdin
66 self.out = out or sys.stdout
66 self.out = out or sys.stdout
67 self.env = env or os.environ
67 self.env = env or os.environ
68 self.form = cgi.parse(self.inp, self.env)
68 self.form = cgi.parse(self.inp, self.env)
69
69
70 def write(self, *things):
70 def write(self, *things):
71 for thing in things:
71 for thing in things:
72 if hasattr(thing, "__iter__"):
72 if hasattr(thing, "__iter__"):
73 for part in thing:
73 for part in thing:
74 self.write(part)
74 self.write(part)
75 else:
75 else:
76 try:
76 try:
77 self.out.write(str(thing))
77 self.out.write(str(thing))
78 except socket.error, inst:
78 except socket.error, inst:
79 if inst[0] != errno.ECONNRESET:
79 if inst[0] != errno.ECONNRESET:
80 raise
80 raise
81
81
82 def header(self, headers=[('Content-type','text/html')]):
82 def header(self, headers=[('Content-type','text/html')]):
83 for header in headers:
83 for header in headers:
84 self.out.write("%s: %s\r\n" % header)
84 self.out.write("%s: %s\r\n" % header)
85 self.out.write("\r\n")
85 self.out.write("\r\n")
86
86
87 def httphdr(self, type, file="", size=0):
87 def httphdr(self, type, file="", size=0):
88
88
89 headers = [('Content-type', type)]
89 headers = [('Content-type', type)]
90 if file:
90 if file:
91 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
91 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
92 if size > 0:
92 if size > 0:
93 headers.append(('Content-length', str(size)))
93 headers.append(('Content-length', str(size)))
94 self.header(headers)
94 self.header(headers)
95
95
96 class templater:
96 class templater:
97 def __init__(self, mapfile, filters={}, defaults={}):
97 def __init__(self, mapfile, filters={}, defaults={}):
98 self.cache = {}
98 self.cache = {}
99 self.map = {}
99 self.map = {}
100 self.base = os.path.dirname(mapfile)
100 self.base = os.path.dirname(mapfile)
101 self.filters = filters
101 self.filters = filters
102 self.defaults = defaults
102 self.defaults = defaults
103
103
104 for l in file(mapfile):
104 for l in file(mapfile):
105 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
105 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
106 if m:
106 if m:
107 self.cache[m.group(1)] = m.group(2)
107 self.cache[m.group(1)] = m.group(2)
108 else:
108 else:
109 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
109 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
110 if m:
110 if m:
111 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
111 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
112 else:
112 else:
113 raise LookupError("unknown map entry '%s'" % l)
113 raise LookupError("unknown map entry '%s'" % l)
114
114
115 def __call__(self, t, **map):
115 def __call__(self, t, **map):
116 m = self.defaults.copy()
116 m = self.defaults.copy()
117 m.update(map)
117 m.update(map)
118 try:
118 try:
119 tmpl = self.cache[t]
119 tmpl = self.cache[t]
120 except KeyError:
120 except KeyError:
121 tmpl = self.cache[t] = file(self.map[t]).read()
121 tmpl = self.cache[t] = file(self.map[t]).read()
122 return self.template(tmpl, self.filters, **m)
122 return self.template(tmpl, self.filters, **m)
123
123
124 def template(self, tmpl, filters={}, **map):
124 def template(self, tmpl, filters={}, **map):
125 while tmpl:
125 while tmpl:
126 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
126 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
127 if m:
127 if m:
128 yield tmpl[:m.start(0)]
128 yield tmpl[:m.start(0)]
129 v = map.get(m.group(1), "")
129 v = map.get(m.group(1), "")
130 v = callable(v) and v(**map) or v
130 v = callable(v) and v(**map) or v
131
131
132 format = m.group(2)
132 format = m.group(2)
133 fl = m.group(4)
133 fl = m.group(4)
134
134
135 if format:
135 if format:
136 q = v.__iter__
136 q = v.__iter__
137 for i in q():
137 for i in q():
138 lm = map.copy()
138 lm = map.copy()
139 lm.update(i)
139 lm.update(i)
140 yield self(format[1:], **lm)
140 yield self(format[1:], **lm)
141
141
142 v = ""
142 v = ""
143
143
144 elif fl:
144 elif fl:
145 for f in fl.split("|")[1:]:
145 for f in fl.split("|")[1:]:
146 v = filters[f](v)
146 v = filters[f](v)
147
147
148 yield v
148 yield v
149 tmpl = tmpl[m.end(0):]
149 tmpl = tmpl[m.end(0):]
150 else:
150 else:
151 yield tmpl
151 yield tmpl
152 return
152 return
153
153
154 def rfc822date(x):
154 def rfc822date(x):
155 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
155 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
156
156
157 common_filters = {
157 common_filters = {
158 "escape": cgi.escape,
158 "escape": cgi.escape,
159 "age": age,
159 "age": age,
160 "date": (lambda x: time.asctime(time.gmtime(x))),
160 "date": (lambda x: time.asctime(time.gmtime(x))),
161 "addbreaks": nl2br,
161 "addbreaks": nl2br,
162 "obfuscate": obfuscate,
162 "obfuscate": obfuscate,
163 "short": (lambda x: x[:12]),
163 "short": (lambda x: x[:12]),
164 "firstline": (lambda x: x.splitlines(1)[0]),
164 "firstline": (lambda x: x.splitlines(1)[0]),
165 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
165 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
166 "rfc822date": rfc822date,
166 "rfc822date": rfc822date,
167 }
167 }
168
168
169
169
170
170
171 class hgweb:
171 class hgweb:
172 def __init__(self, repo, name=None):
172 def __init__(self, repo, name=None):
173 if type(repo) == type(""):
173 if type(repo) == type(""):
174 self.repo = hg.repository(ui.ui(), repo)
174 self.repo = hg.repository(ui.ui(), repo)
175 else:
175 else:
176 self.repo = repo
176 self.repo = repo
177
177
178 self.mtime = -1
178 self.mtime = -1
179 self.reponame = name
179 self.reponame = name
180 self.archives = 'zip', 'gz', 'bz2'
180 self.archives = 'zip', 'gz', 'bz2'
181
181
182 def refresh(self):
182 def refresh(self):
183 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
183 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
184 if s.st_mtime != self.mtime:
184 if s.st_mtime != self.mtime:
185 self.mtime = s.st_mtime
185 self.mtime = s.st_mtime
186 self.repo = hg.repository(self.repo.ui, self.repo.root)
186 self.repo = hg.repository(self.repo.ui, self.repo.root)
187 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
187 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
188 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
188 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
189 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
189 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
190
190
191 def date(self, cs):
191 def date(self, cs):
192 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
192 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
193
193
194 def listfiles(self, files, mf):
194 def listfiles(self, files, mf):
195 for f in files[:self.maxfiles]:
195 for f in files[:self.maxfiles]:
196 yield self.t("filenodelink", node=hex(mf[f]), file=f)
196 yield self.t("filenodelink", node=hex(mf[f]), file=f)
197 if len(files) > self.maxfiles:
197 if len(files) > self.maxfiles:
198 yield self.t("fileellipses")
198 yield self.t("fileellipses")
199
199
200 def listfilediffs(self, files, changeset):
200 def listfilediffs(self, files, changeset):
201 for f in files[:self.maxfiles]:
201 for f in files[:self.maxfiles]:
202 yield self.t("filedifflink", node=hex(changeset), file=f)
202 yield self.t("filedifflink", node=hex(changeset), file=f)
203 if len(files) > self.maxfiles:
203 if len(files) > self.maxfiles:
204 yield self.t("fileellipses")
204 yield self.t("fileellipses")
205
205
206 def parents(self, t1, nodes=[], rev=None,**args):
206 def parents(self, t1, nodes=[], rev=None,**args):
207 if not rev:
207 if not rev:
208 rev = lambda x: ""
208 rev = lambda x: ""
209 for node in nodes:
209 for node in nodes:
210 if node != nullid:
210 if node != nullid:
211 yield self.t(t1, node=hex(node), rev=rev(node), **args)
211 yield self.t(t1, node=hex(node), rev=rev(node), **args)
212
212
213 def showtag(self, t1, node=nullid, **args):
213 def showtag(self, t1, node=nullid, **args):
214 for t in self.repo.nodetags(node):
214 for t in self.repo.nodetags(node):
215 yield self.t(t1, tag=t, **args)
215 yield self.t(t1, tag=t, **args)
216
216
217 def diff(self, node1, node2, files):
217 def diff(self, node1, node2, files):
218 def filterfiles(list, files):
218 def filterfiles(list, files):
219 l = [x for x in list if x in files]
219 l = [x for x in list if x in files]
220
220
221 for f in files:
221 for f in files:
222 if f[-1] != os.sep:
222 if f[-1] != os.sep:
223 f += os.sep
223 f += os.sep
224 l += [x for x in list if x.startswith(f)]
224 l += [x for x in list if x.startswith(f)]
225 return l
225 return l
226
226
227 parity = [0]
227 parity = [0]
228 def diffblock(diff, f, fn):
228 def diffblock(diff, f, fn):
229 yield self.t("diffblock",
229 yield self.t("diffblock",
230 lines=prettyprintlines(diff),
230 lines=prettyprintlines(diff),
231 parity=parity[0],
231 parity=parity[0],
232 file=f,
232 file=f,
233 filenode=hex(fn or nullid))
233 filenode=hex(fn or nullid))
234 parity[0] = 1 - parity[0]
234 parity[0] = 1 - parity[0]
235
235
236 def prettyprintlines(diff):
236 def prettyprintlines(diff):
237 for l in diff.splitlines(1):
237 for l in diff.splitlines(1):
238 if l.startswith('+'):
238 if l.startswith('+'):
239 yield self.t("difflineplus", line=l)
239 yield self.t("difflineplus", line=l)
240 elif l.startswith('-'):
240 elif l.startswith('-'):
241 yield self.t("difflineminus", line=l)
241 yield self.t("difflineminus", line=l)
242 elif l.startswith('@'):
242 elif l.startswith('@'):
243 yield self.t("difflineat", line=l)
243 yield self.t("difflineat", line=l)
244 else:
244 else:
245 yield self.t("diffline", line=l)
245 yield self.t("diffline", line=l)
246
246
247 r = self.repo
247 r = self.repo
248 cl = r.changelog
248 cl = r.changelog
249 mf = r.manifest
249 mf = r.manifest
250 change1 = cl.read(node1)
250 change1 = cl.read(node1)
251 change2 = cl.read(node2)
251 change2 = cl.read(node2)
252 mmap1 = mf.read(change1[0])
252 mmap1 = mf.read(change1[0])
253 mmap2 = mf.read(change2[0])
253 mmap2 = mf.read(change2[0])
254 date1 = self.date(change1)
254 date1 = self.date(change1)
255 date2 = self.date(change2)
255 date2 = self.date(change2)
256
256
257 c, a, d, u = r.changes(node1, node2)
257 c, a, d, u = r.changes(node1, node2)
258 if files:
258 if files:
259 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
259 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
260
260
261 for f in c:
261 for f in c:
262 to = r.file(f).read(mmap1[f])
262 to = r.file(f).read(mmap1[f])
263 tn = r.file(f).read(mmap2[f])
263 tn = r.file(f).read(mmap2[f])
264 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
264 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
265 for f in a:
265 for f in a:
266 to = None
266 to = None
267 tn = r.file(f).read(mmap2[f])
267 tn = r.file(f).read(mmap2[f])
268 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
268 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
269 for f in d:
269 for f in d:
270 to = r.file(f).read(mmap1[f])
270 to = r.file(f).read(mmap1[f])
271 tn = None
271 tn = None
272 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
272 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
273
273
274 def changelog(self, pos):
274 def changelog(self, pos):
275 def changenav(**map):
275 def changenav(**map):
276 def seq(factor=1):
276 def seq(factor=1):
277 yield 1 * factor
277 yield 1 * factor
278 yield 3 * factor
278 yield 3 * factor
279 #yield 5 * factor
279 #yield 5 * factor
280 for f in seq(factor * 10):
280 for f in seq(factor * 10):
281 yield f
281 yield f
282
282
283 l = []
283 l = []
284 for f in seq():
284 for f in seq():
285 if f < self.maxchanges / 2:
285 if f < self.maxchanges / 2:
286 continue
286 continue
287 if f > count:
287 if f > count:
288 break
288 break
289 r = "%d" % f
289 r = "%d" % f
290 if pos + f < count:
290 if pos + f < count:
291 l.append(("+" + r, pos + f))
291 l.append(("+" + r, pos + f))
292 if pos - f >= 0:
292 if pos - f >= 0:
293 l.insert(0, ("-" + r, pos - f))
293 l.insert(0, ("-" + r, pos - f))
294
294
295 yield {"rev": 0, "label": "(0)"}
295 yield {"rev": 0, "label": "(0)"}
296
296
297 for label, rev in l:
297 for label, rev in l:
298 yield {"label": label, "rev": rev}
298 yield {"label": label, "rev": rev}
299
299
300 yield {"label": "tip", "rev": ""}
300 yield {"label": "tip", "rev": ""}
301
301
302 def changelist(**map):
302 def changelist(**map):
303 parity = (start - end) & 1
303 parity = (start - end) & 1
304 cl = self.repo.changelog
304 cl = self.repo.changelog
305 l = [] # build a list in forward order for efficiency
305 l = [] # build a list in forward order for efficiency
306 for i in range(start, end):
306 for i in range(start, end):
307 n = cl.node(i)
307 n = cl.node(i)
308 changes = cl.read(n)
308 changes = cl.read(n)
309 hn = hex(n)
309 hn = hex(n)
310 t = float(changes[2].split(' ')[0])
310 t = float(changes[2].split(' ')[0])
311
311
312 l.insert(0, {"parity": parity,
312 l.insert(0, {"parity": parity,
313 "author": changes[1],
313 "author": changes[1],
314 "parent": self.parents("changelogparent",
314 "parent": self.parents("changelogparent",
315 cl.parents(n), cl.rev),
315 cl.parents(n), cl.rev),
316 "changelogtag": self.showtag("changelogtag",n),
316 "changelogtag": self.showtag("changelogtag",n),
317 "manifest": hex(changes[0]),
317 "manifest": hex(changes[0]),
318 "desc": changes[4],
318 "desc": changes[4],
319 "date": t,
319 "date": t,
320 "files": self.listfilediffs(changes[3], n),
320 "files": self.listfilediffs(changes[3], n),
321 "rev": i,
321 "rev": i,
322 "node": hn})
322 "node": hn})
323 parity = 1 - parity
323 parity = 1 - parity
324
324
325 for e in l:
325 for e in l:
326 yield e
326 yield e
327
327
328 cl = self.repo.changelog
328 cl = self.repo.changelog
329 mf = cl.read(cl.tip())[0]
329 mf = cl.read(cl.tip())[0]
330 count = cl.count()
330 count = cl.count()
331 start = max(0, pos - self.maxchanges + 1)
331 start = max(0, pos - self.maxchanges + 1)
332 end = min(count, start + self.maxchanges)
332 end = min(count, start + self.maxchanges)
333 pos = end - 1
333 pos = end - 1
334
334
335 yield self.t('changelog',
335 yield self.t('changelog',
336 changenav=changenav,
336 changenav=changenav,
337 manifest=hex(mf),
337 manifest=hex(mf),
338 rev=pos, changesets=count, entries=changelist)
338 rev=pos, changesets=count, entries=changelist)
339
339
340 def search(self, query):
340 def search(self, query):
341
341
342 def changelist(**map):
342 def changelist(**map):
343 cl = self.repo.changelog
343 cl = self.repo.changelog
344 count = 0
344 count = 0
345 qw = query.lower().split()
345 qw = query.lower().split()
346
346
347 def revgen():
347 def revgen():
348 for i in range(cl.count() - 1, 0, -100):
348 for i in range(cl.count() - 1, 0, -100):
349 l = []
349 l = []
350 for j in range(max(0, i - 100), i):
350 for j in range(max(0, i - 100), i):
351 n = cl.node(j)
351 n = cl.node(j)
352 changes = cl.read(n)
352 changes = cl.read(n)
353 l.append((n, j, changes))
353 l.append((n, j, changes))
354 l.reverse()
354 l.reverse()
355 for e in l:
355 for e in l:
356 yield e
356 yield e
357
357
358 for n, i, changes in revgen():
358 for n, i, changes in revgen():
359 miss = 0
359 miss = 0
360 for q in qw:
360 for q in qw:
361 if not (q in changes[1].lower() or
361 if not (q in changes[1].lower() or
362 q in changes[4].lower() or
362 q in changes[4].lower() or
363 q in " ".join(changes[3][:20]).lower()):
363 q in " ".join(changes[3][:20]).lower()):
364 miss = 1
364 miss = 1
365 break
365 break
366 if miss:
366 if miss:
367 continue
367 continue
368
368
369 count += 1
369 count += 1
370 hn = hex(n)
370 hn = hex(n)
371 t = float(changes[2].split(' ')[0])
371 t = float(changes[2].split(' ')[0])
372
372
373 yield self.t('searchentry',
373 yield self.t('searchentry',
374 parity=count & 1,
374 parity=count & 1,
375 author=changes[1],
375 author=changes[1],
376 parent=self.parents("changelogparent",
376 parent=self.parents("changelogparent",
377 cl.parents(n), cl.rev),
377 cl.parents(n), cl.rev),
378 changelogtag=self.showtag("changelogtag",n),
378 changelogtag=self.showtag("changelogtag",n),
379 manifest=hex(changes[0]),
379 manifest=hex(changes[0]),
380 desc=changes[4],
380 desc=changes[4],
381 date=t,
381 date=t,
382 files=self.listfilediffs(changes[3], n),
382 files=self.listfilediffs(changes[3], n),
383 rev=i,
383 rev=i,
384 node=hn)
384 node=hn)
385
385
386 if count >= self.maxchanges:
386 if count >= self.maxchanges:
387 break
387 break
388
388
389 cl = self.repo.changelog
389 cl = self.repo.changelog
390 mf = cl.read(cl.tip())[0]
390 mf = cl.read(cl.tip())[0]
391
391
392 yield self.t('search',
392 yield self.t('search',
393 query=query,
393 query=query,
394 manifest=hex(mf),
394 manifest=hex(mf),
395 entries=changelist)
395 entries=changelist)
396
396
397 def changeset(self, nodeid):
397 def changeset(self, nodeid):
398 n = bin(nodeid)
398 n = bin(nodeid)
399 cl = self.repo.changelog
399 cl = self.repo.changelog
400 changes = cl.read(n)
400 changes = cl.read(n)
401 p1 = cl.parents(n)[0]
401 p1 = cl.parents(n)[0]
402 t = float(changes[2].split(' ')[0])
402 t = float(changes[2].split(' ')[0])
403
403
404 files = []
404 files = []
405 mf = self.repo.manifest.read(changes[0])
405 mf = self.repo.manifest.read(changes[0])
406 for f in changes[3]:
406 for f in changes[3]:
407 files.append(self.t("filenodelink",
407 files.append(self.t("filenodelink",
408 filenode=hex(mf.get(f, nullid)), file=f))
408 filenode=hex(mf.get(f, nullid)), file=f))
409
409
410 def diff(**map):
410 def diff(**map):
411 yield self.diff(p1, n, None)
411 yield self.diff(p1, n, None)
412
412
413 def archivelist():
413 def archivelist():
414 for i in self.archives:
414 for i in self.archives:
415 if self.repo.ui.configbool("web", "allow" + i, False):
415 if self.repo.ui.configbool("web", "allow" + i, False):
416 yield {"type" : i, "node" : nodeid}
416 yield {"type" : i, "node" : nodeid}
417
417
418 yield self.t('changeset',
418 yield self.t('changeset',
419 diff=diff,
419 diff=diff,
420 rev=cl.rev(n),
420 rev=cl.rev(n),
421 node=nodeid,
421 node=nodeid,
422 parent=self.parents("changesetparent",
422 parent=self.parents("changesetparent",
423 cl.parents(n), cl.rev),
423 cl.parents(n), cl.rev),
424 changesettag=self.showtag("changesettag",n),
424 changesettag=self.showtag("changesettag",n),
425 manifest=hex(changes[0]),
425 manifest=hex(changes[0]),
426 author=changes[1],
426 author=changes[1],
427 desc=changes[4],
427 desc=changes[4],
428 date=t,
428 date=t,
429 files=files,
429 files=files,
430 archives=archivelist())
430 archives=archivelist())
431
431
432 def filelog(self, f, filenode):
432 def filelog(self, f, filenode):
433 cl = self.repo.changelog
433 cl = self.repo.changelog
434 fl = self.repo.file(f)
434 fl = self.repo.file(f)
435 count = fl.count()
435 count = fl.count()
436
436
437 def entries(**map):
437 def entries(**map):
438 l = []
438 l = []
439 parity = (count - 1) & 1
439 parity = (count - 1) & 1
440
440
441 for i in range(count):
441 for i in range(count):
442 n = fl.node(i)
442 n = fl.node(i)
443 lr = fl.linkrev(n)
443 lr = fl.linkrev(n)
444 cn = cl.node(lr)
444 cn = cl.node(lr)
445 cs = cl.read(cl.node(lr))
445 cs = cl.read(cl.node(lr))
446 t = float(cs[2].split(' ')[0])
446 t = float(cs[2].split(' ')[0])
447
447
448 l.insert(0, {"parity": parity,
448 l.insert(0, {"parity": parity,
449 "filenode": hex(n),
449 "filenode": hex(n),
450 "filerev": i,
450 "filerev": i,
451 "file": f,
451 "file": f,
452 "node": hex(cn),
452 "node": hex(cn),
453 "author": cs[1],
453 "author": cs[1],
454 "date": t,
454 "date": t,
455 "parent": self.parents("filelogparent",
455 "parent": self.parents("filelogparent",
456 fl.parents(n),
456 fl.parents(n),
457 fl.rev, file=f),
457 fl.rev, file=f),
458 "desc": cs[4]})
458 "desc": cs[4]})
459 parity = 1 - parity
459 parity = 1 - parity
460
460
461 for e in l:
461 for e in l:
462 yield e
462 yield e
463
463
464 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
464 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
465
465
466 def filerevision(self, f, node):
466 def filerevision(self, f, node):
467 fl = self.repo.file(f)
467 fl = self.repo.file(f)
468 n = bin(node)
468 n = bin(node)
469 text = fl.read(n)
469 text = fl.read(n)
470 changerev = fl.linkrev(n)
470 changerev = fl.linkrev(n)
471 cl = self.repo.changelog
471 cl = self.repo.changelog
472 cn = cl.node(changerev)
472 cn = cl.node(changerev)
473 cs = cl.read(cn)
473 cs = cl.read(cn)
474 t = float(cs[2].split(' ')[0])
474 t = float(cs[2].split(' ')[0])
475 mfn = cs[0]
475 mfn = cs[0]
476
476
477 def lines():
477 def lines():
478 for l, t in enumerate(text.splitlines(1)):
478 for l, t in enumerate(text.splitlines(1)):
479 yield {"line": t,
479 yield {"line": t,
480 "linenumber": "% 6d" % (l + 1),
480 "linenumber": "% 6d" % (l + 1),
481 "parity": l & 1}
481 "parity": l & 1}
482
482
483 yield self.t("filerevision",
483 yield self.t("filerevision",
484 file=f,
484 file=f,
485 filenode=node,
485 filenode=node,
486 path=up(f),
486 path=up(f),
487 text=lines(),
487 text=lines(),
488 rev=changerev,
488 rev=changerev,
489 node=hex(cn),
489 node=hex(cn),
490 manifest=hex(mfn),
490 manifest=hex(mfn),
491 author=cs[1],
491 author=cs[1],
492 date=t,
492 date=t,
493 parent=self.parents("filerevparent",
493 parent=self.parents("filerevparent",
494 fl.parents(n), fl.rev, file=f),
494 fl.parents(n), fl.rev, file=f),
495 permissions=self.repo.manifest.readflags(mfn)[f])
495 permissions=self.repo.manifest.readflags(mfn)[f])
496
496
497 def fileannotate(self, f, node):
497 def fileannotate(self, f, node):
498 bcache = {}
498 bcache = {}
499 ncache = {}
499 ncache = {}
500 fl = self.repo.file(f)
500 fl = self.repo.file(f)
501 n = bin(node)
501 n = bin(node)
502 changerev = fl.linkrev(n)
502 changerev = fl.linkrev(n)
503
503
504 cl = self.repo.changelog
504 cl = self.repo.changelog
505 cn = cl.node(changerev)
505 cn = cl.node(changerev)
506 cs = cl.read(cn)
506 cs = cl.read(cn)
507 t = float(cs[2].split(' ')[0])
507 t = float(cs[2].split(' ')[0])
508 mfn = cs[0]
508 mfn = cs[0]
509
509
510 def annotate(**map):
510 def annotate(**map):
511 parity = 1
511 parity = 1
512 last = None
512 last = None
513 for r, l in fl.annotate(n):
513 for r, l in fl.annotate(n):
514 try:
514 try:
515 cnode = ncache[r]
515 cnode = ncache[r]
516 except KeyError:
516 except KeyError:
517 cnode = ncache[r] = self.repo.changelog.node(r)
517 cnode = ncache[r] = self.repo.changelog.node(r)
518
518
519 try:
519 try:
520 name = bcache[r]
520 name = bcache[r]
521 except KeyError:
521 except KeyError:
522 cl = self.repo.changelog.read(cnode)
522 cl = self.repo.changelog.read(cnode)
523 bcache[r] = name = self.repo.ui.shortuser(cl[1])
523 bcache[r] = name = self.repo.ui.shortuser(cl[1])
524
524
525 if last != cnode:
525 if last != cnode:
526 parity = 1 - parity
526 parity = 1 - parity
527 last = cnode
527 last = cnode
528
528
529 yield {"parity": parity,
529 yield {"parity": parity,
530 "node": hex(cnode),
530 "node": hex(cnode),
531 "rev": r,
531 "rev": r,
532 "author": name,
532 "author": name,
533 "file": f,
533 "file": f,
534 "line": l}
534 "line": l}
535
535
536 yield self.t("fileannotate",
536 yield self.t("fileannotate",
537 file=f,
537 file=f,
538 filenode=node,
538 filenode=node,
539 annotate=annotate,
539 annotate=annotate,
540 path=up(f),
540 path=up(f),
541 rev=changerev,
541 rev=changerev,
542 node=hex(cn),
542 node=hex(cn),
543 manifest=hex(mfn),
543 manifest=hex(mfn),
544 author=cs[1],
544 author=cs[1],
545 date=t,
545 date=t,
546 parent=self.parents("fileannotateparent",
546 parent=self.parents("fileannotateparent",
547 fl.parents(n), fl.rev, file=f),
547 fl.parents(n), fl.rev, file=f),
548 permissions=self.repo.manifest.readflags(mfn)[f])
548 permissions=self.repo.manifest.readflags(mfn)[f])
549
549
550 def manifest(self, mnode, path):
550 def manifest(self, mnode, path):
551 mf = self.repo.manifest.read(bin(mnode))
551 mf = self.repo.manifest.read(bin(mnode))
552 rev = self.repo.manifest.rev(bin(mnode))
552 rev = self.repo.manifest.rev(bin(mnode))
553 node = self.repo.changelog.node(rev)
553 node = self.repo.changelog.node(rev)
554 mff=self.repo.manifest.readflags(bin(mnode))
554 mff=self.repo.manifest.readflags(bin(mnode))
555
555
556 files = {}
556 files = {}
557
557
558 p = path[1:]
558 p = path[1:]
559 l = len(p)
559 l = len(p)
560
560
561 for f,n in mf.items():
561 for f,n in mf.items():
562 if f[:l] != p:
562 if f[:l] != p:
563 continue
563 continue
564 remain = f[l:]
564 remain = f[l:]
565 if "/" in remain:
565 if "/" in remain:
566 short = remain[:remain.find("/") + 1] # bleah
566 short = remain[:remain.find("/") + 1] # bleah
567 files[short] = (f, None)
567 files[short] = (f, None)
568 else:
568 else:
569 short = os.path.basename(remain)
569 short = os.path.basename(remain)
570 files[short] = (f, n)
570 files[short] = (f, n)
571
571
572 def filelist(**map):
572 def filelist(**map):
573 parity = 0
573 parity = 0
574 fl = files.keys()
574 fl = files.keys()
575 fl.sort()
575 fl.sort()
576 for f in fl:
576 for f in fl:
577 full, fnode = files[f]
577 full, fnode = files[f]
578 if not fnode:
578 if not fnode:
579 continue
579 continue
580
580
581 yield {"file": full,
581 yield {"file": full,
582 "manifest": mnode,
582 "manifest": mnode,
583 "filenode": hex(fnode),
583 "filenode": hex(fnode),
584 "parity": parity,
584 "parity": parity,
585 "basename": f,
585 "basename": f,
586 "permissions": mff[full]}
586 "permissions": mff[full]}
587 parity = 1 - parity
587 parity = 1 - parity
588
588
589 def dirlist(**map):
589 def dirlist(**map):
590 parity = 0
590 parity = 0
591 fl = files.keys()
591 fl = files.keys()
592 fl.sort()
592 fl.sort()
593 for f in fl:
593 for f in fl:
594 full, fnode = files[f]
594 full, fnode = files[f]
595 if fnode:
595 if fnode:
596 continue
596 continue
597
597
598 yield {"parity": parity,
598 yield {"parity": parity,
599 "path": os.path.join(path, f),
599 "path": os.path.join(path, f),
600 "manifest": mnode,
600 "manifest": mnode,
601 "basename": f[:-1]}
601 "basename": f[:-1]}
602 parity = 1 - parity
602 parity = 1 - parity
603
603
604 yield self.t("manifest",
604 yield self.t("manifest",
605 manifest=mnode,
605 manifest=mnode,
606 rev=rev,
606 rev=rev,
607 node=hex(node),
607 node=hex(node),
608 path=path,
608 path=path,
609 up=up(path),
609 up=up(path),
610 fentries=filelist,
610 fentries=filelist,
611 dentries=dirlist)
611 dentries=dirlist)
612
612
613 def tags(self):
613 def tags(self):
614 cl = self.repo.changelog
614 cl = self.repo.changelog
615 mf = cl.read(cl.tip())[0]
615 mf = cl.read(cl.tip())[0]
616
616
617 i = self.repo.tagslist()
617 i = self.repo.tagslist()
618 i.reverse()
618 i.reverse()
619
619
620 def entries(**map):
620 def entries(**map):
621 parity = 0
621 parity = 0
622 for k,n in i:
622 for k,n in i:
623 yield {"parity": parity,
623 yield {"parity": parity,
624 "tag": k,
624 "tag": k,
625 "node": hex(n)}
625 "node": hex(n)}
626 parity = 1 - parity
626 parity = 1 - parity
627
627
628 yield self.t("tags",
628 yield self.t("tags",
629 manifest=hex(mf),
629 manifest=hex(mf),
630 entries=entries)
630 entries=entries)
631
631
632 def filediff(self, file, changeset):
632 def filediff(self, file, changeset):
633 n = bin(changeset)
633 n = bin(changeset)
634 cl = self.repo.changelog
634 cl = self.repo.changelog
635 p1 = cl.parents(n)[0]
635 p1 = cl.parents(n)[0]
636 cs = cl.read(n)
636 cs = cl.read(n)
637 mf = self.repo.manifest.read(cs[0])
637 mf = self.repo.manifest.read(cs[0])
638
638
639 def diff(**map):
639 def diff(**map):
640 yield self.diff(p1, n, file)
640 yield self.diff(p1, n, file)
641
641
642 yield self.t("filediff",
642 yield self.t("filediff",
643 file=file,
643 file=file,
644 filenode=hex(mf.get(file, nullid)),
644 filenode=hex(mf.get(file, nullid)),
645 node=changeset,
645 node=changeset,
646 rev=self.repo.changelog.rev(n),
646 rev=self.repo.changelog.rev(n),
647 parent=self.parents("filediffparent",
647 parent=self.parents("filediffparent",
648 cl.parents(n), cl.rev),
648 cl.parents(n), cl.rev),
649 diff=diff)
649 diff=diff)
650
650
651 def archive(self, req, cnode, type):
651 def archive(self, req, cnode, type):
652 cs = self.repo.changelog.read(cnode)
652 cs = self.repo.changelog.read(cnode)
653 mnode = cs[0]
653 mnode = cs[0]
654 mf = self.repo.manifest.read(mnode)
654 mf = self.repo.manifest.read(mnode)
655 rev = self.repo.manifest.rev(mnode)
655 rev = self.repo.manifest.rev(mnode)
656 reponame = re.sub(r"\W+", "-", self.reponame)
656 reponame = re.sub(r"\W+", "-", self.reponame)
657 name = "%s-%s/" % (reponame, short(cnode))
657 name = "%s-%s/" % (reponame, short(cnode))
658
658
659 files = mf.keys()
659 files = mf.keys()
660 files.sort()
660 files.sort()
661
661
662 if type == 'zip':
662 if type == 'zip':
663 tmp = tempfile.mkstemp()[1]
663 tmp = tempfile.mkstemp()[1]
664 try:
664 try:
665 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
665 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
666
666
667 for f in files:
667 for f in files:
668 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
668 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
669 zf.close()
669 zf.close()
670
670
671 f = open(tmp, 'r')
671 f = open(tmp, 'r')
672 req.httphdr('application/zip', name[:-1] + '.zip',
672 req.httphdr('application/zip', name[:-1] + '.zip',
673 os.path.getsize(tmp))
673 os.path.getsize(tmp))
674 req.write(f.read())
674 req.write(f.read())
675 f.close()
675 f.close()
676 finally:
676 finally:
677 os.unlink(tmp)
677 os.unlink(tmp)
678
678
679 else:
679 else:
680 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
680 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
681 mff = self.repo.manifest.readflags(mnode)
681 mff = self.repo.manifest.readflags(mnode)
682 mtime = int(time.time())
682 mtime = int(time.time())
683
683
684 if type == "gz":
684 if type == "gz":
685 encoding = "gzip"
685 encoding = "gzip"
686 else:
686 else:
687 encoding = "x-bzip2"
687 encoding = "x-bzip2"
688 req.header([('Content-type', 'application/x-tar'),
688 req.header([('Content-type', 'application/x-tar'),
689 ('Content-disposition', 'attachment; filename=%s%s%s' %
689 ('Content-disposition', 'attachment; filename=%s%s%s' %
690 (name[:-1], '.tar.', type)),
690 (name[:-1], '.tar.', type)),
691 ('Content-encoding', encoding)])
691 ('Content-encoding', encoding)])
692 for fname in files:
692 for fname in files:
693 rcont = self.repo.file(fname).read(mf[fname])
693 rcont = self.repo.file(fname).read(mf[fname])
694 finfo = tarfile.TarInfo(name + fname)
694 finfo = tarfile.TarInfo(name + fname)
695 finfo.mtime = mtime
695 finfo.mtime = mtime
696 finfo.size = len(rcont)
696 finfo.size = len(rcont)
697 finfo.mode = mff[fname] and 0755 or 0644
697 finfo.mode = mff[fname] and 0755 or 0644
698 tf.addfile(finfo, StringIO.StringIO(rcont))
698 tf.addfile(finfo, StringIO.StringIO(rcont))
699 tf.close()
699 tf.close()
700
700
701 # add tags to things
701 # add tags to things
702 # tags -> list of changesets corresponding to tags
702 # tags -> list of changesets corresponding to tags
703 # find tag, changeset, file
703 # find tag, changeset, file
704
704
705 def run(self, req=hgrequest()):
705 def run(self, req=hgrequest()):
706 def header(**map):
706 def header(**map):
707 yield self.t("header", **map)
707 yield self.t("header", **map)
708
708
709 def footer(**map):
709 def footer(**map):
710 yield self.t("footer", **map)
710 yield self.t("footer", **map)
711
711
712 self.refresh()
712 self.refresh()
713
713
714 t = self.repo.ui.config("web", "templates", templatepath())
714 t = self.repo.ui.config("web", "templates", templatepath())
715 m = os.path.join(t, "map")
715 m = os.path.join(t, "map")
716 style = self.repo.ui.config("web", "style", "")
716 style = self.repo.ui.config("web", "style", "")
717 if req.form.has_key('style'):
717 if req.form.has_key('style'):
718 style = req.form['style'][0]
718 style = req.form['style'][0]
719 if style:
719 if style:
720 b = os.path.basename("map-" + style)
720 b = os.path.basename("map-" + style)
721 p = os.path.join(t, b)
721 p = os.path.join(t, b)
722 if os.path.isfile(p):
722 if os.path.isfile(p):
723 m = p
723 m = p
724
724
725 port = req.env["SERVER_PORT"]
725 port = req.env["SERVER_PORT"]
726 port = port != "80" and (":" + port) or ""
726 port = port != "80" and (":" + port) or ""
727 uri = req.env["REQUEST_URI"]
727 uri = req.env["REQUEST_URI"]
728 if "?" in uri:
728 if "?" in uri:
729 uri = uri.split("?")[0]
729 uri = uri.split("?")[0]
730 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
730 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
731 if not self.reponame:
731 if not self.reponame:
732 self.reponame = (self.repo.ui.config("web", "name")
732 self.reponame = (self.repo.ui.config("web", "name")
733 or uri.strip('/') or self.repo.root)
733 or uri.strip('/') or self.repo.root)
734
734
735 self.t = templater(m, common_filters,
735 self.t = templater(m, common_filters,
736 {"url": url,
736 {"url": url,
737 "repo": self.reponame,
737 "repo": self.reponame,
738 "header": header,
738 "header": header,
739 "footer": footer,
739 "footer": footer,
740 })
740 })
741
741
742 if not req.form.has_key('cmd'):
742 if not req.form.has_key('cmd'):
743 req.form['cmd'] = [self.t.cache['default'],]
743 req.form['cmd'] = [self.t.cache['default'],]
744
744
745 if req.form['cmd'][0] == 'changelog':
745 if req.form['cmd'][0] == 'changelog':
746 c = self.repo.changelog.count() - 1
746 c = self.repo.changelog.count() - 1
747 hi = c
747 hi = c
748 if req.form.has_key('rev'):
748 if req.form.has_key('rev'):
749 hi = req.form['rev'][0]
749 hi = req.form['rev'][0]
750 try:
750 try:
751 hi = self.repo.changelog.rev(self.repo.lookup(hi))
751 hi = self.repo.changelog.rev(self.repo.lookup(hi))
752 except hg.RepoError:
752 except hg.RepoError:
753 req.write(self.search(hi))
753 req.write(self.search(hi))
754 return
754 return
755
755
756 req.write(self.changelog(hi))
756 req.write(self.changelog(hi))
757
757
758 elif req.form['cmd'][0] == 'changeset':
758 elif req.form['cmd'][0] == 'changeset':
759 req.write(self.changeset(req.form['node'][0]))
759 req.write(self.changeset(req.form['node'][0]))
760
760
761 elif req.form['cmd'][0] == 'manifest':
761 elif req.form['cmd'][0] == 'manifest':
762 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
762 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
763
763
764 elif req.form['cmd'][0] == 'tags':
764 elif req.form['cmd'][0] == 'tags':
765 req.write(self.tags())
765 req.write(self.tags())
766
766
767 elif req.form['cmd'][0] == 'filediff':
767 elif req.form['cmd'][0] == 'filediff':
768 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
768 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
769
769
770 elif req.form['cmd'][0] == 'file':
770 elif req.form['cmd'][0] == 'file':
771 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
771 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
772
772
773 elif req.form['cmd'][0] == 'annotate':
773 elif req.form['cmd'][0] == 'annotate':
774 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
774 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
775
775
776 elif req.form['cmd'][0] == 'filelog':
776 elif req.form['cmd'][0] == 'filelog':
777 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
777 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
778
778
779 elif req.form['cmd'][0] == 'heads':
779 elif req.form['cmd'][0] == 'heads':
780 req.httphdr("application/mercurial-0.1")
780 req.httphdr("application/mercurial-0.1")
781 h = self.repo.heads()
781 h = self.repo.heads()
782 req.write(" ".join(map(hex, h)) + "\n")
782 req.write(" ".join(map(hex, h)) + "\n")
783
783
784 elif req.form['cmd'][0] == 'branches':
784 elif req.form['cmd'][0] == 'branches':
785 req.httphdr("application/mercurial-0.1")
785 req.httphdr("application/mercurial-0.1")
786 nodes = []
786 nodes = []
787 if req.form.has_key('nodes'):
787 if req.form.has_key('nodes'):
788 nodes = map(bin, req.form['nodes'][0].split(" "))
788 nodes = map(bin, req.form['nodes'][0].split(" "))
789 for b in self.repo.branches(nodes):
789 for b in self.repo.branches(nodes):
790 req.write(" ".join(map(hex, b)) + "\n")
790 req.write(" ".join(map(hex, b)) + "\n")
791
791
792 elif req.form['cmd'][0] == 'between':
792 elif req.form['cmd'][0] == 'between':
793 req.httphdr("application/mercurial-0.1")
793 req.httphdr("application/mercurial-0.1")
794 nodes = []
794 nodes = []
795 if req.form.has_key('pairs'):
795 if req.form.has_key('pairs'):
796 pairs = [map(bin, p.split("-"))
796 pairs = [map(bin, p.split("-"))
797 for p in req.form['pairs'][0].split(" ")]
797 for p in req.form['pairs'][0].split(" ")]
798 for b in self.repo.between(pairs):
798 for b in self.repo.between(pairs):
799 req.write(" ".join(map(hex, b)) + "\n")
799 req.write(" ".join(map(hex, b)) + "\n")
800
800
801 elif req.form['cmd'][0] == 'changegroup':
801 elif req.form['cmd'][0] == 'changegroup':
802 req.httphdr("application/mercurial-0.1")
802 req.httphdr("application/mercurial-0.1")
803 nodes = []
803 nodes = []
804 if not self.allowpull:
804 if not self.allowpull:
805 return
805 return
806
806
807 if req.form.has_key('roots'):
807 if req.form.has_key('roots'):
808 nodes = map(bin, req.form['roots'][0].split(" "))
808 nodes = map(bin, req.form['roots'][0].split(" "))
809
809
810 z = zlib.compressobj()
810 z = zlib.compressobj()
811 f = self.repo.changegroup(nodes)
811 f = self.repo.changegroup(nodes)
812 while 1:
812 while 1:
813 chunk = f.read(4096)
813 chunk = f.read(4096)
814 if not chunk:
814 if not chunk:
815 break
815 break
816 req.write(z.compress(chunk))
816 req.write(z.compress(chunk))
817
817
818 req.write(z.flush())
818 req.write(z.flush())
819
819
820 elif req.form['cmd'][0] == 'archive':
820 elif req.form['cmd'][0] == 'archive':
821 changeset = bin(req.form['node'][0])
821 changeset = bin(req.form['node'][0])
822 type = req.form['type'][0]
822 type = req.form['type'][0]
823 if (type in self.archives and
823 if (type in self.archives and
824 self.repo.ui.configbool("web", "allow" + type, False)):
824 self.repo.ui.configbool("web", "allow" + type, False)):
825 self.archive(req, changeset, type)
825 self.archive(req, changeset, type)
826 return
826 return
827
827
828 req.write(self.t("error"))
828 req.write(self.t("error"))
829
829
830 else:
830 else:
831 req.write(self.t("error"))
831 req.write(self.t("error"))
832
832
833 def create_server(repo):
833 def create_server(repo):
834
834
835 def openlog(opt, default):
835 def openlog(opt, default):
836 if opt and opt != '-':
836 if opt and opt != '-':
837 return open(opt, 'w')
837 return open(opt, 'w')
838 return default
838 return default
839
839
840 address = repo.ui.config("web", "address", "")
840 address = repo.ui.config("web", "address", "")
841 port = int(repo.ui.config("web", "port", 8000))
841 port = int(repo.ui.config("web", "port", 8000))
842 use_ipv6 = repo.ui.configbool("web", "ipv6")
842 use_ipv6 = repo.ui.configbool("web", "ipv6")
843 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
843 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
844 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
844 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
845
845
846 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
846 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
847 address_family = getattr(socket, 'AF_INET6', None)
847 address_family = getattr(socket, 'AF_INET6', None)
848
848
849 def __init__(self, *args, **kwargs):
849 def __init__(self, *args, **kwargs):
850 if self.address_family is None:
850 if self.address_family is None:
851 raise hg.RepoError('IPv6 not available on this system')
851 raise hg.RepoError('IPv6 not available on this system')
852 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
852 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
853
853
854 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
854 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
855 def log_error(self, format, *args):
855 def log_error(self, format, *args):
856 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
856 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
857 self.log_date_time_string(),
857 self.log_date_time_string(),
858 format % args))
858 format % args))
859
859
860 def log_message(self, format, *args):
860 def log_message(self, format, *args):
861 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
861 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
862 self.log_date_time_string(),
862 self.log_date_time_string(),
863 format % args))
863 format % args))
864
864
865 def do_POST(self):
865 def do_POST(self):
866 try:
866 try:
867 self.do_hgweb()
867 self.do_hgweb()
868 except socket.error, inst:
868 except socket.error, inst:
869 if inst[0] != errno.EPIPE:
869 if inst[0] != errno.EPIPE:
870 raise
870 raise
871
871
872 def do_GET(self):
872 def do_GET(self):
873 self.do_POST()
873 self.do_POST()
874
874
875 def do_hgweb(self):
875 def do_hgweb(self):
876 query = ""
876 query = ""
877 p = self.path.find("?")
877 p = self.path.find("?")
878 if p:
878 if p:
879 query = self.path[p + 1:]
879 query = self.path[p + 1:]
880 query = query.replace('+', ' ')
880 query = query.replace('+', ' ')
881
881
882 env = {}
882 env = {}
883 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
883 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
884 env['REQUEST_METHOD'] = self.command
884 env['REQUEST_METHOD'] = self.command
885 env['SERVER_NAME'] = self.server.server_name
885 env['SERVER_NAME'] = self.server.server_name
886 env['SERVER_PORT'] = str(self.server.server_port)
886 env['SERVER_PORT'] = str(self.server.server_port)
887 env['REQUEST_URI'] = "/"
887 env['REQUEST_URI'] = "/"
888 if query:
888 if query:
889 env['QUERY_STRING'] = query
889 env['QUERY_STRING'] = query
890 host = self.address_string()
890 host = self.address_string()
891 if host != self.client_address[0]:
891 if host != self.client_address[0]:
892 env['REMOTE_HOST'] = host
892 env['REMOTE_HOST'] = host
893 env['REMOTE_ADDR'] = self.client_address[0]
893 env['REMOTE_ADDR'] = self.client_address[0]
894
894
895 if self.headers.typeheader is None:
895 if self.headers.typeheader is None:
896 env['CONTENT_TYPE'] = self.headers.type
896 env['CONTENT_TYPE'] = self.headers.type
897 else:
897 else:
898 env['CONTENT_TYPE'] = self.headers.typeheader
898 env['CONTENT_TYPE'] = self.headers.typeheader
899 length = self.headers.getheader('content-length')
899 length = self.headers.getheader('content-length')
900 if length:
900 if length:
901 env['CONTENT_LENGTH'] = length
901 env['CONTENT_LENGTH'] = length
902 accept = []
902 accept = []
903 for line in self.headers.getallmatchingheaders('accept'):
903 for line in self.headers.getallmatchingheaders('accept'):
904 if line[:1] in "\t\n\r ":
904 if line[:1] in "\t\n\r ":
905 accept.append(line.strip())
905 accept.append(line.strip())
906 else:
906 else:
907 accept = accept + line[7:].split(',')
907 accept = accept + line[7:].split(',')
908 env['HTTP_ACCEPT'] = ','.join(accept)
908 env['HTTP_ACCEPT'] = ','.join(accept)
909
909
910 req = hgrequest(self.rfile, self.wfile, env)
910 req = hgrequest(self.rfile, self.wfile, env)
911 self.send_response(200, "Script output follows")
911 self.send_response(200, "Script output follows")
912 hg.run(req)
912 hg.run(req)
913
913
914 hg = hgweb(repo)
914 hg = hgweb(repo)
915 if use_ipv6:
915 if use_ipv6:
916 return IPv6HTTPServer((address, port), hgwebhandler)
916 return IPv6HTTPServer((address, port), hgwebhandler)
917 else:
917 else:
918 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
918 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
919
919
920 def server(path, name, templates, address, port, use_ipv6=False,
920 def server(path, name, templates, address, port, use_ipv6=False,
921 accesslog=sys.stdout, errorlog=sys.stderr):
921 accesslog=sys.stdout, errorlog=sys.stderr):
922 httpd = create_server(path, name, templates, address, port, use_ipv6,
922 httpd = create_server(path, name, templates, address, port, use_ipv6,
923 accesslog, errorlog)
923 accesslog, errorlog)
924 httpd.serve_forever()
924 httpd.serve_forever()
925
925
926 # This is a stopgap
926 # This is a stopgap
927 class hgwebdir:
927 class hgwebdir:
928 def __init__(self, config):
928 def __init__(self, config):
929 def cleannames(items):
929 def cleannames(items):
930 return [(name.strip('/'), path) for name, path in items]
930 return [(name.strip('/'), path) for name, path in items]
931
931
932 if type(config) == type([]):
932 if type(config) == type([]):
933 self.repos = cleannames(config)
933 self.repos = cleannames(config)
934 elif type(config) == type({}):
934 elif type(config) == type({}):
935 self.repos = cleannames(config.items())
935 self.repos = cleannames(config.items())
936 self.repos.sort()
936 self.repos.sort()
937 else:
937 else:
938 cp = ConfigParser.SafeConfigParser()
938 cp = ConfigParser.SafeConfigParser()
939 cp.read(config)
939 cp.read(config)
940 self.repos = cleannames(cp.items("paths"))
940 self.repos = cleannames(cp.items("paths"))
941 self.repos.sort()
941 self.repos.sort()
942
942
943 def run(self, req=hgrequest()):
943 def run(self, req=hgrequest()):
944 def header(**map):
944 def header(**map):
945 yield tmpl("header", **map)
945 yield tmpl("header", **map)
946
946
947 def footer(**map):
947 def footer(**map):
948 yield tmpl("footer", **map)
948 yield tmpl("footer", **map)
949
949
950 m = os.path.join(templatepath(), "map")
950 m = os.path.join(templatepath(), "map")
951 tmpl = templater(m, common_filters,
951 tmpl = templater(m, common_filters,
952 {"header": header, "footer": footer})
952 {"header": header, "footer": footer})
953
953
954 def entries(**map):
954 def entries(**map):
955 parity = 0
955 parity = 0
956 for name, path in self.repos:
956 for name, path in self.repos:
957 u = ui.ui()
957 u = ui.ui()
958 try:
958 try:
959 u.readconfig(file(os.path.join(path, '.hg', 'hgrc')))
959 u.readconfig(file(os.path.join(path, '.hg', 'hgrc')))
960 except IOError:
960 except IOError:
961 pass
961 pass
962 get = u.config
962 get = u.config
963
963
964 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
964 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
965 .replace("//", "/"))
965 .replace("//", "/"))
966
966
967 yield dict(contact=(get("ui", "username") or # preferred
967 yield dict(contact=(get("ui", "username") or # preferred
968 get("web", "contact") or # deprecated
968 get("web", "contact") or # deprecated
969 get("web", "author", "unknown")), # also
969 get("web", "author", "unknown")), # also
970 name=get("web", "name", name),
970 name=get("web", "name", name),
971 url=url,
971 url=url,
972 parity=parity,
972 parity=parity,
973 shortdesc=get("web", "description", "unknown"),
973 shortdesc=get("web", "description", "unknown"),
974 lastupdate=os.stat(os.path.join(path, ".hg",
974 lastupdate=os.stat(os.path.join(path, ".hg",
975 "00changelog.d")).st_mtime)
975 "00changelog.d")).st_mtime)
976
976
977 parity = 1 - parity
977 parity = 1 - parity
978
978
979 virtual = req.env.get("PATH_INFO", "").strip('/')
979 virtual = req.env.get("PATH_INFO", "").strip('/')
980 if virtual:
980 if virtual:
981 real = dict(self.repos).get(virtual)
981 real = dict(self.repos).get(virtual)
982 if real:
982 if real:
983 hgweb(real).run(req)
983 hgweb(real).run(req)
984 else:
984 else:
985 req.write(tmpl("notfound", repo=virtual))
985 req.write(tmpl("notfound", repo=virtual))
986 else:
986 else:
987 req.write(tmpl("index", entries=entries))
987 req.write(tmpl("index", entries=entries))
@@ -1,74 +1,74 b''
1 # packagescan.py - Helper module for identifing used modules.
1 # packagescan.py - Helper module for identifing used modules.
2 # Used for the py2exe distutil.
2 # Used for the py2exe distutil.
3 #
3 #
4 # Copyright 2005 Volker Kleinfeld <Volker.Kleinfeld@gmx.de>
4 # Copyright 2005 Volker Kleinfeld <Volker.Kleinfeld@gmx.de>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8 import glob
8 import glob
9 import os
9 import os
10 import sys
10 import sys
11 import demandload
11 import demandload
12 import ihooks
12 import ihooks
13
13
14 requiredmodules = {} # Will contain the modules imported by demandload
14 requiredmodules = {} # Will contain the modules imported by demandload
15 def demandload(scope, modules):
15 def demandload(scope, modules):
16 """ fake demandload function that collects the required modules """
16 """ fake demandload function that collects the required modules """
17 for m in modules.split():
17 for m in modules.split():
18 mod = None
18 mod = None
19 mod = __import__(m,scope,scope)
19 mod = __import__(m,scope,scope)
20 scope[m] = mod
20 scope[m] = mod
21 requiredmodules[mod.__name__] = 1
21 requiredmodules[mod.__name__] = 1
22
22
23 def getmodules(libpath,packagename):
23 def getmodules(libpath,packagename):
24 """ helper for finding all required modules of package <packagename> """
24 """ helper for finding all required modules of package <packagename> """
25 # Use the package in the build directory
25 # Use the package in the build directory
26 libpath = os.path.abspath(libpath)
26 libpath = os.path.abspath(libpath)
27 sys.path.insert(0,libpath)
27 sys.path.insert(0,libpath)
28 packdir = os.path.join(libpath,packagename)
28 packdir = os.path.join(libpath,packagename)
29 # A normal import would not find the package in
29 # A normal import would not find the package in
30 # the build directory. ihook is used to force the import.
30 # the build directory. ihook is used to force the import.
31 # After the package is imported the import scope for
31 # After the package is imported the import scope for
32 # the following imports is settled.
32 # the following imports is settled.
33 p = importfrom(packdir)
33 p = importfrom(packdir)
34 globals()[packagename] = p
34 globals()[packagename] = p
35 sys.modules[packagename] = p
35 sys.modules[packagename] = p
36 # Fetch the python modules in the package
36 # Fetch the python modules in the package
37 cwd = os.getcwd()
37 cwd = os.getcwd()
38 os.chdir(packdir)
38 os.chdir(packdir)
39 pymodulefiles = glob.glob('*.py')
39 pymodulefiles = glob.glob('*.py')
40 extmodulefiles = glob.glob('*.pyd')
40 extmodulefiles = glob.glob('*.pyd')
41 os.chdir(cwd)
41 os.chdir(cwd)
42 # Install a fake demandload module
42 # Install a fake demandload module
43 sys.modules['mercurial.demandload'] = sys.modules['mercurial.packagescan']
43 sys.modules['mercurial.demandload'] = sys.modules['mercurial.packagescan']
44 # Import all python modules and by that run the fake demandload
44 # Import all python modules and by that run the fake demandload
45 for m in pymodulefiles:
45 for m in pymodulefiles:
46 if m == '__init__.py': continue
46 if m == '__init__.py': continue
47 tmp = {}
47 tmp = {}
48 mname,ext = os.path.splitext(m)
48 mname,ext = os.path.splitext(m)
49 fullname = packagename+'.'+mname
49 fullname = packagename+'.'+mname
50 __import__(fullname,tmp,tmp)
50 __import__(fullname,tmp,tmp)
51 requiredmodules[fullname] = 1
51 requiredmodules[fullname] = 1
52 # Import all extension modules and by that run the fake demandload
52 # Import all extension modules and by that run the fake demandload
53 for m in extmodulefiles:
53 for m in extmodulefiles:
54 tmp = {}
54 tmp = {}
55 mname,ext = os.path.splitext(m)
55 mname,ext = os.path.splitext(m)
56 fullname = packagename+'.'+mname
56 fullname = packagename+'.'+mname
57 __import__(fullname,tmp,tmp)
57 __import__(fullname,tmp,tmp)
58 requiredmodules[fullname] = 1
58 requiredmodules[fullname] = 1
59 includes = requiredmodules.keys()
59 includes = requiredmodules.keys()
60 return includes
60 return includes
61
61
62 def importfrom(filename):
62 def importfrom(filename):
63 """
63 """
64 import module/package from a named file and returns the module.
64 import module/package from a named file and returns the module.
65 It does not check on sys.modules or includes the module in the scope.
65 It does not check on sys.modules or includes the module in the scope.
66 """
66 """
67 loader = ihooks.BasicModuleLoader()
67 loader = ihooks.BasicModuleLoader()
68 path, file = os.path.split(filename)
68 path, file = os.path.split(filename)
69 name, ext = os.path.splitext(file)
69 name, ext = os.path.splitext(file)
70 m = loader.find_module_in_dir(name, path)
70 m = loader.find_module_in_dir(name, path)
71 if not m:
71 if not m:
72 raise ImportError, name
72 raise ImportError, name
73 m = loader.load_module(name, m)
73 m = loader.load_module(name, m)
74 return m
74 return m
@@ -1,500 +1,500 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8
8
9 This contains helper routines that are independent of the SCM core and hide
9 This contains helper routines that are independent of the SCM core and hide
10 platform-specific details from the core.
10 platform-specific details from the core.
11 """
11 """
12
12
13 import os, errno
13 import os, errno
14 from demandload import *
14 from demandload import *
15 demandload(globals(), "re cStringIO shutil popen2 threading")
15 demandload(globals(), "re cStringIO shutil popen2 threading")
16
16
17 def filter(s, cmd):
17 def filter(s, cmd):
18 "filter a string through a command that transforms its input to its output"
18 "filter a string through a command that transforms its input to its output"
19 (pout, pin) = popen2.popen2(cmd, -1, 'b')
19 (pout, pin) = popen2.popen2(cmd, -1, 'b')
20 def writer():
20 def writer():
21 pin.write(s)
21 pin.write(s)
22 pin.close()
22 pin.close()
23
23
24 # we should use select instead on UNIX, but this will work on most
24 # we should use select instead on UNIX, but this will work on most
25 # systems, including Windows
25 # systems, including Windows
26 w = threading.Thread(target=writer)
26 w = threading.Thread(target=writer)
27 w.start()
27 w.start()
28 f = pout.read()
28 f = pout.read()
29 pout.close()
29 pout.close()
30 w.join()
30 w.join()
31 return f
31 return f
32
32
33 def patch(strip, patchname, ui):
33 def patch(strip, patchname, ui):
34 """apply the patch <patchname> to the working directory.
34 """apply the patch <patchname> to the working directory.
35 a list of patched files is returned"""
35 a list of patched files is returned"""
36 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
36 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
37 files = {}
37 files = {}
38 for line in fp:
38 for line in fp:
39 line = line.rstrip()
39 line = line.rstrip()
40 ui.status("%s\n" % line)
40 ui.status("%s\n" % line)
41 if line.startswith('patching file '):
41 if line.startswith('patching file '):
42 pf = parse_patch_output(line)
42 pf = parse_patch_output(line)
43 files.setdefault(pf, 1)
43 files.setdefault(pf, 1)
44 code = fp.close()
44 code = fp.close()
45 if code:
45 if code:
46 raise Abort("patch command failed: exit status %s " % code)
46 raise Abort("patch command failed: exit status %s " % code)
47 return files.keys()
47 return files.keys()
48
48
49 def binary(s):
49 def binary(s):
50 """return true if a string is binary data using diff's heuristic"""
50 """return true if a string is binary data using diff's heuristic"""
51 if s and '\0' in s[:4096]:
51 if s and '\0' in s[:4096]:
52 return True
52 return True
53 return False
53 return False
54
54
55 def unique(g):
55 def unique(g):
56 """return the uniq elements of iterable g"""
56 """return the uniq elements of iterable g"""
57 seen = {}
57 seen = {}
58 for f in g:
58 for f in g:
59 if f not in seen:
59 if f not in seen:
60 seen[f] = 1
60 seen[f] = 1
61 yield f
61 yield f
62
62
63 class Abort(Exception):
63 class Abort(Exception):
64 """Raised if a command needs to print an error and exit."""
64 """Raised if a command needs to print an error and exit."""
65
65
66 def always(fn): return True
66 def always(fn): return True
67 def never(fn): return False
67 def never(fn): return False
68
68
69 def globre(pat, head='^', tail='$'):
69 def globre(pat, head='^', tail='$'):
70 "convert a glob pattern into a regexp"
70 "convert a glob pattern into a regexp"
71 i, n = 0, len(pat)
71 i, n = 0, len(pat)
72 res = ''
72 res = ''
73 group = False
73 group = False
74 def peek(): return i < n and pat[i]
74 def peek(): return i < n and pat[i]
75 while i < n:
75 while i < n:
76 c = pat[i]
76 c = pat[i]
77 i = i+1
77 i = i+1
78 if c == '*':
78 if c == '*':
79 if peek() == '*':
79 if peek() == '*':
80 i += 1
80 i += 1
81 res += '.*'
81 res += '.*'
82 else:
82 else:
83 res += '[^/]*'
83 res += '[^/]*'
84 elif c == '?':
84 elif c == '?':
85 res += '.'
85 res += '.'
86 elif c == '[':
86 elif c == '[':
87 j = i
87 j = i
88 if j < n and pat[j] in '!]':
88 if j < n and pat[j] in '!]':
89 j += 1
89 j += 1
90 while j < n and pat[j] != ']':
90 while j < n and pat[j] != ']':
91 j += 1
91 j += 1
92 if j >= n:
92 if j >= n:
93 res += '\\['
93 res += '\\['
94 else:
94 else:
95 stuff = pat[i:j].replace('\\','\\\\')
95 stuff = pat[i:j].replace('\\','\\\\')
96 i = j + 1
96 i = j + 1
97 if stuff[0] == '!':
97 if stuff[0] == '!':
98 stuff = '^' + stuff[1:]
98 stuff = '^' + stuff[1:]
99 elif stuff[0] == '^':
99 elif stuff[0] == '^':
100 stuff = '\\' + stuff
100 stuff = '\\' + stuff
101 res = '%s[%s]' % (res, stuff)
101 res = '%s[%s]' % (res, stuff)
102 elif c == '{':
102 elif c == '{':
103 group = True
103 group = True
104 res += '(?:'
104 res += '(?:'
105 elif c == '}' and group:
105 elif c == '}' and group:
106 res += ')'
106 res += ')'
107 group = False
107 group = False
108 elif c == ',' and group:
108 elif c == ',' and group:
109 res += '|'
109 res += '|'
110 else:
110 else:
111 res += re.escape(c)
111 res += re.escape(c)
112 return head + res + tail
112 return head + res + tail
113
113
114 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
114 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
115
115
116 def pathto(n1, n2):
116 def pathto(n1, n2):
117 '''return the relative path from one place to another.
117 '''return the relative path from one place to another.
118 this returns a path in the form used by the local filesystem, not hg.'''
118 this returns a path in the form used by the local filesystem, not hg.'''
119 if not n1: return localpath(n2)
119 if not n1: return localpath(n2)
120 a, b = n1.split('/'), n2.split('/')
120 a, b = n1.split('/'), n2.split('/')
121 a.reverse(), b.reverse()
121 a.reverse(), b.reverse()
122 while a and b and a[-1] == b[-1]:
122 while a and b and a[-1] == b[-1]:
123 a.pop(), b.pop()
123 a.pop(), b.pop()
124 b.reverse()
124 b.reverse()
125 return os.sep.join((['..'] * len(a)) + b)
125 return os.sep.join((['..'] * len(a)) + b)
126
126
127 def canonpath(root, cwd, myname):
127 def canonpath(root, cwd, myname):
128 """return the canonical path of myname, given cwd and root"""
128 """return the canonical path of myname, given cwd and root"""
129 rootsep = root + os.sep
129 rootsep = root + os.sep
130 name = myname
130 name = myname
131 if not name.startswith(os.sep):
131 if not name.startswith(os.sep):
132 name = os.path.join(root, cwd, name)
132 name = os.path.join(root, cwd, name)
133 name = os.path.normpath(name)
133 name = os.path.normpath(name)
134 if name.startswith(rootsep):
134 if name.startswith(rootsep):
135 return pconvert(name[len(rootsep):])
135 return pconvert(name[len(rootsep):])
136 elif name == root:
136 elif name == root:
137 return ''
137 return ''
138 else:
138 else:
139 raise Abort('%s not under root' % myname)
139 raise Abort('%s not under root' % myname)
140
140
141 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
141 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
142 """build a function to match a set of file patterns
142 """build a function to match a set of file patterns
143
143
144 arguments:
144 arguments:
145 canonroot - the canonical root of the tree you're matching against
145 canonroot - the canonical root of the tree you're matching against
146 cwd - the current working directory, if relevant
146 cwd - the current working directory, if relevant
147 names - patterns to find
147 names - patterns to find
148 inc - patterns to include
148 inc - patterns to include
149 exc - patterns to exclude
149 exc - patterns to exclude
150 head - a regex to prepend to patterns to control whether a match is rooted
150 head - a regex to prepend to patterns to control whether a match is rooted
151
151
152 a pattern is one of:
152 a pattern is one of:
153 'glob:<rooted glob>'
153 'glob:<rooted glob>'
154 're:<rooted regexp>'
154 're:<rooted regexp>'
155 'path:<rooted path>'
155 'path:<rooted path>'
156 'relglob:<relative glob>'
156 'relglob:<relative glob>'
157 'relpath:<relative path>'
157 'relpath:<relative path>'
158 'relre:<relative regexp>'
158 'relre:<relative regexp>'
159 '<rooted path or regexp>'
159 '<rooted path or regexp>'
160
160
161 returns:
161 returns:
162 a 3-tuple containing
162 a 3-tuple containing
163 - list of explicit non-pattern names passed in
163 - list of explicit non-pattern names passed in
164 - a bool match(filename) function
164 - a bool match(filename) function
165 - a bool indicating if any patterns were passed in
165 - a bool indicating if any patterns were passed in
166
166
167 todo:
167 todo:
168 make head regex a rooted bool
168 make head regex a rooted bool
169 """
169 """
170
170
171 def patkind(name):
171 def patkind(name):
172 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
172 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
173 if name.startswith(prefix + ':'): return name.split(':', 1)
173 if name.startswith(prefix + ':'): return name.split(':', 1)
174 for c in name:
174 for c in name:
175 if c in _globchars: return 'glob', name
175 if c in _globchars: return 'glob', name
176 return 'relpath', name
176 return 'relpath', name
177
177
178 def regex(kind, name, tail):
178 def regex(kind, name, tail):
179 '''convert a pattern into a regular expression'''
179 '''convert a pattern into a regular expression'''
180 if kind == 're':
180 if kind == 're':
181 return name
181 return name
182 elif kind == 'path':
182 elif kind == 'path':
183 return '^' + re.escape(name) + '(?:/|$)'
183 return '^' + re.escape(name) + '(?:/|$)'
184 elif kind == 'relglob':
184 elif kind == 'relglob':
185 return head + globre(name, '(?:|.*/)', tail)
185 return head + globre(name, '(?:|.*/)', tail)
186 elif kind == 'relpath':
186 elif kind == 'relpath':
187 return head + re.escape(name) + tail
187 return head + re.escape(name) + tail
188 elif kind == 'relre':
188 elif kind == 'relre':
189 if name.startswith('^'):
189 if name.startswith('^'):
190 return name
190 return name
191 return '.*' + name
191 return '.*' + name
192 return head + globre(name, '', tail)
192 return head + globre(name, '', tail)
193
193
194 def matchfn(pats, tail):
194 def matchfn(pats, tail):
195 """build a matching function from a set of patterns"""
195 """build a matching function from a set of patterns"""
196 if pats:
196 if pats:
197 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
197 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
198 return re.compile(pat).match
198 return re.compile(pat).match
199
199
200 def globprefix(pat):
200 def globprefix(pat):
201 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
201 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
202 root = []
202 root = []
203 for p in pat.split(os.sep):
203 for p in pat.split(os.sep):
204 if patkind(p)[0] == 'glob': break
204 if patkind(p)[0] == 'glob': break
205 root.append(p)
205 root.append(p)
206 return '/'.join(root)
206 return '/'.join(root)
207
207
208 pats = []
208 pats = []
209 files = []
209 files = []
210 roots = []
210 roots = []
211 for kind, name in map(patkind, names):
211 for kind, name in map(patkind, names):
212 if kind in ('glob', 'relpath'):
212 if kind in ('glob', 'relpath'):
213 name = canonpath(canonroot, cwd, name)
213 name = canonpath(canonroot, cwd, name)
214 if name == '':
214 if name == '':
215 kind, name = 'glob', '**'
215 kind, name = 'glob', '**'
216 if kind in ('glob', 'path', 're'):
216 if kind in ('glob', 'path', 're'):
217 pats.append((kind, name))
217 pats.append((kind, name))
218 if kind == 'glob':
218 if kind == 'glob':
219 root = globprefix(name)
219 root = globprefix(name)
220 if root: roots.append(root)
220 if root: roots.append(root)
221 elif kind == 'relpath':
221 elif kind == 'relpath':
222 files.append((kind, name))
222 files.append((kind, name))
223 roots.append(name)
223 roots.append(name)
224
224
225 patmatch = matchfn(pats, '$') or always
225 patmatch = matchfn(pats, '$') or always
226 filematch = matchfn(files, '(?:/|$)') or always
226 filematch = matchfn(files, '(?:/|$)') or always
227 incmatch = always
227 incmatch = always
228 if inc:
228 if inc:
229 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
229 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
230 excmatch = lambda fn: False
230 excmatch = lambda fn: False
231 if exc:
231 if exc:
232 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
232 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
233
233
234 return (roots,
234 return (roots,
235 lambda fn: (incmatch(fn) and not excmatch(fn) and
235 lambda fn: (incmatch(fn) and not excmatch(fn) and
236 (fn.endswith('/') or
236 (fn.endswith('/') or
237 (not pats and not files) or
237 (not pats and not files) or
238 (pats and patmatch(fn)) or
238 (pats and patmatch(fn)) or
239 (files and filematch(fn)))),
239 (files and filematch(fn)))),
240 (inc or exc or (pats and pats != [('glob', '**')])) and True)
240 (inc or exc or (pats and pats != [('glob', '**')])) and True)
241
241
242 def system(cmd, errprefix=None):
242 def system(cmd, errprefix=None):
243 """execute a shell command that must succeed"""
243 """execute a shell command that must succeed"""
244 rc = os.system(cmd)
244 rc = os.system(cmd)
245 if rc:
245 if rc:
246 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
246 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
247 explain_exit(rc)[0])
247 explain_exit(rc)[0])
248 if errprefix:
248 if errprefix:
249 errmsg = "%s: %s" % (errprefix, errmsg)
249 errmsg = "%s: %s" % (errprefix, errmsg)
250 raise Abort(errmsg)
250 raise Abort(errmsg)
251
251
252 def rename(src, dst):
252 def rename(src, dst):
253 """forcibly rename a file"""
253 """forcibly rename a file"""
254 try:
254 try:
255 os.rename(src, dst)
255 os.rename(src, dst)
256 except:
256 except:
257 os.unlink(dst)
257 os.unlink(dst)
258 os.rename(src, dst)
258 os.rename(src, dst)
259
259
260 def copyfiles(src, dst, hardlink=None):
260 def copyfiles(src, dst, hardlink=None):
261 """Copy a directory tree using hardlinks if possible"""
261 """Copy a directory tree using hardlinks if possible"""
262
262
263 if hardlink is None:
263 if hardlink is None:
264 hardlink = (os.stat(src).st_dev ==
264 hardlink = (os.stat(src).st_dev ==
265 os.stat(os.path.dirname(dst)).st_dev)
265 os.stat(os.path.dirname(dst)).st_dev)
266
266
267 if os.path.isdir(src):
267 if os.path.isdir(src):
268 os.mkdir(dst)
268 os.mkdir(dst)
269 for name in os.listdir(src):
269 for name in os.listdir(src):
270 srcname = os.path.join(src, name)
270 srcname = os.path.join(src, name)
271 dstname = os.path.join(dst, name)
271 dstname = os.path.join(dst, name)
272 copyfiles(srcname, dstname, hardlink)
272 copyfiles(srcname, dstname, hardlink)
273 else:
273 else:
274 if hardlink:
274 if hardlink:
275 try:
275 try:
276 os_link(src, dst)
276 os_link(src, dst)
277 except:
277 except:
278 hardlink = False
278 hardlink = False
279 shutil.copy2(src, dst)
279 shutil.copy2(src, dst)
280 else:
280 else:
281 shutil.copy2(src, dst)
281 shutil.copy2(src, dst)
282
282
283 def opener(base):
283 def opener(base):
284 """
284 """
285 return a function that opens files relative to base
285 return a function that opens files relative to base
286
286
287 this function is used to hide the details of COW semantics and
287 this function is used to hide the details of COW semantics and
288 remote file access from higher level code.
288 remote file access from higher level code.
289 """
289 """
290 p = base
290 p = base
291 def o(path, mode="r"):
291 def o(path, mode="r"):
292 f = os.path.join(p, path)
292 f = os.path.join(p, path)
293
293
294 mode += "b" # for that other OS
294 mode += "b" # for that other OS
295
295
296 if mode[0] != "r":
296 if mode[0] != "r":
297 try:
297 try:
298 nlink = nlinks(f)
298 nlink = nlinks(f)
299 except OSError:
299 except OSError:
300 d = os.path.dirname(f)
300 d = os.path.dirname(f)
301 if not os.path.isdir(d):
301 if not os.path.isdir(d):
302 os.makedirs(d)
302 os.makedirs(d)
303 else:
303 else:
304 if nlink > 1:
304 if nlink > 1:
305 file(f + ".tmp", "wb").write(file(f, "rb").read())
305 file(f + ".tmp", "wb").write(file(f, "rb").read())
306 rename(f+".tmp", f)
306 rename(f+".tmp", f)
307
307
308 return file(f, mode)
308 return file(f, mode)
309
309
310 return o
310 return o
311
311
312 def _makelock_file(info, pathname):
312 def _makelock_file(info, pathname):
313 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
313 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
314 os.write(ld, info)
314 os.write(ld, info)
315 os.close(ld)
315 os.close(ld)
316
316
317 def _readlock_file(pathname):
317 def _readlock_file(pathname):
318 return file(pathname).read()
318 return file(pathname).read()
319
319
320 def nlinks(pathname):
320 def nlinks(pathname):
321 """Return number of hardlinks for the given file."""
321 """Return number of hardlinks for the given file."""
322 return os.stat(pathname).st_nlink
322 return os.stat(pathname).st_nlink
323
323
324 if hasattr(os, 'link'):
324 if hasattr(os, 'link'):
325 os_link = os.link
325 os_link = os.link
326 else:
326 else:
327 def os_link(src, dst):
327 def os_link(src, dst):
328 raise OSError(0, "Hardlinks not supported")
328 raise OSError(0, "Hardlinks not supported")
329
329
330 # Platform specific variants
330 # Platform specific variants
331 if os.name == 'nt':
331 if os.name == 'nt':
332 nulldev = 'NUL:'
332 nulldev = 'NUL:'
333
333
334 def parse_patch_output(output_line):
334 def parse_patch_output(output_line):
335 """parses the output produced by patch and returns the file name"""
335 """parses the output produced by patch and returns the file name"""
336 pf = output_line[14:]
336 pf = output_line[14:]
337 if pf[0] == '`':
337 if pf[0] == '`':
338 pf = pf[1:-1] # Remove the quotes
338 pf = pf[1:-1] # Remove the quotes
339 return pf
339 return pf
340
340
341 try: # ActivePython can create hard links using win32file module
341 try: # ActivePython can create hard links using win32file module
342 import win32file
342 import win32file
343
343
344 def os_link(src, dst): # NB will only succeed on NTFS
344 def os_link(src, dst): # NB will only succeed on NTFS
345 win32file.CreateHardLink(dst, src)
345 win32file.CreateHardLink(dst, src)
346
346
347 def nlinks(pathname):
347 def nlinks(pathname):
348 """Return number of hardlinks for the given file."""
348 """Return number of hardlinks for the given file."""
349 try:
349 try:
350 fh = win32file.CreateFile(pathname,
350 fh = win32file.CreateFile(pathname,
351 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
351 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
352 None, win32file.OPEN_EXISTING, 0, None)
352 None, win32file.OPEN_EXISTING, 0, None)
353 res = win32file.GetFileInformationByHandle(fh)
353 res = win32file.GetFileInformationByHandle(fh)
354 fh.Close()
354 fh.Close()
355 return res[7]
355 return res[7]
356 except:
356 except:
357 return os.stat(pathname).st_nlink
357 return os.stat(pathname).st_nlink
358
358
359 except ImportError:
359 except ImportError:
360 pass
360 pass
361
361
362 def is_exec(f, last):
362 def is_exec(f, last):
363 return last
363 return last
364
364
365 def set_exec(f, mode):
365 def set_exec(f, mode):
366 pass
366 pass
367
367
368 def pconvert(path):
368 def pconvert(path):
369 return path.replace("\\", "/")
369 return path.replace("\\", "/")
370
370
371 def localpath(path):
371 def localpath(path):
372 return path.replace('/', '\\')
372 return path.replace('/', '\\')
373
373
374 def normpath(path):
374 def normpath(path):
375 return pconvert(os.path.normpath(path))
375 return pconvert(os.path.normpath(path))
376
376
377 makelock = _makelock_file
377 makelock = _makelock_file
378 readlock = _readlock_file
378 readlock = _readlock_file
379
379
380 def explain_exit(code):
380 def explain_exit(code):
381 return "exited with status %d" % code, code
381 return "exited with status %d" % code, code
382
382
383 else:
383 else:
384 nulldev = '/dev/null'
384 nulldev = '/dev/null'
385
385
386 def parse_patch_output(output_line):
386 def parse_patch_output(output_line):
387 """parses the output produced by patch and returns the file name"""
387 """parses the output produced by patch and returns the file name"""
388 return output_line[14:]
388 return output_line[14:]
389
389
390 def is_exec(f, last):
390 def is_exec(f, last):
391 """check whether a file is executable"""
391 """check whether a file is executable"""
392 return (os.stat(f).st_mode & 0100 != 0)
392 return (os.stat(f).st_mode & 0100 != 0)
393
393
394 def set_exec(f, mode):
394 def set_exec(f, mode):
395 s = os.stat(f).st_mode
395 s = os.stat(f).st_mode
396 if (s & 0100 != 0) == mode:
396 if (s & 0100 != 0) == mode:
397 return
397 return
398 if mode:
398 if mode:
399 # Turn on +x for every +r bit when making a file executable
399 # Turn on +x for every +r bit when making a file executable
400 # and obey umask.
400 # and obey umask.
401 umask = os.umask(0)
401 umask = os.umask(0)
402 os.umask(umask)
402 os.umask(umask)
403 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
403 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
404 else:
404 else:
405 os.chmod(f, s & 0666)
405 os.chmod(f, s & 0666)
406
406
407 def pconvert(path):
407 def pconvert(path):
408 return path
408 return path
409
409
410 def localpath(path):
410 def localpath(path):
411 return path
411 return path
412
412
413 normpath = os.path.normpath
413 normpath = os.path.normpath
414
414
415 def makelock(info, pathname):
415 def makelock(info, pathname):
416 try:
416 try:
417 os.symlink(info, pathname)
417 os.symlink(info, pathname)
418 except OSError, why:
418 except OSError, why:
419 if why.errno == errno.EEXIST:
419 if why.errno == errno.EEXIST:
420 raise
420 raise
421 else:
421 else:
422 _makelock_file(info, pathname)
422 _makelock_file(info, pathname)
423
423
424 def readlock(pathname):
424 def readlock(pathname):
425 try:
425 try:
426 return os.readlink(pathname)
426 return os.readlink(pathname)
427 except OSError, why:
427 except OSError, why:
428 if why.errno == errno.EINVAL:
428 if why.errno == errno.EINVAL:
429 return _readlock_file(pathname)
429 return _readlock_file(pathname)
430 else:
430 else:
431 raise
431 raise
432
432
433 def explain_exit(code):
433 def explain_exit(code):
434 """return a 2-tuple (desc, code) describing a process's status"""
434 """return a 2-tuple (desc, code) describing a process's status"""
435 if os.WIFEXITED(code):
435 if os.WIFEXITED(code):
436 val = os.WEXITSTATUS(code)
436 val = os.WEXITSTATUS(code)
437 return "exited with status %d" % val, val
437 return "exited with status %d" % val, val
438 elif os.WIFSIGNALED(code):
438 elif os.WIFSIGNALED(code):
439 val = os.WTERMSIG(code)
439 val = os.WTERMSIG(code)
440 return "killed by signal %d" % val, val
440 return "killed by signal %d" % val, val
441 elif os.WIFSTOPPED(code):
441 elif os.WIFSTOPPED(code):
442 val = os.WSTOPSIG(code)
442 val = os.WSTOPSIG(code)
443 return "stopped by signal %d" % val, val
443 return "stopped by signal %d" % val, val
444 raise ValueError("invalid exit code")
444 raise ValueError("invalid exit code")
445
445
446 class chunkbuffer(object):
446 class chunkbuffer(object):
447 """Allow arbitrary sized chunks of data to be efficiently read from an
447 """Allow arbitrary sized chunks of data to be efficiently read from an
448 iterator over chunks of arbitrary size."""
448 iterator over chunks of arbitrary size."""
449
449
450 def __init__(self, in_iter, targetsize = 2**16):
450 def __init__(self, in_iter, targetsize = 2**16):
451 """in_iter is the iterator that's iterating over the input chunks.
451 """in_iter is the iterator that's iterating over the input chunks.
452 targetsize is how big a buffer to try to maintain."""
452 targetsize is how big a buffer to try to maintain."""
453 self.in_iter = iter(in_iter)
453 self.in_iter = iter(in_iter)
454 self.buf = ''
454 self.buf = ''
455 self.targetsize = int(targetsize)
455 self.targetsize = int(targetsize)
456 if self.targetsize <= 0:
456 if self.targetsize <= 0:
457 raise ValueError("targetsize must be greater than 0, was %d" %
457 raise ValueError("targetsize must be greater than 0, was %d" %
458 targetsize)
458 targetsize)
459 self.iterempty = False
459 self.iterempty = False
460
460
461 def fillbuf(self):
461 def fillbuf(self):
462 """Ignore target size; read every chunk from iterator until empty."""
462 """Ignore target size; read every chunk from iterator until empty."""
463 if not self.iterempty:
463 if not self.iterempty:
464 collector = cStringIO.StringIO()
464 collector = cStringIO.StringIO()
465 collector.write(self.buf)
465 collector.write(self.buf)
466 for ch in self.in_iter:
466 for ch in self.in_iter:
467 collector.write(ch)
467 collector.write(ch)
468 self.buf = collector.getvalue()
468 self.buf = collector.getvalue()
469 self.iterempty = True
469 self.iterempty = True
470
470
471 def read(self, l):
471 def read(self, l):
472 """Read L bytes of data from the iterator of chunks of data.
472 """Read L bytes of data from the iterator of chunks of data.
473 Returns less than L bytes if the iterator runs dry."""
473 Returns less than L bytes if the iterator runs dry."""
474 if l > len(self.buf) and not self.iterempty:
474 if l > len(self.buf) and not self.iterempty:
475 # Clamp to a multiple of self.targetsize
475 # Clamp to a multiple of self.targetsize
476 targetsize = self.targetsize * ((l // self.targetsize) + 1)
476 targetsize = self.targetsize * ((l // self.targetsize) + 1)
477 collector = cStringIO.StringIO()
477 collector = cStringIO.StringIO()
478 collector.write(self.buf)
478 collector.write(self.buf)
479 collected = len(self.buf)
479 collected = len(self.buf)
480 for chunk in self.in_iter:
480 for chunk in self.in_iter:
481 collector.write(chunk)
481 collector.write(chunk)
482 collected += len(chunk)
482 collected += len(chunk)
483 if collected >= targetsize:
483 if collected >= targetsize:
484 break
484 break
485 if collected < targetsize:
485 if collected < targetsize:
486 self.iterempty = True
486 self.iterempty = True
487 self.buf = collector.getvalue()
487 self.buf = collector.getvalue()
488 s, self.buf = self.buf[:l], buffer(self.buf, l)
488 s, self.buf = self.buf[:l], buffer(self.buf, l)
489 return s
489 return s
490
490
491 def filechunkiter(f, size = 65536):
491 def filechunkiter(f, size = 65536):
492 """Create a generator that produces all the data in the file size
492 """Create a generator that produces all the data in the file size
493 (default 65536) bytes at a time. Chunks may be less than size
493 (default 65536) bytes at a time. Chunks may be less than size
494 bytes if the chunk is the last chunk in the file, or the file is a
494 bytes if the chunk is the last chunk in the file, or the file is a
495 socket or some other type of file that sometimes reads less data
495 socket or some other type of file that sometimes reads less data
496 than is requested."""
496 than is requested."""
497 s = f.read(size)
497 s = f.read(size)
498 while len(s) >= 0:
498 while len(s) >= 0:
499 yield s
499 yield s
500 s = f.read(size)
500 s = f.read(size)
@@ -1,146 +1,146 b''
1 Some notes about Mercurial's design
1 Some notes about Mercurial's design
2
2
3 Revlogs:
3 Revlogs:
4
4
5 The fundamental storage type in Mercurial is a "revlog". A revlog is
5 The fundamental storage type in Mercurial is a "revlog". A revlog is
6 the set of all revisions to a file. Each revision is either stored
6 the set of all revisions to a file. Each revision is either stored
7 compressed in its entirety or as a compressed binary delta against the
7 compressed in its entirety or as a compressed binary delta against the
8 previous version. The decision of when to store a full version is made
8 previous version. The decision of when to store a full version is made
9 based on how much data would be needed to reconstruct the file. This
9 based on how much data would be needed to reconstruct the file. This
10 lets us ensure that we never need to read huge amounts of data to
10 lets us ensure that we never need to read huge amounts of data to
11 reconstruct a file, regardless of how many revisions of it we store.
11 reconstruct a file, regardless of how many revisions of it we store.
12
12
13 In fact, we should always be able to do it with a single read,
13 In fact, we should always be able to do it with a single read,
14 provided we know when and where to read. This is where the index comes
14 provided we know when and where to read. This is where the index comes
15 in. Each revlog has an index containing a special hash (nodeid) of the
15 in. Each revlog has an index containing a special hash (nodeid) of the
16 text, hashes for its parents, and where and how much of the revlog
16 text, hashes for its parents, and where and how much of the revlog
17 data we need to read to reconstruct it. Thus, with one read of the
17 data we need to read to reconstruct it. Thus, with one read of the
18 index and one read of the data, we can reconstruct any version in time
18 index and one read of the data, we can reconstruct any version in time
19 proportional to the file size.
19 proportional to the file size.
20
20
21 Similarly, revlogs and their indices are append-only. This means that
21 Similarly, revlogs and their indices are append-only. This means that
22 adding a new version is also O(1) seeks.
22 adding a new version is also O(1) seeks.
23
23
24 Generally revlogs are used to represent revisions of files, but they
24 Generally revlogs are used to represent revisions of files, but they
25 also are used to represent manifests and changesets.
25 also are used to represent manifests and changesets.
26
26
27 Manifests:
27 Manifests:
28
28
29 A manifest is simply a list of all files in a given revision of a
29 A manifest is simply a list of all files in a given revision of a
30 project along with the nodeids of the corresponding file revisions. So
30 project along with the nodeids of the corresponding file revisions. So
31 grabbing a given version of the project means simply looking up its
31 grabbing a given version of the project means simply looking up its
32 manifest and reconstruction all the file revisions pointed to by it.
32 manifest and reconstruction all the file revisions pointed to by it.
33
33
34 Changesets:
34 Changesets:
35
35
36 A changeset is a list of all files changed in a check-in along with a
36 A changeset is a list of all files changed in a check-in along with a
37 change description and some metadata like user and date. It also
37 change description and some metadata like user and date. It also
38 contains a nodeid to the relevent revision of the manifest. Changesets
38 contains a nodeid to the relevent revision of the manifest. Changesets
39 and manifests are one-to-one, but contain different data for
39 and manifests are one-to-one, but contain different data for
40 convenience.
40 convenience.
41
41
42 Nodeids:
42 Nodeids:
43
43
44 Nodeids are unique ids that are used to represent the contents of a
44 Nodeids are unique ids that are used to represent the contents of a
45 file AND its position in the project history. That is, if you change a
45 file AND its position in the project history. That is, if you change a
46 file and then change it back, the result will have a different nodeid
46 file and then change it back, the result will have a different nodeid
47 because it has different history. This is accomplished by including
47 because it has different history. This is accomplished by including
48 the parents of a given revision's nodeids with the revision's text
48 the parents of a given revision's nodeids with the revision's text
49 when calculating the hash.
49 when calculating the hash.
50
50
51 Graph merging:
51 Graph merging:
52
52
53 Nodeids are implemented as they are to simplify merging. Merging a
53 Nodeids are implemented as they are to simplify merging. Merging a
54 pair of directed acyclic graphs (aka "the family tree" of the file
54 pair of directed acyclic graphs (aka "the family tree" of the file
55 history) requires some method of determining if nodes in different
55 history) requires some method of determining if nodes in different
56 graphs correspond. Simply comparing the contents of the node (by
56 graphs correspond. Simply comparing the contents of the node (by
57 comparing text of given revisions or their hashes) can get confused by
57 comparing text of given revisions or their hashes) can get confused by
58 identical revisions in the tree.
58 identical revisions in the tree.
59
59
60 The nodeid approach makes it trivial - the hash uniquely describes a
60 The nodeid approach makes it trivial - the hash uniquely describes a
61 revision's contents and its graph position relative to the root, so
61 revision's contents and its graph position relative to the root, so
62 merge is simply checking whether each nodeid in graph A is in the hash
62 merge is simply checking whether each nodeid in graph A is in the hash
63 table of graph B. If not, we pull them in, adding them sequentially to
63 table of graph B. If not, we pull them in, adding them sequentially to
64 the revlog.
64 the revlog.
65
65
66 Branching and merging:
66 Branching and merging:
67
67
68 Everything in Mercurial is potentially a branch and every user
68 Everything in Mercurial is potentially a branch and every user
69 effectively works in their own branch. When you do a checkout,
69 effectively works in their own branch. When you do a checkout,
70 Mercurial remembers what the parent changeset was and uses it for the
70 Mercurial remembers what the parent changeset was and uses it for the
71 next check in.
71 next check in.
72
72
73 To do a merge of branches in Mercurial, you check out the heads of the
73 To do a merge of branches in Mercurial, you check out the heads of the
74 two branches into the same working directory which causes a merge to
74 two branches into the same working directory which causes a merge to
75 be performed, and then check in the result once you're happy with it.
75 be performed, and then check in the result once you're happy with it.
76 The resulting checkin will have two parents.
76 The resulting checkin will have two parents.
77
77
78 It decides when a merge is necessary by first determining if there are
78 It decides when a merge is necessary by first determining if there are
79 any uncommitted changes in the working directory. This effectively
79 any uncommitted changes in the working directory. This effectively
80 makes the working directory a branch off the checked in version it's
80 makes the working directory a branch off the checked in version it's
81 based on. Then it also determines if the working directory is a direct
81 based on. Then it also determines if the working directory is a direct
82 ancestor or descendent of the second version we're attempting to
82 ancestor or descendent of the second version we're attempting to
83 checkout. If neither is true, we simply replace the working directory
83 checkout. If neither is true, we simply replace the working directory
84 version with the new version. Otherwise we perform a merge between the
84 version with the new version. Otherwise we perform a merge between the
85 two versions.
85 two versions.
86
86
87 Merging files and manifests:
87 Merging files and manifests:
88
88
89 We begin by comparing two versions manifests and deciding which files
89 We begin by comparing two versions manifests and deciding which files
90 need to be added, deleted, and merged.
90 need to be added, deleted, and merged.
91
91
92 Then for each file, we perform a graph merge and resolve as above.
92 Then for each file, we perform a graph merge and resolve as above.
93 It's important to merge files using per-file DAGs rather than just
93 It's important to merge files using per-file DAGs rather than just
94 changeset level DAGs as this diagram illustrates:
94 changeset level DAGs as this diagram illustrates:
95
95
96 M M1 M2
96 M M1 M2
97
97
98 AB
98 AB
99 |`-------v M2 clones M
99 |`-------v M2 clones M
100 aB AB file A is change in mainline
100 aB AB file A is change in mainline
101 |`---v AB' file B is changed in M2
101 |`---v AB' file B is changed in M2
102 | aB / | M1 clones M
102 | aB / | M1 clones M
103 | ab/ | M1 changes B
103 | ab/ | M1 changes B
104 | ab' | M1 merges from M2, changes to B conflict
104 | ab' | M1 merges from M2, changes to B conflict
105 | | A'B' M2 changes A
105 | | A'B' M2 changes A
106 `---+--.|
106 `---+--.|
107 | a'B' M2 merges from mainline, changes to A conflict
107 | a'B' M2 merges from mainline, changes to A conflict
108 `--.|
108 `--.|
109 ??? depending on which ancestor we choose, we will have
109 ??? depending on which ancestor we choose, we will have
110 to redo A hand-merge, B hand-merge, or both
110 to redo A hand-merge, B hand-merge, or both
111 but if we look at the files independently, everything
111 but if we look at the files independently, everything
112 is fine
112 is fine
113
113
114 The result is a merged version in the working directory, waiting for
114 The result is a merged version in the working directory, waiting for
115 check-in.
115 check-in.
116
116
117 Rollback:
117 Rollback:
118
118
119 When performing a commit or a merge, we order things so that the
119 When performing a commit or a merge, we order things so that the
120 changeset entry gets added last. We keep a transaction log of the name
120 changeset entry gets added last. We keep a transaction log of the name
121 of each file touched and its length prior to the transaction. On
121 of each file touched and its length prior to the transaction. On
122 abort, we simply truncate each file to its prior length. This is one
122 abort, we simply truncate each file to its prior length. This is one
123 of the nice properties of the append-only structure of the revlogs.
123 of the nice properties of the append-only structure of the revlogs.
124 We can also reuse this journal for "undo".
124 We can also reuse this journal for "undo".
125
125
126 Merging between repositories:
126 Merging between repositories:
127
127
128 One of the key features of Mercurial is the ability to merge between
128 One of the key features of Mercurial is the ability to merge between
129 independent repositories in a decentralized fashion. Each repository
129 independent repositories in a decentralized fashion. Each repository
130 can act as a read-only server or a client. Clients operating by
130 can act as a read-only server or a client. Clients operating by
131 pulling all branches that it hasn't seen from the server and adding
131 pulling all branches that it hasn't seen from the server and adding
132 them into its graph. This is done in two steps: searching for new
132 them into its graph. This is done in two steps: searching for new
133 "roots" and pulling a "changegroup"
133 "roots" and pulling a "changegroup"
134
134
135 Searching for new "roots" begins by finding all new heads and
135 Searching for new "roots" begins by finding all new heads and
136 searching backwards from those heads to the first unknown nodes in
136 searching backwards from those heads to the first unknown nodes in
137 their respective branches. These nodes are the 'roots' that are used
137 their respective branches. These nodes are the 'roots' that are used
138 to calculate the 'changegroup': the set of all changesets starting at
138 to calculate the 'changegroup': the set of all changesets starting at
139 those roots. Mercurial takes pains to make this search efficient in
139 those roots. Mercurial takes pains to make this search efficient in
140 both bandwidth and round-trips.
140 both bandwidth and round-trips.
141
141
142 Once the roots are found, the changegroup can be transferred as a
142 Once the roots are found, the changegroup can be transferred as a
143 single streaming transfer. This is organized as an ordered set of
143 single streaming transfer. This is organized as an ordered set of
144 deltas for changesets, manifests, and files. Large chunks of deltas
144 deltas for changesets, manifests, and files. Large chunks of deltas
145 can be directly added to the repository without unpacking so it's
145 can be directly added to the repository without unpacking so it's
146 fairly fast.
146 fairly fast.
@@ -1,75 +1,75 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # This is the mercurial setup script.
3 # This is the mercurial setup script.
4 #
4 #
5 # './setup.py install', or
5 # './setup.py install', or
6 # './setup.py --help' for more options
6 # './setup.py --help' for more options
7
7
8 import glob
8 import glob
9 from distutils.core import setup, Extension
9 from distutils.core import setup, Extension
10 from distutils.command.install_data import install_data
10 from distutils.command.install_data import install_data
11
11
12 import mercurial.version
12 import mercurial.version
13
13
14 # py2exe needs to be installed to work
14 # py2exe needs to be installed to work
15 try:
15 try:
16 import py2exe
16 import py2exe
17
17
18 # Due to the use of demandload py2exe is not finding the modules.
18 # Due to the use of demandload py2exe is not finding the modules.
19 # packagescan.getmodules creates a list of modules included in
19 # packagescan.getmodules creates a list of modules included in
20 # the mercurial package plus depdent modules.
20 # the mercurial package plus depdent modules.
21 import mercurial.packagescan
21 import mercurial.packagescan
22 from py2exe.build_exe import py2exe as build_exe
22 from py2exe.build_exe import py2exe as build_exe
23
23
24 class py2exe_for_demandload(build_exe):
24 class py2exe_for_demandload(build_exe):
25 """ overwrites the py2exe command class for getting the build
25 """ overwrites the py2exe command class for getting the build
26 directory and for setting the 'includes' option."""
26 directory and for setting the 'includes' option."""
27 def initialize_options(self):
27 def initialize_options(self):
28 self.build_lib = None
28 self.build_lib = None
29 build_exe.initialize_options(self)
29 build_exe.initialize_options(self)
30 def finalize_options(self):
30 def finalize_options(self):
31 # Get the build directory, ie. where to search for modules.
31 # Get the build directory, ie. where to search for modules.
32 self.set_undefined_options('build',
32 self.set_undefined_options('build',
33 ('build_lib', 'build_lib'))
33 ('build_lib', 'build_lib'))
34 # Sets the 'includes' option with the list of needed modules
34 # Sets the 'includes' option with the list of needed modules
35 if not self.includes:
35 if not self.includes:
36 self.includes = []
36 self.includes = []
37 self.includes += mercurial.packagescan.getmodules(self.build_lib,'mercurial')
37 self.includes += mercurial.packagescan.getmodules(self.build_lib,'mercurial')
38 build_exe.finalize_options(self)
38 build_exe.finalize_options(self)
39 except ImportError:
39 except ImportError:
40 py2exe_for_demandload = None
40 py2exe_for_demandload = None
41
41
42
42
43 # specify version string, otherwise 'hg identify' will be used:
43 # specify version string, otherwise 'hg identify' will be used:
44 version = ''
44 version = ''
45
45
46 class install_package_data(install_data):
46 class install_package_data(install_data):
47 def finalize_options(self):
47 def finalize_options(self):
48 self.set_undefined_options('install',
48 self.set_undefined_options('install',
49 ('install_lib', 'install_dir'))
49 ('install_lib', 'install_dir'))
50 install_data.finalize_options(self)
50 install_data.finalize_options(self)
51
51
52 try:
52 try:
53 mercurial.version.remember_version(version)
53 mercurial.version.remember_version(version)
54 cmdclass = {'install_data': install_package_data}
54 cmdclass = {'install_data': install_package_data}
55 if py2exe_for_demandload is not None:
55 if py2exe_for_demandload is not None:
56 cmdclass['py2exe'] = py2exe_for_demandload
56 cmdclass['py2exe'] = py2exe_for_demandload
57 setup(name='mercurial',
57 setup(name='mercurial',
58 version=mercurial.version.get_version(),
58 version=mercurial.version.get_version(),
59 author='Matt Mackall',
59 author='Matt Mackall',
60 author_email='mpm@selenic.com',
60 author_email='mpm@selenic.com',
61 url='http://selenic.com/mercurial',
61 url='http://selenic.com/mercurial',
62 description='scalable distributed SCM',
62 description='scalable distributed SCM',
63 license='GNU GPL',
63 license='GNU GPL',
64 packages=['mercurial'],
64 packages=['mercurial'],
65 ext_modules=[Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
65 ext_modules=[Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
66 Extension('mercurial.bdiff', ['mercurial/bdiff.c'])],
66 Extension('mercurial.bdiff', ['mercurial/bdiff.c'])],
67 data_files=[('mercurial/templates',
67 data_files=[('mercurial/templates',
68 ['templates/map'] +
68 ['templates/map'] +
69 glob.glob('templates/map-*') +
69 glob.glob('templates/map-*') +
70 glob.glob('templates/*.tmpl'))],
70 glob.glob('templates/*.tmpl'))],
71 cmdclass=cmdclass,
71 cmdclass=cmdclass,
72 scripts=['hg', 'hgmerge'],
72 scripts=['hg', 'hgmerge'],
73 console = ['hg'])
73 console = ['hg'])
74 finally:
74 finally:
75 mercurial.version.forget_version()
75 mercurial.version.forget_version()
@@ -1,54 +1,54 b''
1 Content-type: text/html
1 Content-type: text/html
2
2
3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4 <html>
4 <html>
5 <head>
5 <head>
6 <style type="text/css">
6 <style type="text/css">
7 <!--
7 <!--
8 a { text-decoration:none; }
8 a { text-decoration:none; }
9 .parity0 { background-color: #dddddd; }
9 .parity0 { background-color: #dddddd; }
10 .parity1 { background-color: #eeeeee; }
10 .parity1 { background-color: #eeeeee; }
11 .lineno { width: 60px; color: #aaaaaa; font-size: smaller; }
11 .lineno { width: 60px; color: #aaaaaa; font-size: smaller; }
12 .plusline { color: green; }
12 .plusline { color: green; }
13 .minusline { color: red; }
13 .minusline { color: red; }
14 .atline { color: purple; }
14 .atline { color: purple; }
15 .annotate { font-size: smaller; text-align: right; padding-right: 1em; }
15 .annotate { font-size: smaller; text-align: right; padding-right: 1em; }
16 .buttons a {
16 .buttons a {
17 background-color: #666666;
17 background-color: #666666;
18 padding: 2pt;
18 padding: 2pt;
19 color: white;
19 color: white;
20 font-family: sans;
20 font-family: sans;
21 font-weight: bold;
21 font-weight: bold;
22 }
22 }
23 .metatag {
23 .metatag {
24 background-color: #888888;
24 background-color: #888888;
25 color: white;
25 color: white;
26 text-align: right;
26 text-align: right;
27 }
27 }
28
28
29 /* Common */
29 /* Common */
30 pre { margin: 0; }
30 pre { margin: 0; }
31
31
32
32
33 /* Changelog entries */
33 /* Changelog entries */
34 .changelogEntry { width: 100%; }
34 .changelogEntry { width: 100%; }
35 .changelogEntry th { font-weight: normal; text-align: right; vertical-align: top; width: 15%;}
35 .changelogEntry th { font-weight: normal; text-align: right; vertical-align: top; width: 15%;}
36 .changelogEntry th.age, .changelogEntry th.firstline { font-weight: bold; }
36 .changelogEntry th.age, .changelogEntry th.firstline { font-weight: bold; }
37 .changelogEntry th.firstline { text-align: left; width: inherit; }
37 .changelogEntry th.firstline { text-align: left; width: inherit; }
38
38
39 /* Tag entries */
39 /* Tag entries */
40 #tagEntries { list-style: none; margin: 0; padding: 0; }
40 #tagEntries { list-style: none; margin: 0; padding: 0; }
41 #tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
41 #tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
42 #tagEntries .tagEntry span.node { font-family: monospace; }
42 #tagEntries .tagEntry span.node { font-family: monospace; }
43
43
44 /* Changeset entry */
44 /* Changeset entry */
45 #changesetEntry { }
45 #changesetEntry { }
46 #changesetEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
46 #changesetEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
47 #changesetEntry th.files, #changesetEntry th.description { vertical-align: top; }
47 #changesetEntry th.files, #changesetEntry th.description { vertical-align: top; }
48
48
49 /* File diff view */
49 /* File diff view */
50 #filediffEntry { }
50 #filediffEntry { }
51 #filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
51 #filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
52
52
53 -->
53 -->
54 </style>
54 </style>
@@ -1,145 +1,145 b''
1 #!/bin/sh -e
1 #!/bin/sh -e
2
2
3 LANG="C"; export LANG
3 LANG="C"; export LANG
4 LC_CTYPE="C"; export LC_CTYPE
4 LC_CTYPE="C"; export LC_CTYPE
5 LC_NUMERIC="C"; export LC_NUMERIC
5 LC_NUMERIC="C"; export LC_NUMERIC
6 LC_TIME="C"; export LC_TIME
6 LC_TIME="C"; export LC_TIME
7 LC_COLLATE="C"; export LC_COLLATE
7 LC_COLLATE="C"; export LC_COLLATE
8 LC_MONETARY="C"; export LC_MONETARY
8 LC_MONETARY="C"; export LC_MONETARY
9 LC_MESSAGES="C"; export LC_MESSAGES
9 LC_MESSAGES="C"; export LC_MESSAGES
10 LC_PAPER="C"; export LC_PAPER
10 LC_PAPER="C"; export LC_PAPER
11 LC_NAME="C"; export LC_NAME
11 LC_NAME="C"; export LC_NAME
12 LC_ADDRESS="C"; export LC_ADDRESS
12 LC_ADDRESS="C"; export LC_ADDRESS
13 LC_TELEPHONE="C"; export LC_TELEPHONE
13 LC_TELEPHONE="C"; export LC_TELEPHONE
14 LC_MEASUREMENT="C"; export LC_MEASUREMENT
14 LC_MEASUREMENT="C"; export LC_MEASUREMENT
15 LC_IDENTIFICATION="C"; export LC_IDENTIFICATION
15 LC_IDENTIFICATION="C"; export LC_IDENTIFICATION
16 LC_ALL=""; export LC_ALL
16 LC_ALL=""; export LC_ALL
17 TZ=GMT; export TZ
17 TZ=GMT; export TZ
18 HGEDITOR=true; export HGEDITOR
18 HGEDITOR=true; export HGEDITOR
19 HGMERGE=true; export HGMERGE
19 HGMERGE=true; export HGMERGE
20 HGUSER="test"; export HGUSER
20 HGUSER="test"; export HGUSER
21
21
22 umask 022
22 umask 022
23
23
24 tests=0
24 tests=0
25 failed=0
25 failed=0
26
26
27 HGTMP=""
27 HGTMP=""
28 cleanup_exit() {
28 cleanup_exit() {
29 rm -rf "$HGTMP"
29 rm -rf "$HGTMP"
30 }
30 }
31
31
32 # Remove temporary files even if we get interrupted
32 # Remove temporary files even if we get interrupted
33 trap "cleanup_exit" 0 # normal exit
33 trap "cleanup_exit" 0 # normal exit
34 trap "exit 255" 1 2 3 6 15 # HUP INT QUIT ABRT TERM
34 trap "exit 255" 1 2 3 6 15 # HUP INT QUIT ABRT TERM
35
35
36 HGTMP="${TMPDIR-/tmp}/hgtests.$RANDOM.$RANDOM.$RANDOM.$$"
36 HGTMP="${TMPDIR-/tmp}/hgtests.$RANDOM.$RANDOM.$RANDOM.$$"
37 (umask 077 && mkdir "$HGTMP") || {
37 (umask 077 && mkdir "$HGTMP") || {
38 echo "Could not create temporary directory! Exiting." 1>&2
38 echo "Could not create temporary directory! Exiting." 1>&2
39 exit 1
39 exit 1
40 }
40 }
41
41
42 TESTDIR="$PWD"
42 TESTDIR="$PWD"
43
43
44 if [ -d /usr/lib64 ]; then
44 if [ -d /usr/lib64 ]; then
45 lib=lib64
45 lib=lib64
46 else
46 else
47 lib=lib
47 lib=lib
48 fi
48 fi
49
49
50 INST="$HGTMP/install"
50 INST="$HGTMP/install"
51 cd ..
51 cd ..
52 if ${PYTHON-python} setup.py install --home="$INST" > tests/install.err 2>&1
52 if ${PYTHON-python} setup.py install --home="$INST" > tests/install.err 2>&1
53 then
53 then
54 rm tests/install.err
54 rm tests/install.err
55 else
55 else
56 cat tests/install.err
56 cat tests/install.err
57 exit 1
57 exit 1
58 fi
58 fi
59 cd "$TESTDIR"
59 cd "$TESTDIR"
60
60
61 PATH="$INST/bin:$PATH"; export PATH
61 PATH="$INST/bin:$PATH"; export PATH
62 PYTHONPATH="$INST/$lib/python"; export PYTHONPATH
62 PYTHONPATH="$INST/$lib/python"; export PYTHONPATH
63
63
64
64
65 run_one() {
65 run_one() {
66 rm -f "$1.err"
66 rm -f "$1.err"
67
67
68 mkdir "$HGTMP/$1"
68 mkdir "$HGTMP/$1"
69 cd "$HGTMP/$1"
69 cd "$HGTMP/$1"
70 fail=0
70 fail=0
71 HOME="$HGTMP/$1"; export HOME
71 HOME="$HGTMP/$1"; export HOME
72 OUT="$HGTMP/$1.out"
72 OUT="$HGTMP/$1.out"
73 OUTOK="$TESTDIR/$1.out"
73 OUTOK="$TESTDIR/$1.out"
74 ERR="$TESTDIR/$1.err"
74 ERR="$TESTDIR/$1.err"
75
75
76 if "$TESTDIR/$1" > "$OUT" 2>&1; then
76 if "$TESTDIR/$1" > "$OUT" 2>&1; then
77 : no error
77 : no error
78 else
78 else
79 echo "$1 failed with error code $?"
79 echo "$1 failed with error code $?"
80 fail=1
80 fail=1
81 fi
81 fi
82
82
83 if [ -s "$OUT" -a ! -s "$OUTOK" ] ; then
83 if [ -s "$OUT" -a ! -s "$OUTOK" ] ; then
84 cp "$OUT" "$ERR"
84 cp "$OUT" "$ERR"
85 echo
85 echo
86 echo "$1 generated unexpected output:"
86 echo "$1 generated unexpected output:"
87 cat "$ERR"
87 cat "$ERR"
88 fail=1
88 fail=1
89 elif [ -r "$OUTOK" ]; then
89 elif [ -r "$OUTOK" ]; then
90 if diff -u "$OUTOK" "$OUT" > /dev/null; then
90 if diff -u "$OUTOK" "$OUT" > /dev/null; then
91 : no differences
91 : no differences
92 else
92 else
93 cp "$OUT" "$ERR"
93 cp "$OUT" "$ERR"
94 echo
94 echo
95 echo "$1 output changed:"
95 echo "$1 output changed:"
96 diff -u "$OUTOK" "$ERR" || true
96 diff -u "$OUTOK" "$ERR" || true
97 fail=1
97 fail=1
98 fi
98 fi
99 fi
99 fi
100
100
101 cd "$TESTDIR"
101 cd "$TESTDIR"
102 rm -f "$HGTMP/$1.out"
102 rm -f "$HGTMP/$1.out"
103 rm -rf "$HGTMP/$1"
103 rm -rf "$HGTMP/$1"
104 return $fail
104 return $fail
105 }
105 }
106
106
107 # list of prerequisite programs
107 # list of prerequisite programs
108 # stuff from coreutils (cat, rm, etc) are not tested
108 # stuff from coreutils (cat, rm, etc) are not tested
109 prereqs="python merge diff grep unzip md5sum gunzip sed"
109 prereqs="python merge diff grep unzip md5sum gunzip sed"
110 missing=''
110 missing=''
111 for pre in $prereqs ; do
111 for pre in $prereqs ; do
112 if type $pre > /dev/null 2>&1 ; then
112 if type $pre > /dev/null 2>&1 ; then
113 : prereq exists
113 : prereq exists
114 else
114 else
115 missing="$pre $missing"
115 missing="$pre $missing"
116 fi
116 fi
117 done
117 done
118
118
119 if [ "$missing" != '' ] ; then
119 if [ "$missing" != '' ] ; then
120 echo "ERROR: the test suite needs some programs to execute correctly."
120 echo "ERROR: the test suite needs some programs to execute correctly."
121 echo "The following programs are missing: "
121 echo "The following programs are missing: "
122 for pre in $missing; do
122 for pre in $missing; do
123 echo " $pre"
123 echo " $pre"
124 done
124 done
125 exit 1
125 exit 1
126 fi
126 fi
127
127
128 TESTS="$*"
128 TESTS="$*"
129 if [ -z "$TESTS" ] ; then
129 if [ -z "$TESTS" ] ; then
130 TESTS=`ls test-* | grep -v "[.~]"`
130 TESTS=`ls test-* | grep -v "[.~]"`
131 fi
131 fi
132
132
133 for f in $TESTS ; do
133 for f in $TESTS ; do
134 echo -n "."
134 echo -n "."
135 run_one $f || failed=`expr $failed + 1`
135 run_one $f || failed=`expr $failed + 1`
136 tests=`expr $tests + 1`
136 tests=`expr $tests + 1`
137 done
137 done
138
138
139 echo
139 echo
140 echo "Ran $tests tests, $failed failed."
140 echo "Ran $tests tests, $failed failed."
141
141
142 if [ $failed -gt 0 ] ; then
142 if [ $failed -gt 0 ] ; then
143 exit 1
143 exit 1
144 fi
144 fi
145 exit 0
145 exit 0
@@ -1,33 +1,33 b''
1 #!/bin/sh
1 #!/bin/sh
2 hg --debug init
2 hg --debug init
3 echo this is a1 > a
3 echo this is a1 > a
4 hg add a
4 hg add a
5 hg commit -m0 -d "0 0"
5 hg commit -m0 -d "0 0"
6 echo this is b1 > b
6 echo this is b1 > b
7 hg add b
7 hg add b
8 hg commit -m1 -d "0 0"
8 hg commit -m1 -d "0 0"
9 hg manifest 1
9 hg manifest 1
10 echo this is c1 > c
10 echo this is c1 > c
11 hg rawcommit -p 1 -d "0 0" -m2 c
11 hg rawcommit -p 1 -d "0 0" -m2 c
12 hg manifest 2
12 hg manifest 2
13 hg parents
13 hg parents
14 rm b
14 rm b
15 hg rawcommit -p 2 -d "0 0" -m3 b
15 hg rawcommit -p 2 -d "0 0" -m3 b
16 hg manifest 3
16 hg manifest 3
17 hg parents
17 hg parents
18 echo this is a22 > a
18 echo this is a22 > a
19 hg rawcommit -p 3 -d "0 0" -m4 a
19 hg rawcommit -p 3 -d "0 0" -m4 a
20 hg manifest 4
20 hg manifest 4
21 hg parents
21 hg parents
22 echo this is c22 > c
22 echo this is c22 > c
23 hg rawcommit -p 1 -d "0 0" -m5 c
23 hg rawcommit -p 1 -d "0 0" -m5 c
24 hg manifest 5
24 hg manifest 5
25 hg parents
25 hg parents
26 # merge, but no files changed
26 # merge, but no files changed
27 hg rawcommit -p 4 -p 5 -d "0 0" -m6
27 hg rawcommit -p 4 -p 5 -d "0 0" -m6
28 hg manifest 6
28 hg manifest 6
29 hg parents
29 hg parents
30 # no changes what-so-ever
30 # no changes what-so-ever
31 hg rawcommit -p 6 -d "0 0" -m7
31 hg rawcommit -p 6 -d "0 0" -m7
32 hg manifest 7
32 hg manifest 7
33 hg parents
33 hg parents
@@ -1,13 +1,13 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init
3 hg init
4 echo a > a
4 echo a > a
5 hg add a
5 hg add a
6 hg commit -m "test" -d "0 0"
6 hg commit -m "test" -d "0 0"
7 hg history
7 hg history
8 hg tag -d "0 0" "bleah"
8 hg tag -d "0 0" "bleah"
9 hg history
9 hg history
10
10
11 echo foo >> .hgtags
11 echo foo >> .hgtags
12 hg tag -d "0 0" "bleah2" || echo "failed"
12 hg tag -d "0 0" "bleah2" || echo "failed"
13
13
@@ -1,19 +1,19 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 mkdir a
3 mkdir a
4 cd a
4 cd a
5 hg init
5 hg init
6 echo 123 > a
6 echo 123 > a
7 hg add a
7 hg add a
8 hg commit -m "a" -u a -d "0 0"
8 hg commit -m "a" -u a -d "0 0"
9
9
10 cd ..
10 cd ..
11 mkdir b
11 mkdir b
12 cd b
12 cd b
13 hg init
13 hg init
14 echo 321 > b
14 echo 321 > b
15 hg add b
15 hg add b
16 hg commit -m "b" -u b -d "0 0"
16 hg commit -m "b" -u b -d "0 0"
17
17
18 hg pull ../a
18 hg pull ../a
19 hg heads
19 hg heads
@@ -1,34 +1,34 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init
3 hg init
4 touch a
4 touch a
5 hg add a
5 hg add a
6 hg commit -m "Added a" -d "0 0"
6 hg commit -m "Added a" -d "0 0"
7
7
8 touch main
8 touch main
9 hg add main
9 hg add main
10 hg commit -t "Added main" -d "0 0"
10 hg commit -t "Added main" -d "0 0"
11 hg checkout 0
11 hg checkout 0
12
12
13 echo Main should be gone
13 echo Main should be gone
14 ls
14 ls
15
15
16 touch side1
16 touch side1
17 hg add side1
17 hg add side1
18 hg commit -m "Added side1" -d "0 0"
18 hg commit -m "Added side1" -d "0 0"
19 touch side2
19 touch side2
20 hg add side2
20 hg add side2
21 hg commit -m "Added side2" -d "0 0"
21 hg commit -m "Added side2" -d "0 0"
22
22
23 hg log
23 hg log
24
24
25 echo Should have two heads, side2 and main
25 echo Should have two heads, side2 and main
26 hg heads
26 hg heads
27
27
28 echo Should show "a side1 side2"
28 echo Should show "a side1 side2"
29 ls
29 ls
30
30
31 hg update --debug -C 1
31 hg update --debug -C 1
32 echo Should only show "a main"
32 echo Should only show "a main"
33 ls
33 ls
34
34
General Comments 0
You need to be logged in to leave comments. Login now