Show More
@@ -0,0 +1,69 b'' | |||||
|
1 | # hgweb/wsgicgi.py - CGI->WSGI translator | |||
|
2 | # | |||
|
3 | # Copyright 2006 Eric Hopper <hopper@omnifarious.org> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms | |||
|
6 | # of the GNU General Public License, incorporated herein by reference. | |||
|
7 | # | |||
|
8 | # This was originally copied from the public domain code at | |||
|
9 | # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side | |||
|
10 | ||||
|
11 | import os, sys | |||
|
12 | ||||
|
13 | def launch(application): | |||
|
14 | ||||
|
15 | environ = dict(os.environ.items()) | |||
|
16 | environ['wsgi.input'] = sys.stdin | |||
|
17 | environ['wsgi.errors'] = sys.stderr | |||
|
18 | environ['wsgi.version'] = (1,0) | |||
|
19 | environ['wsgi.multithread'] = False | |||
|
20 | environ['wsgi.multiprocess'] = True | |||
|
21 | environ['wsgi.run_once'] = True | |||
|
22 | ||||
|
23 | if environ.get('HTTPS','off') in ('on','1'): | |||
|
24 | environ['wsgi.url_scheme'] = 'https' | |||
|
25 | else: | |||
|
26 | environ['wsgi.url_scheme'] = 'http' | |||
|
27 | ||||
|
28 | headers_set = [] | |||
|
29 | headers_sent = [] | |||
|
30 | ||||
|
31 | def write(data): | |||
|
32 | if not headers_set: | |||
|
33 | raise AssertionError("write() before start_response()") | |||
|
34 | ||||
|
35 | elif not headers_sent: | |||
|
36 | # Before the first output, send the stored headers | |||
|
37 | status, response_headers = headers_sent[:] = headers_set | |||
|
38 | sys.stdout.write('Status: %s\r\n' % status) | |||
|
39 | for header in response_headers: | |||
|
40 | sys.stdout.write('%s: %s\r\n' % header) | |||
|
41 | sys.stdout.write('\r\n') | |||
|
42 | ||||
|
43 | sys.stdout.write(data) | |||
|
44 | sys.stdout.flush() | |||
|
45 | ||||
|
46 | def start_response(status,response_headers,exc_info=None): | |||
|
47 | if exc_info: | |||
|
48 | try: | |||
|
49 | if headers_sent: | |||
|
50 | # Re-raise original exception if headers sent | |||
|
51 | raise exc_info[0], exc_info[1], exc_info[2] | |||
|
52 | finally: | |||
|
53 | exc_info = None # avoid dangling circular ref | |||
|
54 | elif headers_set: | |||
|
55 | raise AssertionError("Headers already set!") | |||
|
56 | ||||
|
57 | headers_set[:] = [status,response_headers] | |||
|
58 | return write | |||
|
59 | ||||
|
60 | result = application(environ, start_response) | |||
|
61 | try: | |||
|
62 | for data in result: | |||
|
63 | if data: # don't send headers until body appears | |||
|
64 | write(data) | |||
|
65 | if not headers_sent: | |||
|
66 | write('') # send headers now if body was empty | |||
|
67 | finally: | |||
|
68 | if hasattr(result,'close'): | |||
|
69 | result.close() |
@@ -0,0 +1,81 b'' | |||||
|
1 | #!/bin/sh | |||
|
2 | ||||
|
3 | hg init a | |||
|
4 | echo line 1 > a/a | |||
|
5 | hg --cwd a ci -d '0 0' -Ama | |||
|
6 | ||||
|
7 | echo line 2 >> a/a | |||
|
8 | hg --cwd a ci -u someone -d '1 0' -m'second change' | |||
|
9 | ||||
|
10 | echo % import exported patch | |||
|
11 | hg clone -r0 a b | |||
|
12 | hg --cwd a export tip > tip.patch | |||
|
13 | hg --cwd b import ../tip.patch | |||
|
14 | echo % message should be same | |||
|
15 | hg --cwd b tip | grep 'second change' | |||
|
16 | echo % committer should be same | |||
|
17 | hg --cwd b tip | grep someone | |||
|
18 | rm -rf b | |||
|
19 | ||||
|
20 | echo % import of plain diff should fail without message | |||
|
21 | hg clone -r0 a b | |||
|
22 | hg --cwd a diff -r0:1 > tip.patch | |||
|
23 | hg --cwd b import ../tip.patch | |||
|
24 | rm -rf b | |||
|
25 | ||||
|
26 | echo % import of plain diff should be ok with message | |||
|
27 | hg clone -r0 a b | |||
|
28 | hg --cwd a diff -r0:1 > tip.patch | |||
|
29 | hg --cwd b import -mpatch ../tip.patch | |||
|
30 | rm -rf b | |||
|
31 | ||||
|
32 | echo % import from stdin | |||
|
33 | hg clone -r0 a b | |||
|
34 | hg --cwd a export tip | hg --cwd b import - | |||
|
35 | rm -rf b | |||
|
36 | ||||
|
37 | echo % override commit message | |||
|
38 | hg clone -r0 a b | |||
|
39 | hg --cwd a export tip | hg --cwd b import -m 'override' - | |||
|
40 | hg --cwd b tip | grep override | |||
|
41 | rm -rf b | |||
|
42 | ||||
|
43 | cat > mkmsg.py <<EOF | |||
|
44 | import email.Message, sys | |||
|
45 | msg = email.Message.Message() | |||
|
46 | msg.set_payload('email commit message\n' + open('tip.patch').read()) | |||
|
47 | msg['Subject'] = 'email patch' | |||
|
48 | msg['From'] = 'email patcher' | |||
|
49 | sys.stdout.write(msg.as_string()) | |||
|
50 | EOF | |||
|
51 | ||||
|
52 | echo % plain diff in email, subject, message body | |||
|
53 | hg clone -r0 a b | |||
|
54 | hg --cwd a diff -r0:1 > tip.patch | |||
|
55 | python mkmsg.py > msg.patch | |||
|
56 | hg --cwd b import ../msg.patch | |||
|
57 | hg --cwd b tip | grep email | |||
|
58 | rm -rf b | |||
|
59 | ||||
|
60 | echo % plain diff in email, no subject, message body | |||
|
61 | hg clone -r0 a b | |||
|
62 | grep -v '^Subject:' msg.patch | hg --cwd b import - | |||
|
63 | rm -rf b | |||
|
64 | ||||
|
65 | echo % plain diff in email, subject, no message body | |||
|
66 | hg clone -r0 a b | |||
|
67 | grep -v '^email ' msg.patch | hg --cwd b import - | |||
|
68 | rm -rf b | |||
|
69 | ||||
|
70 | echo % plain diff in email, no subject, no message body, should fail | |||
|
71 | hg clone -r0 a b | |||
|
72 | grep -v '^\(Subject\|email\)' msg.patch | hg --cwd b import - | |||
|
73 | rm -rf b | |||
|
74 | ||||
|
75 | echo % hg export in email, should use patch header | |||
|
76 | hg clone -r0 a b | |||
|
77 | hg --cwd a export tip > tip.patch | |||
|
78 | python mkmsg.py | hg --cwd b import - | |||
|
79 | hg --cwd b tip | grep second | |||
|
80 | rm -rf b | |||
|
81 |
@@ -0,0 +1,103 b'' | |||||
|
1 | adding a | |||
|
2 | % import exported patch | |||
|
3 | requesting all changes | |||
|
4 | adding changesets | |||
|
5 | adding manifests | |||
|
6 | adding file changes | |||
|
7 | added 1 changesets with 1 changes to 1 files | |||
|
8 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
9 | applying ../tip.patch | |||
|
10 | patching file a | |||
|
11 | % message should be same | |||
|
12 | summary: second change | |||
|
13 | % committer should be same | |||
|
14 | user: someone | |||
|
15 | % import of plain diff should fail without message | |||
|
16 | requesting all changes | |||
|
17 | adding changesets | |||
|
18 | adding manifests | |||
|
19 | adding file changes | |||
|
20 | added 1 changesets with 1 changes to 1 files | |||
|
21 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
22 | applying ../tip.patch | |||
|
23 | patching file a | |||
|
24 | transaction abort! | |||
|
25 | rollback completed | |||
|
26 | % import of plain diff should be ok with message | |||
|
27 | requesting all changes | |||
|
28 | adding changesets | |||
|
29 | adding manifests | |||
|
30 | adding file changes | |||
|
31 | added 1 changesets with 1 changes to 1 files | |||
|
32 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
33 | applying ../tip.patch | |||
|
34 | patching file a | |||
|
35 | % import from stdin | |||
|
36 | requesting all changes | |||
|
37 | adding changesets | |||
|
38 | adding manifests | |||
|
39 | adding file changes | |||
|
40 | added 1 changesets with 1 changes to 1 files | |||
|
41 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
42 | applying patch from stdin | |||
|
43 | patching file a | |||
|
44 | % override commit message | |||
|
45 | requesting all changes | |||
|
46 | adding changesets | |||
|
47 | adding manifests | |||
|
48 | adding file changes | |||
|
49 | added 1 changesets with 1 changes to 1 files | |||
|
50 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
51 | applying patch from stdin | |||
|
52 | patching file a | |||
|
53 | summary: override | |||
|
54 | % plain diff in email, subject, message body | |||
|
55 | requesting all changes | |||
|
56 | adding changesets | |||
|
57 | adding manifests | |||
|
58 | adding file changes | |||
|
59 | added 1 changesets with 1 changes to 1 files | |||
|
60 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
61 | applying ../msg.patch | |||
|
62 | patching file a | |||
|
63 | user: email patcher | |||
|
64 | summary: email patch | |||
|
65 | % plain diff in email, no subject, message body | |||
|
66 | requesting all changes | |||
|
67 | adding changesets | |||
|
68 | adding manifests | |||
|
69 | adding file changes | |||
|
70 | added 1 changesets with 1 changes to 1 files | |||
|
71 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
72 | applying patch from stdin | |||
|
73 | patching file a | |||
|
74 | % plain diff in email, subject, no message body | |||
|
75 | requesting all changes | |||
|
76 | adding changesets | |||
|
77 | adding manifests | |||
|
78 | adding file changes | |||
|
79 | added 1 changesets with 1 changes to 1 files | |||
|
80 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
81 | applying patch from stdin | |||
|
82 | patching file a | |||
|
83 | % plain diff in email, no subject, no message body, should fail | |||
|
84 | requesting all changes | |||
|
85 | adding changesets | |||
|
86 | adding manifests | |||
|
87 | adding file changes | |||
|
88 | added 1 changesets with 1 changes to 1 files | |||
|
89 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
90 | applying patch from stdin | |||
|
91 | patching file a | |||
|
92 | transaction abort! | |||
|
93 | rollback completed | |||
|
94 | % hg export in email, should use patch header | |||
|
95 | requesting all changes | |||
|
96 | adding changesets | |||
|
97 | adding manifests | |||
|
98 | adding file changes | |||
|
99 | added 1 changesets with 1 changes to 1 files | |||
|
100 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
101 | applying patch from stdin | |||
|
102 | patching file a | |||
|
103 | summary: second change |
@@ -0,0 +1,16 b'' | |||||
|
1 | #!/bin/sh | |||
|
2 | ||||
|
3 | hg init | |||
|
4 | echo "test-parse-date" > a | |||
|
5 | hg add a | |||
|
6 | hg ci -d "2006-02-01 13:00:30" -m "rev 0" | |||
|
7 | echo "hi!" >> a | |||
|
8 | hg ci -d "2006-02-01 13:00:30 -0500" -m "rev 1" | |||
|
9 | hg tag -d "2006-04-15 13:30" "Hi" | |||
|
10 | hg backout --merge -d "2006-04-15 13:30 +0200" -m "rev 3" 1 | |||
|
11 | hg ci -d "1150000000 14400" -m "rev 4 (merge)" | |||
|
12 | echo "fail" >> a | |||
|
13 | hg ci -d "should fail" -m "fail" | |||
|
14 | hg ci -d "100000000000000000 1400" -m "fail" | |||
|
15 | hg ci -d "100000 1400000" -m "fail" | |||
|
16 | hg log --template '{date|date}\n' |
@@ -0,0 +1,19 b'' | |||||
|
1 | reverting a | |||
|
2 | changeset 3:107ce1ee2b43 backs out changeset 1:25a1420a55f8 | |||
|
3 | merging with changeset 2:99a1acecff55 | |||
|
4 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |||
|
5 | (branch merge, don't forget to commit) | |||
|
6 | abort: invalid date: 'should fail' | |||
|
7 | transaction abort! | |||
|
8 | rollback completed | |||
|
9 | abort: date exceeds 32 bits: 100000000000000000 | |||
|
10 | transaction abort! | |||
|
11 | rollback completed | |||
|
12 | abort: impossible time zone offset: 1400000 | |||
|
13 | transaction abort! | |||
|
14 | rollback completed | |||
|
15 | Sun Jun 11 00:26:40 2006 -0400 | |||
|
16 | Sat Apr 15 13:30:00 2006 +0200 | |||
|
17 | Sat Apr 15 13:30:00 2006 +0000 | |||
|
18 | Wed Feb 01 13:00:30 2006 -0500 | |||
|
19 | Wed Feb 01 13:00:30 2006 +0000 |
@@ -18,13 +18,10 b'' | |||||
18 | <p class="p2"><br></p> |
|
18 | <p class="p2"><br></p> | |
19 | <p class="p3">This is <i>not</i> a stand-alone version of Mercurial.</p> |
|
19 | <p class="p3">This is <i>not</i> a stand-alone version of Mercurial.</p> | |
20 | <p class="p2"><br></p> |
|
20 | <p class="p2"><br></p> | |
21 |
<p class="p3">To use it, you must have the |
|
21 | <p class="p3">To use it, you must have the Universal MacPython 2.4.3 from <a href="http://www.python.org">www.python.org</a> installed.</p> | |
22 | <p class="p2"><br></p> |
|
22 | <p class="p2"><br></p> | |
23 |
<p class="p3">You can download MacPython 2.4. |
|
23 | <p class="p3">You can download MacPython 2.4.3 from here:</p> | |
24 |
<p class="p4"><span class="s1"><a href="http://python.org/ftp/python/2.4. |
|
24 | <p class="p4"><span class="s1"><a href="http://www.python.org/ftp/python/2.4.3/Universal-MacPython-2.4.3-2006-04-07.dmg">http://www.python.org/ftp/python/2.4.3/Universal-MacPython-2.4.3-2006-04-07.dmg</a></span></p> | |
25 | <p class="p2"><br></p> |
|
|||
26 | <p class="p3">For more information on MacPython, go here:</p> |
|
|||
27 | <p class="p4"><span class="s1"><a href="http://undefined.org/python/">http://undefined.org/python</a></span></p> |
|
|||
28 | <p class="p2"><br></p> |
|
25 | <p class="p2"><br></p> | |
29 | <p class="p1"><b>After you install</b></p> |
|
26 | <p class="p1"><b>After you install</b></p> | |
30 | <p class="p2"><br></p> |
|
27 | <p class="p2"><br></p> |
@@ -12,6 +12,6 b'' | |||||
12 | <body> |
|
12 | <body> | |
13 | <p class="p1">This is a prepackaged release of <a href="http://www.selenic.com/mercurial">Mercurial</a> for Mac OS X.</p> |
|
13 | <p class="p1">This is a prepackaged release of <a href="http://www.selenic.com/mercurial">Mercurial</a> for Mac OS X.</p> | |
14 | <p class="p2"><br></p> |
|
14 | <p class="p2"><br></p> | |
15 |
<p class="p1">It is based on Mercurial 0. |
|
15 | <p class="p1">It is based on Mercurial 0.9.</p> | |
16 | </body> |
|
16 | </body> | |
17 | </html> |
|
17 | </html> |
@@ -653,7 +653,7 b' The Mercurial mode user interface is bas' | |||||
653 | you're already familiar with VC, the same keybindings and functions |
|
653 | you're already familiar with VC, the same keybindings and functions | |
654 | will generally work. |
|
654 | will generally work. | |
655 |
|
655 | |||
656 | Below is a list of many common SCM tasks. In the list, `G/L' |
|
656 | Below is a list of many common SCM tasks. In the list, `G/L\' | |
657 | indicates whether a key binding is global (G) to a repository or local |
|
657 | indicates whether a key binding is global (G) to a repository or local | |
658 | (L) to a file. Many commands take a prefix argument. |
|
658 | (L) to a file. Many commands take a prefix argument. | |
659 |
|
659 | |||
@@ -682,6 +682,8 b' Pull changes G ' | |||||
682 | Update working directory after pull G C-c h u hg-update |
|
682 | Update working directory after pull G C-c h u hg-update | |
683 | See changes that can be pushed G C-c h . hg-outgoing |
|
683 | See changes that can be pushed G C-c h . hg-outgoing | |
684 | Push changes G C-c h > hg-push" |
|
684 | Push changes G C-c h > hg-push" | |
|
685 | (unless vc-make-backup-files | |||
|
686 | (set (make-local-variable 'backup-inhibited) t)) | |||
685 | (run-hooks 'hg-mode-hook)) |
|
687 | (run-hooks 'hg-mode-hook)) | |
686 |
|
688 | |||
687 | (defun hg-find-file-hook () |
|
689 | (defun hg-find-file-hook () | |
@@ -729,6 +731,8 b' With a prefix argument, prompt for the p' | |||||
729 | (goto-char 0) |
|
731 | (goto-char 0) | |
730 | (cd (hg-root path))) |
|
732 | (cd (hg-root path))) | |
731 | (when update |
|
733 | (when update | |
|
734 | (unless vc-make-backup-files | |||
|
735 | (set (make-local-variable 'backup-inhibited) t)) | |||
732 | (with-current-buffer buf |
|
736 | (with-current-buffer buf | |
733 | (hg-mode-line))))) |
|
737 | (hg-mode-line))))) | |
734 |
|
738 | |||
@@ -968,6 +972,7 b' With a prefix argument, prompt for the p' | |||||
968 | (cd (hg-root path))) |
|
972 | (cd (hg-root path))) | |
969 | (when update |
|
973 | (when update | |
970 | (with-current-buffer buf |
|
974 | (with-current-buffer buf | |
|
975 | (set (make-local-variable 'backup-inhibited) nil) | |||
971 | (hg-mode-line))))) |
|
976 | (hg-mode-line))))) | |
972 |
|
977 | |||
973 | (defun hg-incoming (&optional repo) |
|
978 | (defun hg-incoming (&optional repo) |
@@ -214,7 +214,6 b' class queue:' | |||||
214 | return pp[0] |
|
214 | return pp[0] | |
215 | if p1 in arevs: |
|
215 | if p1 in arevs: | |
216 | return pp[1] |
|
216 | return pp[1] | |
217 | return None |
|
|||
218 | return pp[0] |
|
217 | return pp[0] | |
219 |
|
218 | |||
220 | def mergepatch(self, repo, mergeq, series, wlock): |
|
219 | def mergepatch(self, repo, mergeq, series, wlock): | |
@@ -386,15 +385,21 b' class queue:' | |||||
386 | self.ui.write("Local changes found, refresh first\n") |
|
385 | self.ui.write("Local changes found, refresh first\n") | |
387 | sys.exit(1) |
|
386 | sys.exit(1) | |
388 | def new(self, repo, patch, msg=None, force=None): |
|
387 | def new(self, repo, patch, msg=None, force=None): | |
389 | if not force: |
|
388 | commitfiles = [] | |
390 | self.check_localchanges(repo) |
|
389 | (c, a, r, d, u) = repo.changes(None, None) | |
|
390 | if c or a or d or r: | |||
|
391 | if not force: | |||
|
392 | raise util.Abort(_("Local changes found, refresh first")) | |||
|
393 | else: | |||
|
394 | commitfiles = c + a + r | |||
391 | self.check_toppatch(repo) |
|
395 | self.check_toppatch(repo) | |
392 | wlock = repo.wlock() |
|
396 | wlock = repo.wlock() | |
393 | insert = self.series_end() |
|
397 | insert = self.series_end() | |
394 | if msg: |
|
398 | if msg: | |
395 |
n = repo.commit( |
|
399 | n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True, | |
|
400 | wlock=wlock) | |||
396 | else: |
|
401 | else: | |
397 |
n = repo.commit( |
|
402 | n = repo.commit(commitfiles, | |
398 | "New patch: %s" % patch, force=True, wlock=wlock) |
|
403 | "New patch: %s" % patch, force=True, wlock=wlock) | |
399 | if n == None: |
|
404 | if n == None: | |
400 | self.ui.warn("repo commit failed\n") |
|
405 | self.ui.warn("repo commit failed\n") | |
@@ -412,6 +417,8 b' class queue:' | |||||
412 | wlock = None |
|
417 | wlock = None | |
413 | r = self.qrepo() |
|
418 | r = self.qrepo() | |
414 | if r: r.add([patch]) |
|
419 | if r: r.add([patch]) | |
|
420 | if commitfiles: | |||
|
421 | self.refresh(repo, short=True) | |||
415 |
|
422 | |||
416 | def strip(self, repo, rev, update=True, backup="all", wlock=None): |
|
423 | def strip(self, repo, rev, update=True, backup="all", wlock=None): | |
417 | def limitheads(chlog, stop): |
|
424 | def limitheads(chlog, stop): |
@@ -6,7 +6,11 b' import cgitb, os, sys' | |||||
6 | cgitb.enable() |
|
6 | cgitb.enable() | |
7 |
|
7 | |||
8 | # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install |
|
8 | # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install | |
9 | from mercurial import hgweb |
|
9 | from mercurial.hgweb.hgweb_mod import hgweb | |
|
10 | from mercurial.hgweb.request import wsgiapplication | |||
|
11 | import mercurial.hgweb.wsgicgi as wsgicgi | |||
10 |
|
12 | |||
11 | h = hgweb.hgweb("/path/to/repo", "repository name") |
|
13 | def make_web_app(): | |
12 | h.run() |
|
14 | return hgweb("/path/to/repo", "repository name") | |
|
15 | ||||
|
16 | wsgicgi.launch(wsgiapplication(make_web_app)) |
@@ -6,7 +6,9 b' import cgitb, sys' | |||||
6 | cgitb.enable() |
|
6 | cgitb.enable() | |
7 |
|
7 | |||
8 | # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install |
|
8 | # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install | |
9 | from mercurial import hgweb |
|
9 | from mercurial.hgweb.hgwebdir_mod import hgwebdir | |
|
10 | from mercurial.hgweb.request import wsgiapplication | |||
|
11 | import mercurial.hgweb.wsgicgi as wsgicgi | |||
10 |
|
12 | |||
11 | # The config file looks like this. You can have paths to individual |
|
13 | # The config file looks like this. You can have paths to individual | |
12 | # repos, collections of repos in a directory tree, or both. |
|
14 | # repos, collections of repos in a directory tree, or both. | |
@@ -27,5 +29,7 b' from mercurial import hgweb' | |||||
27 | # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples |
|
29 | # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples | |
28 | # or use a dictionary with entries like 'virtual/path': '/real/path' |
|
30 | # or use a dictionary with entries like 'virtual/path': '/real/path' | |
29 |
|
31 | |||
30 | h = hgweb.hgwebdir("hgweb.config") |
|
32 | def make_web_app(): | |
31 | h.run() |
|
33 | return hgwebdir("hgweb.config") | |
|
34 | ||||
|
35 | wsgicgi.launch(wsgiapplication(make_web_app)) |
@@ -39,21 +39,10 b' class changelog(revlog):' | |||||
39 | def add(self, manifest, list, desc, transaction, p1=None, p2=None, |
|
39 | def add(self, manifest, list, desc, transaction, p1=None, p2=None, | |
40 | user=None, date=None): |
|
40 | user=None, date=None): | |
41 | if date: |
|
41 | if date: | |
42 | # validate explicit (probably user-specified) date and |
|
42 | parseddate = "%d %d" % util.parsedate(date) | |
43 | # time zone offset. values must fit in signed 32 bits for |
|
|||
44 | # current 32-bit linux runtimes. timezones go from UTC-12 |
|
|||
45 | # to UTC+14 |
|
|||
46 | try: |
|
|||
47 | when, offset = map(int, date.split(' ')) |
|
|||
48 | except ValueError: |
|
|||
49 | raise ValueError(_('invalid date: %r') % date) |
|
|||
50 | if abs(when) > 0x7fffffff: |
|
|||
51 | raise ValueError(_('date exceeds 32 bits: %d') % when) |
|
|||
52 | if offset < -50400 or offset > 43200: |
|
|||
53 | raise ValueError(_('impossible time zone offset: %d') % offset) |
|
|||
54 | else: |
|
43 | else: | |
55 | date = "%d %d" % util.makedate() |
|
44 | parseddate = "%d %d" % util.makedate() | |
56 | list.sort() |
|
45 | list.sort() | |
57 | l = [hex(manifest), user, date] + list + ["", desc] |
|
46 | l = [hex(manifest), user, parseddate] + list + ["", desc] | |
58 | text = "\n".join(l) |
|
47 | text = "\n".join(l) | |
59 | return self.addrevision(text, transaction, self.count(), p1, p2) |
|
48 | return self.addrevision(text, transaction, self.count(), p1, p2) |
@@ -12,7 +12,7 b' demandload(globals(), "os re sys signal ' | |||||
12 | demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") |
|
12 | demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") | |
13 | demandload(globals(), "fnmatch mdiff random signal tempfile time") |
|
13 | demandload(globals(), "fnmatch mdiff random signal tempfile time") | |
14 | demandload(globals(), "traceback errno socket version struct atexit sets bz2") |
|
14 | demandload(globals(), "traceback errno socket version struct atexit sets bz2") | |
15 | demandload(globals(), "archival changegroup") |
|
15 | demandload(globals(), "archival cStringIO changegroup email.Parser") | |
16 | demandload(globals(), "hgweb.server sshserver") |
|
16 | demandload(globals(), "hgweb.server sshserver") | |
17 |
|
17 | |||
18 | class UnknownCommand(Exception): |
|
18 | class UnknownCommand(Exception): | |
@@ -1719,11 +1719,16 b' def import_(ui, repo, patch1, *patches, ' | |||||
1719 | If there are outstanding changes in the working directory, import |
|
1719 | If there are outstanding changes in the working directory, import | |
1720 | will abort unless given the -f flag. |
|
1720 | will abort unless given the -f flag. | |
1721 |
|
1721 | |||
1722 | If a patch looks like a mail message (its first line starts with |
|
1722 | You can import a patch straight from a mail message. Even patches | |
1723 | "From " or looks like an RFC822 header), it will not be applied |
|
1723 | as attachments work (body part must be type text/plain or | |
1724 | unless the -f option is used. The importer neither parses nor |
|
1724 | text/x-patch to be used). From and Subject headers of email | |
1725 | discards mail headers, so use -f only to override the "mailness" |
|
1725 | message are used as default committer and commit message. All | |
1726 | safety check, not to import a real mail message. |
|
1726 | text/plain body parts before first diff are added to commit | |
|
1727 | message. | |||
|
1728 | ||||
|
1729 | If imported patch was generated by hg export, user and description | |||
|
1730 | from patch override values from message headers and body. Values | |||
|
1731 | given on command line with -m and -u override these. | |||
1727 |
|
1732 | |||
1728 | To read a patch from standard input, use patch name "-". |
|
1733 | To read a patch from standard input, use patch name "-". | |
1729 | """ |
|
1734 | """ | |
@@ -1739,79 +1744,98 b' def import_(ui, repo, patch1, *patches, ' | |||||
1739 |
|
1744 | |||
1740 | # attempt to detect the start of a patch |
|
1745 | # attempt to detect the start of a patch | |
1741 | # (this heuristic is borrowed from quilt) |
|
1746 | # (this heuristic is borrowed from quilt) | |
1742 | diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' + |
|
1747 | diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' + | |
1743 | 'retrieving revision [0-9]+(\.[0-9]+)*$|' + |
|
1748 | 'retrieving revision [0-9]+(\.[0-9]+)*$|' + | |
1744 | '(---|\*\*\*)[ \t])') |
|
1749 | '(---|\*\*\*)[ \t])', re.MULTILINE) | |
1745 |
|
1750 | |||
1746 | for patch in patches: |
|
1751 | for patch in patches: | |
1747 | pf = os.path.join(d, patch) |
|
1752 | pf = os.path.join(d, patch) | |
1748 |
|
1753 | |||
1749 |
message = |
|
1754 | message = None | |
1750 | user = None |
|
1755 | user = None | |
1751 | date = None |
|
1756 | date = None | |
1752 | hgpatch = False |
|
1757 | hgpatch = False | |
|
1758 | ||||
|
1759 | p = email.Parser.Parser() | |||
1753 | if pf == '-': |
|
1760 | if pf == '-': | |
1754 |
|
|
1761 | msg = p.parse(sys.stdin) | |
1755 | fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') |
|
|||
1756 | pf = tmpname |
|
|||
1757 | tmpfp = os.fdopen(fd, 'w') |
|
|||
1758 | ui.status(_("applying patch from stdin\n")) |
|
1762 | ui.status(_("applying patch from stdin\n")) | |
1759 | else: |
|
1763 | else: | |
1760 |
|
|
1764 | msg = p.parse(file(pf)) | |
1761 | tmpfp, tmpname = None, None |
|
|||
1762 | ui.status(_("applying %s\n") % patch) |
|
1765 | ui.status(_("applying %s\n") % patch) | |
|
1766 | ||||
|
1767 | fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') | |||
|
1768 | tmpfp = os.fdopen(fd, 'w') | |||
1763 | try: |
|
1769 | try: | |
1764 | while True: |
|
1770 | message = msg['Subject'] | |
1765 | line = f.readline() |
|
1771 | if message: | |
1766 | if not line: break |
|
1772 | message = message.replace('\n\t', ' ') | |
1767 | if tmpfp: tmpfp.write(line) |
|
1773 | ui.debug('Subject: %s\n' % message) | |
1768 | line = line.rstrip() |
|
1774 | user = msg['From'] | |
1769 | if (not message and not hgpatch and |
|
1775 | if user: | |
1770 | mailre.match(line) and not opts['force']): |
|
1776 | ui.debug('From: %s\n' % user) | |
1771 | if len(line) > 35: |
|
1777 | diffs_seen = 0 | |
1772 | line = line[:32] + '...' |
|
1778 | ok_types = ('text/plain', 'text/x-patch') | |
1773 | raise util.Abort(_('first line looks like a ' |
|
1779 | for part in msg.walk(): | |
1774 | 'mail header: ') + line) |
|
1780 | content_type = part.get_content_type() | |
1775 | if diffre.match(line): |
|
1781 | ui.debug('Content-Type: %s\n' % content_type) | |
|
1782 | if content_type not in ok_types: | |||
|
1783 | continue | |||
|
1784 | payload = part.get_payload(decode=True) | |||
|
1785 | m = diffre.search(payload) | |||
|
1786 | if m: | |||
|
1787 | ui.debug(_('found patch at byte %d\n') % m.start(0)) | |||
|
1788 | diffs_seen += 1 | |||
|
1789 | hgpatch = False | |||
|
1790 | fp = cStringIO.StringIO() | |||
|
1791 | if message: | |||
|
1792 | fp.write(message) | |||
|
1793 | fp.write('\n') | |||
|
1794 | for line in payload[:m.start(0)].splitlines(): | |||
|
1795 | if line.startswith('# HG changeset patch'): | |||
|
1796 | ui.debug(_('patch generated by hg export\n')) | |||
|
1797 | hgpatch = True | |||
|
1798 | # drop earlier commit message content | |||
|
1799 | fp.seek(0) | |||
|
1800 | fp.truncate() | |||
|
1801 | elif hgpatch: | |||
|
1802 | if line.startswith('# User '): | |||
|
1803 | user = line[7:] | |||
|
1804 | ui.debug('From: %s\n' % user) | |||
|
1805 | elif line.startswith("# Date "): | |||
|
1806 | date = line[7:] | |||
|
1807 | if not line.startswith('# '): | |||
|
1808 | fp.write(line) | |||
|
1809 | fp.write('\n') | |||
|
1810 | message = fp.getvalue() | |||
1776 | if tmpfp: |
|
1811 | if tmpfp: | |
1777 |
|
|
1812 | tmpfp.write(payload) | |
1778 |
|
|
1813 | if not payload.endswith('\n'): | |
1779 |
|
|
1814 | tmpfp.write('\n') | |
1780 | elif hgpatch: |
|
1815 | elif not diffs_seen and message and content_type == 'text/plain': | |
1781 | # parse values when importing the result of an hg export |
|
1816 | message += '\n' + payload | |
1782 | if line.startswith("# User "): |
|
|||
1783 | user = line[7:] |
|
|||
1784 | ui.debug(_('User: %s\n') % user) |
|
|||
1785 | elif line.startswith("# Date "): |
|
|||
1786 | date = line[7:] |
|
|||
1787 | elif not line.startswith("# ") and line: |
|
|||
1788 | message.append(line) |
|
|||
1789 | hgpatch = False |
|
|||
1790 | elif line == '# HG changeset patch': |
|
|||
1791 | hgpatch = True |
|
|||
1792 | message = [] # We may have collected garbage |
|
|||
1793 | elif message or line: |
|
|||
1794 | message.append(line) |
|
|||
1795 |
|
1817 | |||
1796 | if opts['message']: |
|
1818 | if opts['message']: | |
1797 | # pickup the cmdline msg |
|
1819 | # pickup the cmdline msg | |
1798 | message = opts['message'] |
|
1820 | message = opts['message'] | |
1799 | elif message: |
|
1821 | elif message: | |
1800 | # pickup the patch msg |
|
1822 | # pickup the patch msg | |
1801 |
message = |
|
1823 | message = message.strip() | |
1802 | else: |
|
1824 | else: | |
1803 | # launch the editor |
|
1825 | # launch the editor | |
1804 | message = None |
|
1826 | message = None | |
1805 | ui.debug(_('message:\n%s\n') % message) |
|
1827 | ui.debug(_('message:\n%s\n') % message) | |
1806 |
|
1828 | |||
1807 |
|
|
1829 | tmpfp.close() | |
1808 | files = util.patch(strip, pf, ui) |
|
1830 | if not diffs_seen: | |
1809 |
|
1831 | raise util.Abort(_('no diffs found')) | ||
|
1832 | ||||
|
1833 | files = util.patch(strip, tmpname, ui) | |||
1810 | if len(files) > 0: |
|
1834 | if len(files) > 0: | |
1811 | addremove_lock(ui, repo, files, {}) |
|
1835 | addremove_lock(ui, repo, files, {}) | |
1812 | repo.commit(files, message, user, date) |
|
1836 | repo.commit(files, message, user, date) | |
1813 | finally: |
|
1837 | finally: | |
1814 |
|
|
1838 | os.unlink(tmpname) | |
1815 |
|
1839 | |||
1816 | def incoming(ui, repo, source="default", **opts): |
|
1840 | def incoming(ui, repo, source="default", **opts): | |
1817 | """show new changesets found in source |
|
1841 | """show new changesets found in source | |
@@ -1851,7 +1875,10 b' def incoming(ui, repo, source="default",' | |||||
1851 | # use the created uncompressed bundlerepo |
|
1875 | # use the created uncompressed bundlerepo | |
1852 | other = bundlerepo.bundlerepository(ui, repo.root, fname) |
|
1876 | other = bundlerepo.bundlerepository(ui, repo.root, fname) | |
1853 |
|
1877 | |||
1854 | o = other.changelog.nodesbetween(incoming)[0] |
|
1878 | revs = None | |
|
1879 | if opts['rev']: | |||
|
1880 | revs = [other.lookup(rev) for rev in opts['rev']] | |||
|
1881 | o = other.changelog.nodesbetween(incoming, revs)[0] | |||
1855 | if opts['newest_first']: |
|
1882 | if opts['newest_first']: | |
1856 | o.reverse() |
|
1883 | o.reverse() | |
1857 | displayer = show_changeset(ui, other, opts) |
|
1884 | displayer = show_changeset(ui, other, opts) | |
@@ -2061,13 +2088,16 b' def outgoing(ui, repo, dest=None, **opts' | |||||
2061 | ui.setconfig("ui", "ssh", opts['ssh']) |
|
2088 | ui.setconfig("ui", "ssh", opts['ssh']) | |
2062 | if opts['remotecmd']: |
|
2089 | if opts['remotecmd']: | |
2063 | ui.setconfig("ui", "remotecmd", opts['remotecmd']) |
|
2090 | ui.setconfig("ui", "remotecmd", opts['remotecmd']) | |
|
2091 | revs = None | |||
|
2092 | if opts['rev']: | |||
|
2093 | revs = [repo.lookup(rev) for rev in opts['rev']] | |||
2064 |
|
2094 | |||
2065 | other = hg.repository(ui, dest) |
|
2095 | other = hg.repository(ui, dest) | |
2066 | o = repo.findoutgoing(other, force=opts['force']) |
|
2096 | o = repo.findoutgoing(other, force=opts['force']) | |
2067 | if not o: |
|
2097 | if not o: | |
2068 | ui.status(_("no changes found\n")) |
|
2098 | ui.status(_("no changes found\n")) | |
2069 | return |
|
2099 | return | |
2070 | o = repo.changelog.nodesbetween(o)[0] |
|
2100 | o = repo.changelog.nodesbetween(o, revs)[0] | |
2071 | if opts['newest_first']: |
|
2101 | if opts['newest_first']: | |
2072 | o.reverse() |
|
2102 | o.reverse() | |
2073 | displayer = show_changeset(ui, repo, opts) |
|
2103 | displayer = show_changeset(ui, repo, opts) | |
@@ -2998,11 +3028,13 b' table = {' | |||||
2998 | ('n', 'newest-first', None, _('show newest record first')), |
|
3028 | ('n', 'newest-first', None, _('show newest record first')), | |
2999 | ('', 'bundle', '', _('file to store the bundles into')), |
|
3029 | ('', 'bundle', '', _('file to store the bundles into')), | |
3000 | ('p', 'patch', None, _('show patch')), |
|
3030 | ('p', 'patch', None, _('show patch')), | |
|
3031 | ('r', 'rev', [], _('a specific revision you would like to pull')), | |||
3001 | ('', 'template', '', _('display with template')), |
|
3032 | ('', 'template', '', _('display with template')), | |
3002 | ('e', 'ssh', '', _('specify ssh command to use')), |
|
3033 | ('e', 'ssh', '', _('specify ssh command to use')), | |
3003 | ('', 'remotecmd', '', |
|
3034 | ('', 'remotecmd', '', | |
3004 | _('specify hg command to run on the remote side'))], |
|
3035 | _('specify hg command to run on the remote side'))], | |
3005 |
_('hg incoming [-p] [-n] [-M] [- |
|
3036 | _('hg incoming [-p] [-n] [-M] [-r REV]...' | |
|
3037 | '[--bundle FILENAME] [SOURCE]')), | |||
3006 | "^init": (init, [], _('hg init [DEST]')), |
|
3038 | "^init": (init, [], _('hg init [DEST]')), | |
3007 | "locate": |
|
3039 | "locate": | |
3008 | (locate, |
|
3040 | (locate, | |
@@ -3040,12 +3072,13 b' table = {' | |||||
3040 | _('run even when remote repository is unrelated')), |
|
3072 | _('run even when remote repository is unrelated')), | |
3041 | ('p', 'patch', None, _('show patch')), |
|
3073 | ('p', 'patch', None, _('show patch')), | |
3042 | ('', 'style', '', _('display using template map file')), |
|
3074 | ('', 'style', '', _('display using template map file')), | |
|
3075 | ('r', 'rev', [], _('a specific revision you would like to push')), | |||
3043 | ('n', 'newest-first', None, _('show newest record first')), |
|
3076 | ('n', 'newest-first', None, _('show newest record first')), | |
3044 | ('', 'template', '', _('display with template')), |
|
3077 | ('', 'template', '', _('display with template')), | |
3045 | ('e', 'ssh', '', _('specify ssh command to use')), |
|
3078 | ('e', 'ssh', '', _('specify ssh command to use')), | |
3046 | ('', 'remotecmd', '', |
|
3079 | ('', 'remotecmd', '', | |
3047 | _('specify hg command to run on the remote side'))], |
|
3080 | _('specify hg command to run on the remote side'))], | |
3048 | _('hg outgoing [-M] [-p] [-n] [DEST]')), |
|
3081 | _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')), | |
3049 | "^parents": |
|
3082 | "^parents": | |
3050 | (parents, |
|
3083 | (parents, | |
3051 | [('b', 'branches', None, _('show branches')), |
|
3084 | [('b', 'branches', None, _('show branches')), |
@@ -17,7 +17,7 b' def get_mtime(repo_path):' | |||||
17 | else: |
|
17 | else: | |
18 | return os.stat(hg_path).st_mtime |
|
18 | return os.stat(hg_path).st_mtime | |
19 |
|
19 | |||
20 | def staticfile(directory, fname): |
|
20 | def staticfile(directory, fname, req): | |
21 | """return a file inside directory with guessed content-type header |
|
21 | """return a file inside directory with guessed content-type header | |
22 |
|
22 | |||
23 | fname always uses '/' as directory separator and isn't allowed to |
|
23 | fname always uses '/' as directory separator and isn't allowed to | |
@@ -36,7 +36,9 b' def staticfile(directory, fname):' | |||||
36 | try: |
|
36 | try: | |
37 | os.stat(path) |
|
37 | os.stat(path) | |
38 | ct = mimetypes.guess_type(path)[0] or "text/plain" |
|
38 | ct = mimetypes.guess_type(path)[0] or "text/plain" | |
39 | return "Content-type: %s\n\n%s" % (ct, file(path).read()) |
|
39 | req.header([('Content-type', ct), | |
|
40 | ('Content-length', os.path.getsize(path))]) | |||
|
41 | return file(path).read() | |||
40 | except (TypeError, OSError): |
|
42 | except (TypeError, OSError): | |
41 | # illegal fname or unreadable file |
|
43 | # illegal fname or unreadable file | |
42 | return "" |
|
44 | return "" |
@@ -10,9 +10,8 b' import os' | |||||
10 | import os.path |
|
10 | import os.path | |
11 | import mimetypes |
|
11 | import mimetypes | |
12 | from mercurial.demandload import demandload |
|
12 | from mercurial.demandload import demandload | |
13 | demandload(globals(), "re zlib ConfigParser cStringIO sys tempfile") |
|
13 | demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile") | |
14 | demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater") |
|
14 | demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater") | |
15 | demandload(globals(), "mercurial.hgweb.request:hgrequest") |
|
|||
16 | demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") |
|
15 | demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") | |
17 | from mercurial.node import * |
|
16 | from mercurial.node import * | |
18 | from mercurial.i18n import gettext as _ |
|
17 | from mercurial.i18n import gettext as _ | |
@@ -651,9 +650,12 b' class hgweb(object):' | |||||
651 | raise Exception("suspicious path") |
|
650 | raise Exception("suspicious path") | |
652 | return p |
|
651 | return p | |
653 |
|
652 | |||
654 |
def run(self, req |
|
653 | def run(self, req): | |
655 | def header(**map): |
|
654 | def header(**map): | |
656 |
|
|
655 | header_file = cStringIO.StringIO(''.join(self.t("header", **map))) | |
|
656 | msg = mimetools.Message(header_file, 0) | |||
|
657 | req.header(msg.items()) | |||
|
658 | yield header_file.read() | |||
657 |
|
659 | |||
658 | def footer(**map): |
|
660 | def footer(**map): | |
659 | yield self.t("footer", |
|
661 | yield self.t("footer", | |
@@ -724,7 +726,6 b' class hgweb(object):' | |||||
724 | method(req) |
|
726 | method(req) | |
725 | else: |
|
727 | else: | |
726 | req.write(self.t("error")) |
|
728 | req.write(self.t("error")) | |
727 | req.done() |
|
|||
728 |
|
729 | |||
729 | def do_changelog(self, req): |
|
730 | def do_changelog(self, req): | |
730 | hi = self.repo.changelog.count() - 1 |
|
731 | hi = self.repo.changelog.count() - 1 | |
@@ -830,7 +831,7 b' class hgweb(object):' | |||||
830 | static = self.repo.ui.config("web", "static", |
|
831 | static = self.repo.ui.config("web", "static", | |
831 | os.path.join(self.templatepath, |
|
832 | os.path.join(self.templatepath, | |
832 | "static")) |
|
833 | "static")) | |
833 | req.write(staticfile(static, fname) |
|
834 | req.write(staticfile(static, fname, req) | |
834 | or self.t("error", error="%r not found" % fname)) |
|
835 | or self.t("error", error="%r not found" % fname)) | |
835 |
|
836 | |||
836 | def do_capabilities(self, req): |
|
837 | def do_capabilities(self, req): |
@@ -8,10 +8,9 b'' | |||||
8 |
|
8 | |||
9 | import os |
|
9 | import os | |
10 | from mercurial.demandload import demandload |
|
10 | from mercurial.demandload import demandload | |
11 | demandload(globals(), "ConfigParser") |
|
11 | demandload(globals(), "ConfigParser mimetools cStringIO") | |
12 | demandload(globals(), "mercurial:ui,hg,util,templater") |
|
12 | demandload(globals(), "mercurial:ui,hg,util,templater") | |
13 | demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb") |
|
13 | demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb") | |
14 | demandload(globals(), "mercurial.hgweb.request:hgrequest") |
|
|||
15 | demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") |
|
14 | demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") | |
16 | from mercurial.i18n import gettext as _ |
|
15 | from mercurial.i18n import gettext as _ | |
17 |
|
16 | |||
@@ -47,9 +46,12 b' class hgwebdir(object):' | |||||
47 | self.repos.append((name.lstrip(os.sep), repo)) |
|
46 | self.repos.append((name.lstrip(os.sep), repo)) | |
48 | self.repos.sort() |
|
47 | self.repos.sort() | |
49 |
|
48 | |||
50 |
def run(self, req |
|
49 | def run(self, req): | |
51 | def header(**map): |
|
50 | def header(**map): | |
52 |
|
|
51 | header_file = cStringIO.StringIO(''.join(tmpl("header", **map))) | |
|
52 | msg = mimetools.Message(header_file, 0) | |||
|
53 | req.header(msg.items()) | |||
|
54 | yield header_file.read() | |||
53 |
|
55 | |||
54 | def footer(**map): |
|
56 | def footer(**map): | |
55 | yield tmpl("footer", motd=self.motd, **map) |
|
57 | yield tmpl("footer", motd=self.motd, **map) | |
@@ -133,7 +135,7 b' class hgwebdir(object):' | |||||
133 | if req.form.has_key('static'): |
|
135 | if req.form.has_key('static'): | |
134 | static = os.path.join(templater.templatepath(), "static") |
|
136 | static = os.path.join(templater.templatepath(), "static") | |
135 | fname = req.form['static'][0] |
|
137 | fname = req.form['static'][0] | |
136 | req.write(staticfile(static, fname) |
|
138 | req.write(staticfile(static, fname, req) | |
137 | or tmpl("error", error="%r not found" % fname)) |
|
139 | or tmpl("error", error="%r not found" % fname)) | |
138 | else: |
|
140 | else: | |
139 | sortable = ["name", "description", "contact", "lastchange"] |
|
141 | sortable = ["name", "description", "contact", "lastchange"] |
@@ -10,13 +10,48 b' from mercurial.demandload import demandl' | |||||
10 | demandload(globals(), "socket sys cgi os errno") |
|
10 | demandload(globals(), "socket sys cgi os errno") | |
11 | from mercurial.i18n import gettext as _ |
|
11 | from mercurial.i18n import gettext as _ | |
12 |
|
12 | |||
13 |
class |
|
13 | class wsgiapplication(object): | |
14 |
def __init__(self, |
|
14 | def __init__(self, destmaker): | |
15 | self.inp = inp or sys.stdin |
|
15 | self.destmaker = destmaker | |
16 | self.out = out or sys.stdout |
|
16 | ||
17 | self.env = env or os.environ |
|
17 | def __call__(self, wsgienv, start_response): | |
|
18 | return _wsgirequest(self.destmaker(), wsgienv, start_response) | |||
|
19 | ||||
|
20 | class _wsgioutputfile(object): | |||
|
21 | def __init__(self, request): | |||
|
22 | self.request = request | |||
|
23 | ||||
|
24 | def write(self, data): | |||
|
25 | self.request.write(data) | |||
|
26 | def writelines(self, lines): | |||
|
27 | for line in lines: | |||
|
28 | self.write(line) | |||
|
29 | def flush(self): | |||
|
30 | return None | |||
|
31 | def close(self): | |||
|
32 | return None | |||
|
33 | ||||
|
34 | class _wsgirequest(object): | |||
|
35 | def __init__(self, destination, wsgienv, start_response): | |||
|
36 | version = wsgienv['wsgi.version'] | |||
|
37 | if (version < (1,0)) or (version >= (2, 0)): | |||
|
38 | raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \ | |||
|
39 | % version) | |||
|
40 | self.inp = wsgienv['wsgi.input'] | |||
|
41 | self.out = _wsgioutputfile(self) | |||
|
42 | self.server_write = None | |||
|
43 | self.err = wsgienv['wsgi.errors'] | |||
|
44 | self.threaded = wsgienv['wsgi.multithread'] | |||
|
45 | self.multiprocess = wsgienv['wsgi.multiprocess'] | |||
|
46 | self.run_once = wsgienv['wsgi.run_once'] | |||
|
47 | self.env = wsgienv | |||
18 | self.form = cgi.parse(self.inp, self.env, keep_blank_values=1) |
|
48 | self.form = cgi.parse(self.inp, self.env, keep_blank_values=1) | |
19 | self.will_close = True |
|
49 | self.start_response = start_response | |
|
50 | self.headers = [] | |||
|
51 | destination.run(self) | |||
|
52 | ||||
|
53 | def __iter__(self): | |||
|
54 | return iter([]) | |||
20 |
|
55 | |||
21 | def read(self, count=-1): |
|
56 | def read(self, count=-1): | |
22 | return self.inp.read(count) |
|
57 | return self.inp.read(count) | |
@@ -27,23 +62,22 b' class hgrequest(object):' | |||||
27 | for part in thing: |
|
62 | for part in thing: | |
28 | self.write(part) |
|
63 | self.write(part) | |
29 | else: |
|
64 | else: | |
|
65 | thing = str(thing) | |||
|
66 | if self.server_write is None: | |||
|
67 | if not self.headers: | |||
|
68 | raise RuntimeError("request.write called before headers sent (%s)." % thing) | |||
|
69 | self.server_write = self.start_response('200 Script output follows', | |||
|
70 | self.headers) | |||
|
71 | self.start_response = None | |||
|
72 | self.headers = None | |||
30 | try: |
|
73 | try: | |
31 |
self. |
|
74 | self.server_write(thing) | |
32 | except socket.error, inst: |
|
75 | except socket.error, inst: | |
33 | if inst[0] != errno.ECONNRESET: |
|
76 | if inst[0] != errno.ECONNRESET: | |
34 | raise |
|
77 | raise | |
35 |
|
78 | |||
36 | def done(self): |
|
|||
37 | if self.will_close: |
|
|||
38 | self.inp.close() |
|
|||
39 | self.out.close() |
|
|||
40 | else: |
|
|||
41 | self.out.flush() |
|
|||
42 |
|
||||
43 | def header(self, headers=[('Content-type','text/html')]): |
|
79 | def header(self, headers=[('Content-type','text/html')]): | |
44 | for header in headers: |
|
80 | self.headers.extend(headers) | |
45 | self.out.write("%s: %s\r\n" % header) |
|
|||
46 | self.out.write("\r\n") |
|
|||
47 |
|
81 | |||
48 | def httphdr(self, type, filename=None, length=0, headers={}): |
|
82 | def httphdr(self, type, filename=None, length=0, headers={}): | |
49 | headers = headers.items() |
|
83 | headers = headers.items() | |
@@ -51,12 +85,6 b' class hgrequest(object):' | |||||
51 | if filename: |
|
85 | if filename: | |
52 | headers.append(('Content-disposition', 'attachment; filename=%s' % |
|
86 | headers.append(('Content-disposition', 'attachment; filename=%s' % | |
53 | filename)) |
|
87 | filename)) | |
54 | # we do not yet support http 1.1 chunked transfer, so we have |
|
|||
55 | # to force connection to close if content-length not known |
|
|||
56 | if length: |
|
88 | if length: | |
57 | headers.append(('Content-length', str(length))) |
|
89 | headers.append(('Content-length', str(length))) | |
58 | self.will_close = False |
|
|||
59 | else: |
|
|||
60 | headers.append(('Connection', 'close')) |
|
|||
61 | self.will_close = True |
|
|||
62 | self.header(headers) |
|
90 | self.header(headers) |
@@ -10,7 +10,7 b' from mercurial.demandload import demandl' | |||||
10 | import os, sys, errno |
|
10 | import os, sys, errno | |
11 | demandload(globals(), "urllib BaseHTTPServer socket SocketServer") |
|
11 | demandload(globals(), "urllib BaseHTTPServer socket SocketServer") | |
12 | demandload(globals(), "mercurial:ui,hg,util,templater") |
|
12 | demandload(globals(), "mercurial:ui,hg,util,templater") | |
13 |
demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request: |
|
13 | demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication") | |
14 | from mercurial.i18n import gettext as _ |
|
14 | from mercurial.i18n import gettext as _ | |
15 |
|
15 | |||
16 | def _splitURI(uri): |
|
16 | def _splitURI(uri): | |
@@ -25,6 +25,17 b' def _splitURI(uri):' | |||||
25 | path, query = uri, '' |
|
25 | path, query = uri, '' | |
26 | return urllib.unquote(path), query |
|
26 | return urllib.unquote(path), query | |
27 |
|
27 | |||
|
28 | class _error_logger(object): | |||
|
29 | def __init__(self, handler): | |||
|
30 | self.handler = handler | |||
|
31 | def flush(self): | |||
|
32 | pass | |||
|
33 | def write(str): | |||
|
34 | self.writelines(str.split('\n')) | |||
|
35 | def writelines(seq): | |||
|
36 | for msg in seq: | |||
|
37 | self.handler.log_error("HG error: %s", msg) | |||
|
38 | ||||
28 | class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler): |
|
39 | class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler): | |
29 | def __init__(self, *args, **kargs): |
|
40 | def __init__(self, *args, **kargs): | |
30 | self.protocol_version = 'HTTP/1.1' |
|
41 | self.protocol_version = 'HTTP/1.1' | |
@@ -76,17 +87,72 b' class _hgwebhandler(object, BaseHTTPServ' | |||||
76 | length = self.headers.getheader('content-length') |
|
87 | length = self.headers.getheader('content-length') | |
77 | if length: |
|
88 | if length: | |
78 | env['CONTENT_LENGTH'] = length |
|
89 | env['CONTENT_LENGTH'] = length | |
79 | accept = [] |
|
90 | for header in [h for h in self.headers.keys() \ | |
80 | for line in self.headers.getallmatchingheaders('accept'): |
|
91 | if h not in ('content-type', 'content-length')]: | |
81 | if line[:1] in "\t\n\r ": |
|
92 | hkey = 'HTTP_' + header.replace('-', '_').upper() | |
82 | accept.append(line.strip()) |
|
93 | hval = self.headers.getheader(header) | |
83 | else: |
|
94 | hval = hval.replace('\n', '').strip() | |
84 | accept = accept + line[7:].split(',') |
|
95 | if hval: | |
85 | env['HTTP_ACCEPT'] = ','.join(accept) |
|
96 | env[hkey] = hval | |
|
97 | env['SERVER_PROTOCOL'] = self.request_version | |||
|
98 | env['wsgi.version'] = (1, 0) | |||
|
99 | env['wsgi.url_scheme'] = 'http' | |||
|
100 | env['wsgi.input'] = self.rfile | |||
|
101 | env['wsgi.errors'] = _error_logger(self) | |||
|
102 | env['wsgi.multithread'] = isinstance(self.server, | |||
|
103 | SocketServer.ThreadingMixIn) | |||
|
104 | env['wsgi.multiprocess'] = isinstance(self.server, | |||
|
105 | SocketServer.ForkingMixIn) | |||
|
106 | env['wsgi.run_once'] = 0 | |||
|
107 | ||||
|
108 | self.close_connection = True | |||
|
109 | self.saved_status = None | |||
|
110 | self.saved_headers = [] | |||
|
111 | self.sent_headers = False | |||
|
112 | self.length = None | |||
|
113 | req = self.server.reqmaker(env, self._start_response) | |||
|
114 | for data in req: | |||
|
115 | if data: | |||
|
116 | self._write(data) | |||
86 |
|
117 | |||
87 | req = hgrequest(self.rfile, self.wfile, env) |
|
118 | def send_headers(self): | |
88 | self.send_response(200, "Script output follows") |
|
119 | if not self.saved_status: | |
89 | self.close_connection = self.server.make_and_run_handler(req) |
|
120 | raise AssertionError("Sending headers before start_response() called") | |
|
121 | saved_status = self.saved_status.split(None, 1) | |||
|
122 | saved_status[0] = int(saved_status[0]) | |||
|
123 | self.send_response(*saved_status) | |||
|
124 | should_close = True | |||
|
125 | for h in self.saved_headers: | |||
|
126 | self.send_header(*h) | |||
|
127 | if h[0].lower() == 'content-length': | |||
|
128 | should_close = False | |||
|
129 | self.length = int(h[1]) | |||
|
130 | if should_close: | |||
|
131 | self.send_header('Connection', 'close') | |||
|
132 | self.close_connection = should_close | |||
|
133 | self.end_headers() | |||
|
134 | self.sent_headers = True | |||
|
135 | ||||
|
136 | def _start_response(self, http_status, headers, exc_info=None): | |||
|
137 | code, msg = http_status.split(None, 1) | |||
|
138 | code = int(code) | |||
|
139 | self.saved_status = http_status | |||
|
140 | bad_headers = ('connection', 'transfer-encoding') | |||
|
141 | self.saved_headers = [ h for h in headers \ | |||
|
142 | if h[0].lower() not in bad_headers ] | |||
|
143 | return self._write | |||
|
144 | ||||
|
145 | def _write(self, data): | |||
|
146 | if not self.saved_status: | |||
|
147 | raise AssertionError("data written before start_response() called") | |||
|
148 | elif not self.sent_headers: | |||
|
149 | self.send_headers() | |||
|
150 | if self.length is not None: | |||
|
151 | if len(data) > self.length: | |||
|
152 | raise AssertionError("Content-length header sent, but more bytes than specified are being written.") | |||
|
153 | self.length = self.length - len(data) | |||
|
154 | self.wfile.write(data) | |||
|
155 | self.wfile.flush() | |||
90 |
|
156 | |||
91 | def create_server(ui, repo): |
|
157 | def create_server(ui, repo): | |
92 | use_threads = True |
|
158 | use_threads = True | |
@@ -126,8 +192,9 b' def create_server(ui, repo):' | |||||
126 | self.webdir_conf = webdir_conf |
|
192 | self.webdir_conf = webdir_conf | |
127 | self.webdirmaker = hgwebdir |
|
193 | self.webdirmaker = hgwebdir | |
128 | self.repoviewmaker = hgweb |
|
194 | self.repoviewmaker = hgweb | |
|
195 | self.reqmaker = wsgiapplication(self.make_handler) | |||
129 |
|
196 | |||
130 |
def make_ |
|
197 | def make_handler(self): | |
131 | if self.webdir_conf: |
|
198 | if self.webdir_conf: | |
132 | hgwebobj = self.webdirmaker(self.webdir_conf) |
|
199 | hgwebobj = self.webdirmaker(self.webdir_conf) | |
133 | elif self.repo is not None: |
|
200 | elif self.repo is not None: | |
@@ -135,8 +202,7 b' def create_server(ui, repo):' | |||||
135 | repo.origroot)) |
|
202 | repo.origroot)) | |
136 | else: |
|
203 | else: | |
137 | raise hg.RepoError(_('no repo found')) |
|
204 | raise hg.RepoError(_('no repo found')) | |
138 |
hgwebobj |
|
205 | return hgwebobj | |
139 | return req.will_close |
|
|||
140 |
|
206 | |||
141 | class IPv6HTTPServer(MercurialHTTPServer): |
|
207 | class IPv6HTTPServer(MercurialHTTPServer): | |
142 | address_family = getattr(socket, 'AF_INET6', None) |
|
208 | address_family = getattr(socket, 'AF_INET6', None) | |
@@ -144,7 +210,7 b' def create_server(ui, repo):' | |||||
144 | def __init__(self, *args, **kwargs): |
|
210 | def __init__(self, *args, **kwargs): | |
145 | if self.address_family is None: |
|
211 | if self.address_family is None: | |
146 | raise hg.RepoError(_('IPv6 not available on this system')) |
|
212 | raise hg.RepoError(_('IPv6 not available on this system')) | |
147 | super(IPv6HTTPServer, self).__init__(*args, **kargs) |
|
213 | super(IPv6HTTPServer, self).__init__(*args, **kwargs) | |
148 |
|
214 | |||
149 | if use_ipv6: |
|
215 | if use_ipv6: | |
150 | return IPv6HTTPServer((address, port), _hgwebhandler) |
|
216 | return IPv6HTTPServer((address, port), _hgwebhandler) |
@@ -225,6 +225,10 b' def isodate(date):' | |||||
225 | '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.''' |
|
225 | '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.''' | |
226 | return util.datestr(date, format='%Y-%m-%d %H:%M') |
|
226 | return util.datestr(date, format='%Y-%m-%d %H:%M') | |
227 |
|
227 | |||
|
228 | def hgdate(date): | |||
|
229 | '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.''' | |||
|
230 | return "%d %d" % date | |||
|
231 | ||||
228 | def nl2br(text): |
|
232 | def nl2br(text): | |
229 | '''replace raw newlines with xhtml line breaks.''' |
|
233 | '''replace raw newlines with xhtml line breaks.''' | |
230 | return text.replace('\n', '<br/>\n') |
|
234 | return text.replace('\n', '<br/>\n') | |
@@ -282,6 +286,7 b' common_filters = {' | |||||
282 | "fill76": lambda x: fill(x, width=76), |
|
286 | "fill76": lambda x: fill(x, width=76), | |
283 | "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'), |
|
287 | "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'), | |
284 | "tabindent": lambda x: indent(x, '\t'), |
|
288 | "tabindent": lambda x: indent(x, '\t'), | |
|
289 | "hgdate": hgdate, | |||
285 | "isodate": isodate, |
|
290 | "isodate": isodate, | |
286 | "obfuscate": obfuscate, |
|
291 | "obfuscate": obfuscate, | |
287 | "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--", |
|
292 | "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--", |
@@ -859,6 +859,49 b" def datestr(date=None, format='%a %b %d " | |||||
859 | s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60)) |
|
859 | s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60)) | |
860 | return s |
|
860 | return s | |
861 |
|
861 | |||
|
862 | def strdate(string, format='%a %b %d %H:%M:%S %Y'): | |||
|
863 | """parse a localized time string and return a (unixtime, offset) tuple. | |||
|
864 | if the string cannot be parsed, ValueError is raised.""" | |||
|
865 | def hastimezone(string): | |||
|
866 | return (string[-4:].isdigit() and | |||
|
867 | (string[-5] == '+' or string[-5] == '-') and | |||
|
868 | string[-6].isspace()) | |||
|
869 | ||||
|
870 | if hastimezone(string): | |||
|
871 | date, tz = string.rsplit(None, 1) | |||
|
872 | tz = int(tz) | |||
|
873 | offset = - 3600 * (tz / 100) - 60 * (tz % 100) | |||
|
874 | else: | |||
|
875 | date, offset = string, 0 | |||
|
876 | when = int(time.mktime(time.strptime(date, format))) + offset | |||
|
877 | return when, offset | |||
|
878 | ||||
|
879 | def parsedate(string, formats=('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M')): | |||
|
880 | """parse a localized time string and return a (unixtime, offset) tuple. | |||
|
881 | The date may be a "unixtime offset" string or in one of the specified | |||
|
882 | formats.""" | |||
|
883 | try: | |||
|
884 | when, offset = map(int, string.split(' ')) | |||
|
885 | except ValueError: | |||
|
886 | for format in formats: | |||
|
887 | try: | |||
|
888 | when, offset = strdate(string, format) | |||
|
889 | except ValueError: | |||
|
890 | pass | |||
|
891 | else: | |||
|
892 | break | |||
|
893 | else: | |||
|
894 | raise ValueError(_('invalid date: %r') % string) | |||
|
895 | # validate explicit (probably user-specified) date and | |||
|
896 | # time zone offset. values must fit in signed 32 bits for | |||
|
897 | # current 32-bit linux runtimes. timezones go from UTC-12 | |||
|
898 | # to UTC+14 | |||
|
899 | if abs(when) > 0x7fffffff: | |||
|
900 | raise ValueError(_('date exceeds 32 bits: %d') % when) | |||
|
901 | if offset < -50400 or offset > 43200: | |||
|
902 | raise ValueError(_('impossible time zone offset: %d') % offset) | |||
|
903 | return when, offset | |||
|
904 | ||||
862 | def shortuser(user): |
|
905 | def shortuser(user): | |
863 | """Return a short representation of a user name or email address.""" |
|
906 | """Return a short representation of a user name or email address.""" | |
864 | f = user.find('@') |
|
907 | f = user.find('@') |
@@ -1,7 +1,7 b'' | |||||
1 | #header# |
|
1 | #header# | |
2 | # HG changeset patch |
|
2 | # HG changeset patch | |
3 | # User #author# |
|
3 | # User #author# | |
4 | # Date #date|date# |
|
4 | # Date #date|hgdate# | |
5 | # Node ID #node# |
|
5 | # Node ID #node# | |
6 | #parent%changesetparent# |
|
6 | #parent%changesetparent# | |
7 | #desc# |
|
7 | #desc# |
@@ -5,8 +5,8 b" difflineplus = '#line#'" | |||||
5 | difflineminus = '#line#' |
|
5 | difflineminus = '#line#' | |
6 | difflineat = '#line#' |
|
6 | difflineat = '#line#' | |
7 | diffline = '#line#' |
|
7 | diffline = '#line#' | |
8 |
changesetparent = '# |
|
8 | changesetparent = '# Parent #node#' | |
9 |
changesetchild = '# |
|
9 | changesetchild = '# Child #node#' | |
10 | filenodelink = '' |
|
10 | filenodelink = '' | |
11 | filerevision = 'Content-Type: #mimetype#\nContent-Disposition: filename=#file#\n\n#raw#' |
|
11 | filerevision = 'Content-Type: #mimetype#\nContent-Disposition: filename=#file#\n\n#raw#' | |
12 | fileline = '#line#' |
|
12 | fileline = '#line#' |
@@ -14,8 +14,10 b' cd ..' | |||||
14 | hg init new |
|
14 | hg init new | |
15 | # http incoming |
|
15 | # http incoming | |
16 | http_proxy= hg -R new incoming http://localhost:20059/ |
|
16 | http_proxy= hg -R new incoming http://localhost:20059/ | |
|
17 | http_proxy= hg -R new incoming -r 4 http://localhost:20059/ | |||
17 | # local incoming |
|
18 | # local incoming | |
18 | hg -R new incoming test |
|
19 | hg -R new incoming test | |
|
20 | hg -R new incoming -r 4 test | |||
19 |
|
21 | |||
20 | # test with --bundle |
|
22 | # test with --bundle | |
21 | http_proxy= hg -R new incoming --bundle test.hg http://localhost:20059/ |
|
23 | http_proxy= hg -R new incoming --bundle test.hg http://localhost:20059/ | |
@@ -42,5 +44,6 b' hg verify' | |||||
42 | cd .. |
|
44 | cd .. | |
43 | hg -R test-dev outgoing test |
|
45 | hg -R test-dev outgoing test | |
44 | http_proxy= hg -R test-dev outgoing http://localhost:20059/ |
|
46 | http_proxy= hg -R test-dev outgoing http://localhost:20059/ | |
|
47 | http_proxy= hg -R test-dev outgoing -r 11 http://localhost:20059/ | |||
45 |
|
48 | |||
46 | kill `cat test/hg.pid` |
|
49 | kill `cat test/hg.pid` |
@@ -75,6 +75,31 b' user: test' | |||||
75 | date: Mon Jan 12 13:46:40 1970 +0000 |
|
75 | date: Mon Jan 12 13:46:40 1970 +0000 | |
76 | summary: 4 |
|
76 | summary: 4 | |
77 |
|
77 | |||
|
78 | changeset: 0:9cb21d99fe27 | |||
|
79 | user: test | |||
|
80 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
81 | summary: 0 | |||
|
82 | ||||
|
83 | changeset: 1:d717f5dfad6a | |||
|
84 | user: test | |||
|
85 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
86 | summary: 1 | |||
|
87 | ||||
|
88 | changeset: 2:c0d6b86da426 | |||
|
89 | user: test | |||
|
90 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
91 | summary: 2 | |||
|
92 | ||||
|
93 | changeset: 3:dfacbd43b3fe | |||
|
94 | user: test | |||
|
95 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
96 | summary: 3 | |||
|
97 | ||||
|
98 | changeset: 4:1f3a964b6022 | |||
|
99 | user: test | |||
|
100 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
101 | summary: 4 | |||
|
102 | ||||
78 | changeset: 5:c028bcc7a28a |
|
103 | changeset: 5:c028bcc7a28a | |
79 | user: test |
|
104 | user: test | |
80 | date: Mon Jan 12 13:46:40 1970 +0000 |
|
105 | date: Mon Jan 12 13:46:40 1970 +0000 | |
@@ -121,6 +146,31 b' user: test' | |||||
121 | date: Mon Jan 12 13:46:40 1970 +0000 |
|
146 | date: Mon Jan 12 13:46:40 1970 +0000 | |
122 | summary: 4 |
|
147 | summary: 4 | |
123 |
|
148 | |||
|
149 | changeset: 0:9cb21d99fe27 | |||
|
150 | user: test | |||
|
151 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
152 | summary: 0 | |||
|
153 | ||||
|
154 | changeset: 1:d717f5dfad6a | |||
|
155 | user: test | |||
|
156 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
157 | summary: 1 | |||
|
158 | ||||
|
159 | changeset: 2:c0d6b86da426 | |||
|
160 | user: test | |||
|
161 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
162 | summary: 2 | |||
|
163 | ||||
|
164 | changeset: 3:dfacbd43b3fe | |||
|
165 | user: test | |||
|
166 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
167 | summary: 3 | |||
|
168 | ||||
|
169 | changeset: 4:1f3a964b6022 | |||
|
170 | user: test | |||
|
171 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
172 | summary: 4 | |||
|
173 | ||||
124 | changeset: 5:c028bcc7a28a |
|
174 | changeset: 5:c028bcc7a28a | |
125 | user: test |
|
175 | user: test | |
126 | date: Mon Jan 12 13:46:40 1970 +0000 |
|
176 | date: Mon Jan 12 13:46:40 1970 +0000 | |
@@ -270,3 +320,19 b' user: test' | |||||
270 | date: Mon Jan 12 13:46:40 1970 +0000 |
|
320 | date: Mon Jan 12 13:46:40 1970 +0000 | |
271 | summary: 13 |
|
321 | summary: 13 | |
272 |
|
322 | |||
|
323 | searching for changes | |||
|
324 | changeset: 9:3741c3ad1096 | |||
|
325 | user: test | |||
|
326 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
327 | summary: 9 | |||
|
328 | ||||
|
329 | changeset: 10:de4143c8d9a5 | |||
|
330 | user: test | |||
|
331 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
332 | summary: 10 | |||
|
333 | ||||
|
334 | changeset: 11:0e1c188b9a7a | |||
|
335 | user: test | |||
|
336 | date: Mon Jan 12 13:46:40 1970 +0000 | |||
|
337 | summary: 11 | |||
|
338 |
General Comments 0
You need to be logged in to leave comments.
Login now