'프로그램 사용 > Version Control' 카테고리의 다른 글
| svn externals 제약사항 (파일은 안됨) (0) | 2017.11.03 |
|---|---|
| tortoiseSVN 엑셀 비교 (0) | 2017.09.16 |
| git 공부.. (2) | 2017.04.25 |
| git-svn 관련글 (0) | 2017.04.25 |
| svn list 예제 (0) | 2017.02.03 |
| svn externals 제약사항 (파일은 안됨) (0) | 2017.11.03 |
|---|---|
| tortoiseSVN 엑셀 비교 (0) | 2017.09.16 |
| git 공부.. (2) | 2017.04.25 |
| git-svn 관련글 (0) | 2017.04.25 |
| svn list 예제 (0) | 2017.02.03 |
후배를 갈아먹자 -_-
[링크 : https://git-scm.com/book/en/v2]
[링크 : https://git-scm.com/book/ko/v2]
git fetch(서버에서 받아오기) git merge(병합) >> git pull git checkout (브랜치 선택) git commit 로컬 저장소 저장 git push 원격 저장소 저장 git clone git init (로컬 저장소 생성) git remote add repo (로컬 저장소와 원격저장소 연결) git pull (원격 저장소의 정보를 내려받음) >> git clone repo (로컬 저장소와 원격저장소 동일) |
| tortoiseSVN 엑셀 비교 (0) | 2017.09.16 |
|---|---|
| sorucetree / git client (0) | 2017.07.03 |
| git-svn 관련글 (0) | 2017.04.25 |
| svn list 예제 (0) | 2017.02.03 |
| svn hook encv (0) | 2016.12.30 |
dcommit이라는걸 이제야 발견해서 검색..
음... 나중에 한번 써먹어볼까?
$ git svn dcommit
[링크 : http://www.allofsoftware.net/2011/08/blog-post.html]
[링크 : https://git-scm.com/book/ko/v1/Git으로-이전하기-Git과-Subversion]
2011/12/23 - [프로그램 사용/CVS & SVN & GIT & Mercurial] - git-svn
| sorucetree / git client (0) | 2017.07.03 |
|---|---|
| git 공부.. (2) | 2017.04.25 |
| svn list 예제 (0) | 2017.02.03 |
| svn hook encv (0) | 2016.12.30 |
| svn commit email - python / synology (0) | 2016.12.30 |
역시 결론은.. 꺼부기가 최고야 -_-!
$ svn list file:///home/pi/repo@2 --verbose |
$ svn help list list (ls): List directory entries in the repository. usage: list [TARGET[@REV]...] List each TARGET file and the contents of each TARGET directory as they exist in the repository. If TARGET is a working copy path, the corresponding repository URL will be used. If specified, REV determines in which revision the target is first looked up. The default TARGET is '.', meaning the repository URL of the current working directory. With --verbose, the following fields will be shown for each item: Revision number of the last commit Author of the last commit If locked, the letter 'O'. (Use 'svn info URL' to see details) Size (in bytes) Date and time of the last commit Valid options: -r [--revision] ARG : ARG (some commands also take ARG1:ARG2 range) A revision argument can be one of: NUMBER revision number '{' DATE '}' revision at start of the date 'HEAD' latest in repository 'BASE' base rev of item's working copy 'COMMITTED' last commit at or before BASE 'PREV' revision just before COMMITTED -v [--verbose] : print extra information -R [--recursive] : descend recursively, same as --depth=infinity --depth ARG : limit operation by depth ARG ('empty', 'files', 'immediates', or 'infinity') --incremental : give output suitable for concatenation --xml : output in XML --include-externals : include externals definitions Global options: --username ARG : specify a username ARG --password ARG : specify a password ARG --no-auth-cache : do not cache authentication tokens --non-interactive : do no interactive prompting (default is to prompt only if standard input is a terminal device) --force-interactive : do interactive prompting even if standard input is not a terminal device --trust-server-cert : accept SSL server certificates from unknown certificate authorities without prompting (but only with '--non-interactive') --config-dir ARG : read user configuration files from directory ARG --config-option ARG : set user configuration option in the format: FILE:SECTION:OPTION=[VALUE] For example: servers:global:http-library=serf |
[링크 : http://svnbook.red-bean.com/en/1.7/svn.tour.history.html]
| git 공부.. (2) | 2017.04.25 |
|---|---|
| git-svn 관련글 (0) | 2017.04.25 |
| svn hook encv (0) | 2016.12.30 |
| svn commit email - python / synology (0) | 2016.12.30 |
| svn diff 결과물 컬러로 보기 (0) | 2016.12.30 |
으으 말일까지 나와서 테스트 해보네 ㅠㅠ
LC_ALL 만 설정하고 LC_ALL=""로 빼고 테스트 하니 한글이 깨지는 현상 발생
그래서 테스트 해보니 되는 듯
아무튼 결론
synology nas에서
svn 1.9.4 인데
hook-env 로는 설정이 안되고
post-commit 내에서
로 로케일과 언어를 설정해주니 문제없이 해결 -_-
export LC_ALL=en_US.utf8 export LANG=en_US.utf8 |
[링크 : https://www.lesstif.com/pages/viewpage.action?pageId=18220003]
[링크 : http://svnbook.red-bean.com/vi/1.8/svn.reposadmin.create.html]
[링크 : http://askubuntu.com/questions/795455/python-default-locale-is-not-working]
| git-svn 관련글 (0) | 2017.04.25 |
|---|---|
| svn list 예제 (0) | 2017.02.03 |
| svn commit email - python / synology (0) | 2016.12.30 |
| svn diff 결과물 컬러로 보기 (0) | 2016.12.30 |
| svn commit시 email 알림 (0) | 2016.12.29 |
svn에서 hook을 통해 commit-post를 실행하면 희한하게 한글이 깨지네..
파이프쪽 로케일이 문제인가..
$ cat mailer.py #!/usr/bin/python # -*- coding:utf-8 -*- import smtplib import sys import os from email.mime.multipart import MIMEMultipart from email.MIMEText import MIMEText from subprocess import Popen, PIPE smtp_hostname="smtp.mailserver.com:port" smtp_username="mailer@mailserver.com" smtp_password="password" toaddrs = ['user1@mailserver.com','user2@mailserver.com','user2@mailserver.com'] project_name = os.path.basename(sys.argv[1]) subject = "["+project_name+"]"+" svn repository update required" content = project_name + "\n" + "rev : " + sys.argv[2]+ "\n" cmd_1 = ['svnlook','changed','-r',sys.argv[2],sys.argv[1]] proc = Popen(cmd_1, stdout=PIPE) difflist = proc.stdout.read() cmd_2 = ['svnlook','info','-r',sys.argv[2],sys.argv[1]] proc = Popen(cmd_2, stdout=PIPE) svninfo = proc.stdout.read() content = "[" + project_name + "]\n" + "rev : " + sys.argv[2]+ "\n" + difflist +"\n" + svninfo msg = MIMEMultipart() msg['From'] = smtp_username msg['To'] = ", ".join(toaddrs) msg['Subject'] = subject msg.attach(MIMEText(content,_charset='utf-8')) print msg.as_string() server = smtplib.SMTP_SSL(smtp_hostname) server.login(smtp_username,smtp_password) server.sendmail(smtp_username, toaddrs, msg.as_string()) server.quit() |
[링크 : http://stackoverflow.com/questions/10147455/how-to-send-an-email-with-gmail-as-provider-using-python]
[링크 : http://stackoverflow.com/.../python-subject-not-shown-when-sending-email-using-smtplib-module]
[링크 : http://blog.saltfactory.net/python/send-mail-via-smtp-and-python.html]
[링크 : http://ngee.tistory.com/159]
[링크 : http://www.janosgyerik.com/setup-and-test-svn-post-commit-hook-to-send-commit-log/]
[링크 : http://stackoverflow.com/questions/3925096/how-to-get-only-the-last-part-of-a-path-in-python]
[링크 : https://docs.python.org/2/library/email-examples.html]
[링크 : http://stackoverflow.com/questions/4537259/python-how-to-pipe-the-output-using-popen]
msg.attach(MIMEText(content,'plain',_charset="utf-8"))
[링크 : http://stackoverflow.com/questions/882712/sending-html-email-using-python]
[링크 : http://blog.saltfactory.net/python/send-mail-via-smtp-and-python.html]
| svn list 예제 (0) | 2017.02.03 |
|---|---|
| svn hook encv (0) | 2016.12.30 |
| svn diff 결과물 컬러로 보기 (0) | 2016.12.30 |
| svn commit시 email 알림 (0) | 2016.12.29 |
| svn console에서 엔터 입력하기 (0) | 2016.11.08 |
리눅스에서 하다 보니 귀찮은게 하나둘씩 보이네 ㅋㅋ
$ sudo apt-get install colordiff $ vi ~/.subversion/config [helpers] diff-cmd = colordiff |
| svn hook encv (0) | 2016.12.30 |
|---|---|
| svn commit email - python / synology (0) | 2016.12.30 |
| svn commit시 email 알림 (0) | 2016.12.29 |
| svn console에서 엔터 입력하기 (0) | 2016.11.08 |
| svn add를 취소하기 (0) | 2016.11.04 |
구글로 하면 잘되나
다만, 구글에서 차단하지 않도록 "보안 수준이 낮은 앱"을 허용해야 한다.
커밋시 이메일은 이렇게 오긴 온다.
---
svn 저장소 안에 보면 이런식으로 구성이 되어있는데
~/repos $ ll 합계 24 -rw-r--r-- 1 pi pi 246 12월 29 13:48 README.txt drwxr-xr-x 2 pi pi 4096 12월 29 13:48 conf drwxr-sr-x 6 pi pi 4096 12월 29 13:48 db -r--r--r-- 1 pi pi 2 12월 29 13:48 format drwxr-xr-x 2 pi pi 4096 12월 29 13:48 hooks drwxr-xr-x 2 pi pi 4096 12월 29 13:48 locks |
hooks에 보면 이런식으로 템플릿 파일들이 존재한다.
~/repos/hooks $ ll 합계 36 -rwxr-xr-x 1 pi pi 2107 12월 29 13:48 post-commit.tmpl -rwxr-xr-x 1 pi pi 1663 12월 29 13:48 post-lock.tmpl -rwxr-xr-x 1 pi pi 2344 12월 29 13:48 post-revprop-change.tmpl -rwxr-xr-x 1 pi pi 1592 12월 29 13:48 post-unlock.tmpl -rwxr-xr-x 1 pi pi 3510 12월 29 13:48 pre-commit.tmpl -rwxr-xr-x 1 pi pi 2434 12월 29 13:48 pre-lock.tmpl -rwxr-xr-x 1 pi pi 2818 12월 29 13:48 pre-revprop-change.tmpl -rwxr-xr-x 1 pi pi 2122 12월 29 13:48 pre-unlock.tmpl -rwxr-xr-x 1 pi pi 3235 12월 29 13:48 start-commit.tmpl |
아무튼 post-commit을 열어 보면 얘는 템플릿이니 수정해서 써라라고 한다.
일단 버전마다 변수가 갯수가 다르네?
$ cat post-commit.tmpl #!/bin/sh # POST-COMMIT HOOK # # The post-commit hook is invoked after a commit. Subversion runs # this hook by invoking a program (script, executable, binary, etc.) # named 'post-commit' (for which this file is a template) with the # following ordered arguments: # # [1] REPOS-PATH (the path to this repository) # [2] REV (the number of the revision just committed) # [3] TXN-NAME (the name of the transaction that has become REV) # # The default working directory for the invocation is undefined, so # the program should set one explicitly if it cares. # # Because the commit has already completed and cannot be undone, # the exit code of the hook program is ignored. The hook program # can use the 'svnlook' utility to help it examine the # newly-committed tree. # # On a Unix system, the normal procedure is to have 'post-commit' # invoke other programs to do the real work, though it may do the # work itself too. # # Note that 'post-commit' must be executable by the user(s) who will # invoke it (typically the user httpd runs as), and that user must # have filesystem-level permission to access the repository. # # On a Windows system, you should name the hook program # 'post-commit.bat' or 'post-commit.exe', # but the basic idea is the same. # # The hook program typically does not inherit the environment of # its parent process. For example, a common problem is for the # PATH environment variable to not be set to its usual value, so # that subprograms fail to launch unless invoked via absolute path. # If you're having unexpected problems with a hook program, the # culprit may be unusual (or missing) environment variables. # # Here is an example hook script, for a Unix /bin/sh interpreter. # For more examples and pre-written hooks, see those in # /usr/share/subversion/hook-scripts, and in the repository at # http://svn.apache.org/repos/asf/subversion/trunk/tools/hook-scripts/ and # http://svn.apache.org/repos/asf/subversion/trunk/contrib/hook-scripts/ REPOS="$1" REV="$2" TXN_NAME="$3" "$REPOS"/hooks/mailer.py commit "$REPOS" $REV "$REPOS"/mailer.conf |
근데 저 망할(!) mailer.py가 없어서 검색을 해보니
python-mailer나 subversion-tools가 맞을거 같고
$ sudo apt-file search mailer.py bzr-email: /usr/lib/python2.7/dist-packages/bzrlib/plugins/email/emailer.py bzr-email: /usr/share/pyshared/bzrlib/plugins/email/emailer.py gourmet: /usr/lib/python2.7/dist-packages/gourmet/exporters/recipe_emailer.py gourmet: /usr/lib/python2.7/dist-packages/gourmet/plugins/email_plugin/emailer.py gourmet: /usr/lib/python2.7/dist-packages/gourmet/plugins/email_plugin/recipe_emailer.py python-apptools: /usr/lib/python2.7/dist-packages/apptools/logger/agent/quality_agent_mailer.py python-apptools: /usr/share/pyshared/apptools/logger/agent/quality_agent_mailer.py python-enthoughtbase: /usr/lib/python2.6/dist-packages/enthought/logger/agent/quality_agent_mailer.py python-enthoughtbase: /usr/lib/python2.7/dist-packages/enthought/logger/agent/quality_agent_mailer.py python-enthoughtbase: /usr/share/pyshared/enthought/logger/agent/quality_agent_mailer.py python-mailer: /usr/share/pyshared/mailer.py python-mailutils: /usr/lib/python2.7/dist-packages/mailutils/mailer.py python-scrapy: /usr/lib/python2.7/dist-packages/scrapy/contrib/statsmailer.py python-zope.sendmail: /usr/lib/python2.6/dist-packages/zope/sendmail/mailer.py python-zope.sendmail: /usr/lib/python2.6/dist-packages/zope/sendmail/tests/test_mailer.py python-zope.sendmail: /usr/lib/python2.7/dist-packages/zope/sendmail/mailer.py python-zope.sendmail: /usr/lib/python2.7/dist-packages/zope/sendmail/tests/test_mailer.py python-zope.sendmail: /usr/share/pyshared/zope/sendmail/mailer.py python-zope.sendmail: /usr/share/pyshared/zope/sendmail/tests/test_mailer.py roundup: /usr/lib/python2.7/dist-packages/roundup/mailer.py sabnzbdplus: /usr/share/sabnzbdplus/sabnzbd/emailer.py subversion-tools: /usr/share/subversion/hook-scripts/mailer/mailer.py zope2.13: /usr/lib/zope2.13/lib/python/Products.MailHost-2.13.1.egg/Products/MailHost/mailer.py zope2.13: /usr/lib/zope2.13/lib/python/zope.sendmail-3.7.5.egg/zope/sendmail/mailer.py zope2.13: /usr/lib/zope2.13/lib/python/zope.sendmail-3.7.5.egg/zope/sendmail/tests/test_mailer.py |
설치를 하니 온갖 패키지가 다 끌려오네..
synology에서 이거 쓸 수 있긴 할려나?
$ sudo apt-get install subversion-tools 패키지 목록을 읽는 중입니다... 완료 의존성 트리를 만드는 중입니다 상태 정보를 읽는 중입니다... 완료 다음 패키지를 더 설치할 것입니다: bsd-mailx exim4 exim4-base exim4-config exim4-daemon-light libconfig-inifiles-perl libsvn-perl liburi-perl python-subversion svn2cl xsltproc 제안하는 패키지: eximon4 exim4-doc-html exim4-doc-info spf-tools-perl swaks libwww-perl ruby-svn 추천하는 패키지: mailx 다음 새 패키지를 설치할 것입니다: bsd-mailx exim4 exim4-base exim4-config exim4-daemon-light libconfig-inifiles-perl libsvn-perl liburi-perl python-subversion subversion-tools svn2cl xsltproc 0개 업그레이드, 12개 새로 설치, 0개 제거 및 33개 업그레이드 안 함. 4,205 k바이트 아카이브를 받아야 합니다. 이 작업 후 13.6 M바이트의 디스크 공간을 더 사용하게 됩니다. 계속 하시겠습니까? [Y/n] |
$ sudo apt-file search mailer.conf mixmaster: /etc/mixmaster/remailer.conf subversion-tools: /usr/share/subversion/hook-scripts/mailer/mailer.conf.example svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config-module.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config-pysrc.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config.ConfigFileSettings-class.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config.ConfigInvalidError-class.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config.ConfigMappingSectionNotFoundError-class.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config.ConfigMappingSpecInvalidError-class.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config.ConfigMissingError-class.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config.ConfigNotFoundError-class.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config.ConfigOptionUnknownError-class.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config.ConfigSectionNotFoundError-class.html svnmailer: /usr/share/doc/svnmailer/docs/apidoc/svnmailer.config.Error-class.html svnmailer: /usr/share/doc/svnmailer/examples/svnmailer.conf |
그나저나 설정파일 드럽게 기네
$ cat /usr/share/subversion/hook-scripts/mailer/mailer.conf.example # # mailer.conf: example configuration file for mailer.py # # $Id: mailer.conf.example 1439592 2013-01-28 19:20:53Z danielsh $ [general] # The [general].diff option is now DEPRECATED. # Instead use [defaults].diff . # # One delivery method must be chosen. mailer.py will prefer using the # "mail_command" option. If that option is empty or commented out, # then it checks whether the "smtp_hostname" option has been # specified. If neither option is set, then the commit message is # delivered to stdout. # # This command will be invoked with destination addresses on the command # line, and the message piped into it. #mail_command = /usr/sbin/sendmail # This option specifies the hostname for delivery via SMTP. #smtp_hostname = localhost # Username and password for SMTP servers requiring authorisation. #smtp_username = example #smtp_password = example # -------------------------------------------------------------------------- # # CONFIGURATION GROUPS # # Any sections other than [general], [defaults], [maps] and sections # referred to within [maps] are considered to be user-defined groups # which override values in the [defaults] section. # These groups are selected using the following three options: # # for_repos # for_paths # search_logmsg # # Each option specifies a regular expression. for_repos is matched # against the absolute path to the repository the mailer is operating # against. for_paths is matched against *every* path (files and # dirs) that was modified during the commit. # # The options specified in the [defaults] section are always selected. The # presence of a non-matching for_repos has no relevance. Note that you may # still use a for_repos value to extract useful information (more on this # later). Any user-defined groups without a for_repos, or which contains # a matching for_repos, will be selected for potential use. # # The subset of user-defined groups identified by the repository are further # refined based on the for_paths option. A group is selected if at least # one path(*) in the commit matches the for_paths regular expression. Note # that the paths are relative to the root of the repository and do not # have a leading slash. # # (*) Actually, each path will select just one group. Thus, it is possible # that one group will match against all paths, while another group matches # none of the paths, even though its for_paths would have selected some of # the paths in the commit. # # search_logmsg specifies a regular expression to match against the # log message. If the regular expression does not match the log # message, the group is not matched; if the regular expression matches # once, the group is used. If there are multiple matches, each # successful match generates another group-match (this is useful if # "named groups" are used). If search_logmsg is not used, no log # message filtering is performed. # # Groups are matched in no particular order. Do not depend upon their # order within this configuration file. The values from [defaults] will # be used if no group is matched or an option in a group does not override # the corresponding value from [defaults]. # # Generally, a commit email is generated for each group that has been # selected. The script will try to minimize mails, so it may be possible # that a single message will be generated to multiple recipients. In # addition, it is possible for multiple messages per group to be generated, # based on the various substitutions that are performed (see the following # section). # # # SUBSTITUTIONS # # The regular expressions can use the "named group" syntax to extract # interesting pieces of the repository or commit path. These named values # can then be substituted in the option values during mail generation. # # For example, let's say that you have a repository with a top-level # directory named "clients", with several client projects underneath: # # REPOS/ # clients/ # gsvn/ # rapidsvn/ # winsvn/ # # The client name can be extracted with a regular expression like: # # for_paths = clients/(?P<client>[^/]*)($|/) # # The substitution is performed using Python's dict-based string # interpolation syntax: # # to_addr = commits@%(client)s.tigris.org # # The %(NAME)s syntax will substitute whatever value for NAME was captured # in the for_repos and for_paths regular expressions. The set of names # available is obtained from the following set of regular expressions: # # [defaults].for_repos (if present) # [GROUP].for_repos (if present in the user-defined group "GROUP") # [GROUP].for_paths (if present in the user-defined group "GROUP") # # The names from the regexes later in the list override the earlier names. # If none of the groups match, but a for_paths is present in [defaults], # then its extracted names will be available. # # Further suppose you want to match bug-ids in log messages: # # search_logmsg = (?P<bugid>(ProjA|ProjB)#\d) # # The bugids would be of the form ProjA#123 and ProjB#456. In this # case, each time the regular expression matches, another match group # will be generated. Thus, if you use: # # commit_subject_prefix = %(bugid)s: # # Then, a log message such as "Fixes ProjA#123 and ProjB#234" would # match both bug-ids, and two emails would be generated - one with # subject "ProjA#123: <...>" and "ProjB#234: <...>". # # Note that each unique set of names for substitution will generate an # email. In the above example, if a commit modified files in all three # client subdirectories, then an email will be sent to all three commits@ # mailing lists on tigris.org. # # The substitution variable "author" is provided by default, and is set # to the author name passed to mailer.py for revprop changes or the # author defined for a revision; if neither is available, then it is # set to "no_author". Thus, you might define a line like: # # from_addr = %(author)s@example.com # # The substitution variable "repos_basename" is provided, and is set to # the directory name of the repository. This can be useful to set # a custom subject that can be re-used in multiple repositories: # # commit_subject_prefix = [svn-%(repos_basename)s] # # For example if the repository is at /path/to/repo/project-x then # the subject of commit emails will be prefixed with [svn-project-x] # # # SUMMARY # # While mailer.py will work to minimize the number of mail messages # generated, a single commit can potentially generate a large number # of variants of a commit message. The criteria for generating messages # is based on: # # groups selected by for_repos # groups selected by for_paths # unique sets of parameters extracted by the above regular expressions # [defaults] # This is not passed to the shell, so do not use shell metacharacters. # The command is split around whitespace, so if you want to include # whitespace in the command, then ### something ###. diff = /usr/bin/diff -u -L %(label_from)s -L %(label_to)s %(from)s %(to)s # The default prefix for the Subject: header for commits. commit_subject_prefix = # The default prefix for the Subject: header for propchanges. propchange_subject_prefix = # The default prefix for the Subject: header for locks. lock_subject_prefix = # The default prefix for the Subject: header for unlocks. unlock_subject_prefix = # The default From: address for messages. If the from_addr is not # specified or it is specified but there is no text after the `=', # then the revision's author is used as the from address. If the # revision author is not specified, such as when a commit is done # without requiring authentication and authorization, then the string # 'no_author' is used. You can specify a default from_addr here and # if you want to have a particular for_repos group use the author as # the from address, you can use "from_addr =". from_addr = invalid@example.com # The default To: addresses for message. One or more addresses, # separated by whitespace (no commas). # NOTE: If you want to use a different character for separating the # addresses put it in front of the addresses included in square # brackets '[ ]'. to_addr = invalid@example.com # If this is set, then a Reply-To: will be inserted into the message. reply_to = # Specify which types of repository changes mailer.py will create # diffs for. Valid options are any combination of # 'add copy modify delete', or 'none' to never create diffs. # If the generate_diffs option is empty, the selection is controlled # by the deprecated options suppress_deletes and suppress_adds. # Note that this only affects the display of diffs - all changes are # mentioned in the summary of changed paths at the top of the message, # regardless of this option's value. # Meaning of the possible values: # add: generates diffs for all added paths # copy: generates diffs for all copied paths # which were not changed after copying # modify: generates diffs for all modified paths, including paths that were # copied and modified afterwards (within the same commit) # delete: generates diffs for all removed paths generate_diffs = add copy modify # Commit URL construction. This adds a URL to the top of the message # that can lead the reader to a Trac, ViewVC or other view of the # commit as a whole. # # The available substitution variable is: rev #commit_url = http://diffs.server.com/trac/software/changeset/%(rev)s # Diff URL construction. For the configured diff URL types, the diff # section (which follows the message header) will include the URL # relevant to the change type, even if actual diff generation for that # change type is disabled (per the generate_diffs option). # # Available substitution variables are: path, base_path, rev, base_rev #diff_add_url = #diff_copy_url = #diff_modify_url = http://diffs.server.com/?p1=%(base_path)s&p2=%(path)s #diff_delete_url = # When set to "yes", the mailer will suppress the creation of a diff which # deletes all the lines in the file. If this is set to anything else, or # is simply commented out, then the diff will be inserted. Note that the # deletion is always mentioned in the message header, regardless of this # option's value. ### DEPRECATED (if generate_diffs is not empty, this option is ignored) #suppress_deletes = yes # When set to "yes", the mailer will suppress the creation of a diff which # adds all the lines in the file. If this is set to anything else, or # is simply commented out, then the diff will be inserted. Note that the # addition is always mentioned in the message header, regardless of this # option's value. ### DEPRECATED (if generate_diffs is not empty, this option is ignored) #suppress_adds = yes # A revision is reported on if any of its changed paths match the # for_paths option. If only some of the changed paths of a revision # match, this variable controls the behaviour for the non-matching # paths. Possible values are: # # yes: (Default) Show in both summary and diffs. # summary: Show the changed paths in the summary, but omit the diffs. # no: Show nothing more than a note saying "and changes in other areas" # show_nonmatching_paths = yes # Subject line length limit. The generated subject line will be truncated # and terminated with "...", to remain within the specified maximum length. # Set to 0 to turn off. #truncate_subject = 200 # -------------------------------------------------------------------------- [maps] # # This section can be used define rewrite mappings for option values. It # is typically used for computing from/to addresses, but can actually be # used to remap values for any option in this file. # # The mappings are global for the entire configuration file. There is # no group-specific mapping capability. For each mapping that you want # to perform, you will provide the name of the option (e.g. from_addr) # and a specification of how to perform those mappings. These declarations # are made here in the [maps] section. # # When an option is accessed, the value is loaded from the configuration # file and all %(NAME)s substitutions are performed. The resulting value # is then passed through the map. If a map entry is not available for # the value, then it will be used unchanged. # # NOTES: - Avoid using map substitution names which differ only in case. # Unexpected results may occur. # - A colon ':' is also considered as separator between option and # value (keep this in mind when trying to map a file path under # windows). # # The format to declare a map is: # # option_name_to_remap = mapping_specification # # At the moment, there is only one type of mapping specification: # # mapping_specification = '[' sectionname ']' # # This will use the given section to map values. The option names in # the section are the input values, and the option values are the result. # # # EXAMPLE: # # We have two projects using two repositories. The name of the repos # does not easily map to their commit mailing lists, so we will use # a mapping to go from a project name (extracted from the repository # path) to their commit list. The committers also need a special # mapping to derive their email address from their repository username. # # [projects] # for_repos = .*/(?P<project>.*) # from_addr = %(author)s # to_addr = %(project)s # # [maps] # from_addr = [authors] # to_addr = [mailing-lists] # # [authors] # john = jconnor@example.com # sarah = sconnor@example.com # # [mailing-lists] # t600 = spottable-commits@example.com # tx = hotness-commits@example.com # # -------------------------------------------------------------------------- # # [example-group] # # send notifications if any web pages are changed # for_paths = .*\.html # # set a custom prefix # commit_subject_prefix = [commit] # propchange_subject_prefix = [propchange] # # override the default, sending these elsewhere # to_addr = www-commits@example.com # # use the revision author as the from address # from_addr = # # use a custom diff program for this group # diff = /usr/bin/my-diff -u -L %(label_from)s -L %(label_to)s %(from)s %(to)s # # [another-example] # # commits to personal repositories should go to that person # for_repos = /home/(?P<who>[^/]*)/repos # to_addr = %(who)s@example.com # # [issuetracker] # search_logmsg = (?P<bugid>(?P<project>projecta|projectb|projectc)#\d+) # # (or, use a mapping if the bug-id to email address is not this trivial) # to_addr = %(project)s-tracker@example.com # commit_subject_prefix = %(bugid)s: # propchange_subject_prefix = %(bugid)s:
|
아따.. 드럽게 길다 -_-
$ cat /usr/share/subversion/hook-scripts/mailer/mailer.py #!/usr/bin/python # -*- coding: utf-8 -*- # # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # # mailer.py: send email describing a commit # # $HeadURL: http://svn.apache.org/repos/asf/subversion/branches/1.8.x/tools/hook-scripts/mailer/mailer.py $ # $LastChangedDate: 2013-04-12 07:44:37 +0000 (Fri, 12 Apr 2013) $ # $LastChangedBy: rhuijben $ # $LastChangedRevision: 1467191 $ # # USAGE: mailer.py commit REPOS REVISION [CONFIG-FILE] # mailer.py propchange REPOS REVISION AUTHOR REVPROPNAME [CONFIG-FILE] # mailer.py propchange2 REPOS REVISION AUTHOR REVPROPNAME ACTION \ # [CONFIG-FILE] # mailer.py lock REPOS AUTHOR [CONFIG-FILE] # mailer.py unlock REPOS AUTHOR [CONFIG-FILE] # # Using CONFIG-FILE, deliver an email describing the changes between # REV and REV-1 for the repository REPOS. # # ACTION was added as a fifth argument to the post-revprop-change hook # in Subversion 1.2.0. Its value is one of 'A', 'M' or 'D' to indicate # if the property was added, modified or deleted, respectively. # # See _MIN_SVN_VERSION below for which version of Subversion's Python # bindings are required by this version of mailer.py. import os import sys try: # Python >=3.0 import configparser from urllib.parse import quote as urllib_parse_quote except ImportError: # Python <3.0 import ConfigParser as configparser from urllib import quote as urllib_parse_quote import time import subprocess if sys.version_info[0] >= 3: # Python >=3.0 from io import StringIO else: # Python <3.0 from cStringIO import StringIO import smtplib import re import tempfile # Minimal version of Subversion's bindings required _MIN_SVN_VERSION = [1, 5, 0] # Import the Subversion Python bindings, making sure they meet our # minimum version requirements. try: import svn.fs import svn.delta import svn.repos import svn.core except ImportError: sys.stderr.write( "You need version %s or better of the Subversion Python bindings.\n" \ % ".".join([str(x) for x in _MIN_SVN_VERSION])) sys.exit(1) if _MIN_SVN_VERSION > [svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR, svn.core.SVN_VER_PATCH]: sys.stderr.write( "You need version %s or better of the Subversion Python bindings.\n" \ % ".".join([str(x) for x in _MIN_SVN_VERSION])) sys.exit(1) SEPARATOR = '=' * 78 def main(pool, cmd, config_fname, repos_dir, cmd_args): ### TODO: Sanity check the incoming args if cmd == 'commit': revision = int(cmd_args[0]) repos = Repository(repos_dir, revision, pool) cfg = Config(config_fname, repos, {'author': repos.author, 'repos_basename': os.path.basename(repos.repos_dir) }) messenger = Commit(pool, cfg, repos) elif cmd == 'propchange' or cmd == 'propchange2': revision = int(cmd_args[0]) author = cmd_args[1] propname = cmd_args[2] action = (cmd == 'propchange2' and cmd_args[3] or 'A') repos = Repository(repos_dir, revision, pool) # Override the repos revision author with the author of the propchange repos.author = author cfg = Config(config_fname, repos, {'author': author, 'repos_basename': os.path.basename(repos.repos_dir) }) messenger = PropChange(pool, cfg, repos, author, propname, action) elif cmd == 'lock' or cmd == 'unlock': author = cmd_args[0] repos = Repository(repos_dir, 0, pool) ### any old revision will do # Override the repos revision author with the author of the lock/unlock repos.author = author cfg = Config(config_fname, repos, {'author': author, 'repos_basename': os.path.basename(repos.repos_dir) }) messenger = Lock(pool, cfg, repos, author, cmd == 'lock') else: raise UnknownSubcommand(cmd) messenger.generate() def remove_leading_slashes(path): while path and path[0] == '/': path = path[1:] return path class OutputBase: "Abstract base class to formalize the interface of output methods" def __init__(self, cfg, repos, prefix_param): self.cfg = cfg self.repos = repos self.prefix_param = prefix_param self._CHUNKSIZE = 128 * 1024 # This is a public member variable. This must be assigned a suitable # piece of descriptive text before make_subject() is called. self.subject = "" def make_subject(self, group, params): prefix = self.cfg.get(self.prefix_param, group, params) if prefix: subject = prefix + ' ' + self.subject else: subject = self.subject try: truncate_subject = int( self.cfg.get('truncate_subject', group, params)) except ValueError: truncate_subject = 0 if truncate_subject and len(subject) > truncate_subject: subject = subject[:(truncate_subject - 3)] + "..." return subject def start(self, group, params): """Override this method. Begin writing an output representation. GROUP is the name of the configuration file group which is causing this output to be produced. PARAMS is a dictionary of any named subexpressions of regular expressions defined in the configuration file, plus the key 'author' contains the author of the action being reported.""" raise NotImplementedError def finish(self): """Override this method. Flush any cached information and finish writing the output representation.""" raise NotImplementedError def write(self, output): """Override this method. Append the literal text string OUTPUT to the output representation.""" raise NotImplementedError def run(self, cmd): """Override this method, if the default implementation is not sufficient. Execute CMD, writing the stdout produced to the output representation.""" # By default we choose to incorporate child stderr into the output pipe_ob = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=sys.platform != "win32") buf = pipe_ob.stdout.read(self._CHUNKSIZE) while buf: self.write(buf) buf = pipe_ob.stdout.read(self._CHUNKSIZE) # wait on the child so we don't end up with a billion zombies pipe_ob.wait() class MailedOutput(OutputBase): def __init__(self, cfg, repos, prefix_param): OutputBase.__init__(self, cfg, repos, prefix_param) def start(self, group, params): # whitespace (or another character) separated list of addresses # which must be split into a clean list to_addr_in = self.cfg.get('to_addr', group, params) # if list of addresses starts with '[.]' # use the character between the square brackets as split char # else use whitespaces if len(to_addr_in) >= 3 and to_addr_in[0] == '[' \ and to_addr_in[2] == ']': self.to_addrs = \ [_f for _f in to_addr_in[3:].split(to_addr_in[1]) if _f] else: self.to_addrs = [_f for _f in to_addr_in.split() if _f] self.from_addr = self.cfg.get('from_addr', group, params) \ or self.repos.author or 'no_author' # if the from_addr (also) starts with '[.]' (may happen if one # map is used for both to_addr and from_addr) remove '[.]' if len(self.from_addr) >= 3 and self.from_addr[0] == '[' \ and self.from_addr[2] == ']': self.from_addr = self.from_addr[3:] self.reply_to = self.cfg.get('reply_to', group, params) # if the reply_to (also) starts with '[.]' (may happen if one # map is used for both to_addr and reply_to) remove '[.]' if len(self.reply_to) >= 3 and self.reply_to[0] == '[' \ and self.reply_to[2] == ']': self.reply_to = self.reply_to[3:] def mail_headers(self, group, params): from email import Utils subject = self.make_subject(group, params) try: subject.encode('ascii') except UnicodeError: from email.Header import Header subject = Header(subject, 'utf-8').encode() hdrs = 'From: %s\n' \ 'To: %s\n' \ 'Subject: %s\n' \ 'Date: %s\n' \ 'Message-ID: %s\n' \ 'MIME-Version: 1.0\n' \ 'Content-Type: text/plain; charset=UTF-8\n' \ 'Content-Transfer-Encoding: 8bit\n' \ 'X-Svn-Commit-Project: %s\n' \ 'X-Svn-Commit-Author: %s\n' \ 'X-Svn-Commit-Revision: %d\n' \ 'X-Svn-Commit-Repository: %s\n' \ % (self.from_addr, ', '.join(self.to_addrs), subject, Utils.formatdate(), Utils.make_msgid(), group, self.repos.author or 'no_author', self.repos.rev, os.path.basename(self.repos.repos_dir)) if self.reply_to: hdrs = '%sReply-To: %s\n' % (hdrs, self.reply_to) return hdrs + '\n' class SMTPOutput(MailedOutput): "Deliver a mail message to an MTA using SMTP." def start(self, group, params): MailedOutput.start(self, group, params) self.buffer = StringIO() self.write = self.buffer.write self.write(self.mail_headers(group, params)) def finish(self): server = smtplib.SMTP(self.cfg.general.smtp_hostname) if self.cfg.is_set('general.smtp_username'): server.login(self.cfg.general.smtp_username, self.cfg.general.smtp_password) server.sendmail(self.from_addr, self.to_addrs, self.buffer.getvalue()) server.quit() class StandardOutput(OutputBase): "Print the commit message to stdout." def __init__(self, cfg, repos, prefix_param): OutputBase.__init__(self, cfg, repos, prefix_param) self.write = sys.stdout.write def start(self, group, params): self.write("Group: " + (group or "defaults") + "\n") self.write("Subject: " + self.make_subject(group, params) + "\n\n") def finish(self): pass class PipeOutput(MailedOutput): "Deliver a mail message to an MTA via a pipe." def __init__(self, cfg, repos, prefix_param): MailedOutput.__init__(self, cfg, repos, prefix_param) # figure out the command for delivery self.cmd = cfg.general.mail_command.split() def start(self, group, params): MailedOutput.start(self, group, params) ### gotta fix this. this is pretty specific to sendmail and qmail's ### mailwrapper program. should be able to use option param substitution cmd = self.cmd + [ '-f', self.from_addr ] + self.to_addrs # construct the pipe for talking to the mailer self.pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE, close_fds=sys.platform != "win32") self.write = self.pipe.stdin.write # start writing out the mail message self.write(self.mail_headers(group, params)) def finish(self): # signal that we're done sending content self.pipe.stdin.close() # wait to avoid zombies self.pipe.wait() class Messenger: def __init__(self, pool, cfg, repos, prefix_param): self.pool = pool self.cfg = cfg self.repos = repos if cfg.is_set('general.mail_command'): cls = PipeOutput elif cfg.is_set('general.smtp_hostname'): cls = SMTPOutput else: cls = StandardOutput self.output = cls(cfg, repos, prefix_param) class Commit(Messenger): def __init__(self, pool, cfg, repos): Messenger.__init__(self, pool, cfg, repos, 'commit_subject_prefix') # get all the changes and sort by path editor = svn.repos.ChangeCollector(repos.fs_ptr, repos.root_this, \ self.pool) e_ptr, e_baton = svn.delta.make_editor(editor, self.pool) svn.repos.replay2(repos.root_this, "", svn.core.SVN_INVALID_REVNUM, 1, e_ptr, e_baton, None, self.pool) self.changelist = sorted(editor.get_changes().items()) log = repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or '' # collect the set of groups and the unique sets of params for the options self.groups = { } for path, change in self.changelist: for (group, params) in self.cfg.which_groups(path, log): # turn the params into a hashable object and stash it away param_list = sorted(params.items()) # collect the set of paths belonging to this group if (group, tuple(param_list)) in self.groups: old_param, paths = self.groups[group, tuple(param_list)] else: paths = { } paths[path] = None self.groups[group, tuple(param_list)] = (params, paths) # figure out the changed directories dirs = { } for path, change in self.changelist: if change.item_kind == svn.core.svn_node_dir: dirs[path] = None else: idx = path.rfind('/') if idx == -1: dirs[''] = None else: dirs[path[:idx]] = None dirlist = list(dirs.keys()) commondir, dirlist = get_commondir(dirlist) # compose the basic subject line. later, we can prefix it. dirlist.sort() dirlist = ' '.join(dirlist) if commondir: self.output.subject = 'r%d - in %s: %s' % (repos.rev, commondir, dirlist) else: self.output.subject = 'r%d - %s' % (repos.rev, dirlist) def generate(self): "Generate email for the various groups and option-params." ### the groups need to be further compressed. if the headers and ### body are the same across groups, then we can have multiple To: ### addresses. SMTPOutput holds the entire message body in memory, ### so if the body doesn't change, then it can be sent N times ### rather than rebuilding it each time. subpool = svn.core.svn_pool_create(self.pool) # build a renderer, tied to our output stream renderer = TextCommitRenderer(self.output) for (group, param_tuple), (params, paths) in self.groups.items(): self.output.start(group, params) # generate the content for this group and set of params generate_content(renderer, self.cfg, self.repos, self.changelist, group, params, paths, subpool) self.output.finish() svn.core.svn_pool_clear(subpool) svn.core.svn_pool_destroy(subpool) class PropChange(Messenger): def __init__(self, pool, cfg, repos, author, propname, action): Messenger.__init__(self, pool, cfg, repos, 'propchange_subject_prefix') self.author = author self.propname = propname self.action = action # collect the set of groups and the unique sets of params for the options self.groups = { } for (group, params) in self.cfg.which_groups('', None): # turn the params into a hashable object and stash it away param_list = sorted(params.items()) self.groups[group, tuple(param_list)] = params self.output.subject = 'r%d - %s' % (repos.rev, propname) def generate(self): actions = { 'A': 'added', 'M': 'modified', 'D': 'deleted' } for (group, param_tuple), params in self.groups.items(): self.output.start(group, params) self.output.write('Author: %s\n' 'Revision: %s\n' 'Property Name: %s\n' 'Action: %s\n' '\n' % (self.author, self.repos.rev, self.propname, actions.get(self.action, 'Unknown (\'%s\')' \ % self.action))) if self.action == 'A' or self.action not in actions: self.output.write('Property value:\n') propvalue = self.repos.get_rev_prop(self.propname) self.output.write(propvalue) elif self.action == 'M': self.output.write('Property diff:\n') tempfile1 = tempfile.NamedTemporaryFile() tempfile1.write(sys.stdin.read()) tempfile1.flush() tempfile2 = tempfile.NamedTemporaryFile() tempfile2.write(self.repos.get_rev_prop(self.propname)) tempfile2.flush() self.output.run(self.cfg.get_diff_cmd(group, { 'label_from' : 'old property value', 'label_to' : 'new property value', 'from' : tempfile1.name, 'to' : tempfile2.name, })) self.output.finish() def get_commondir(dirlist): """Figure out the common portion/parent (commondir) of all the paths in DIRLIST and return a tuple consisting of commondir, dirlist. If a commondir is found, the dirlist returned is rooted in that commondir. If no commondir is found, dirlist is returned unchanged, and commondir is the empty string.""" if len(dirlist) < 2 or '/' in dirlist: commondir = '' newdirs = dirlist else: common = dirlist[0].split('/') for j in range(1, len(dirlist)): d = dirlist[j] parts = d.split('/') for i in range(len(common)): if i == len(parts) or common[i] != parts[i]: del common[i:] break commondir = '/'.join(common) if commondir: # strip the common portion from each directory l = len(commondir) + 1 newdirs = [ ] for d in dirlist: if d == commondir: newdirs.append('.') else: newdirs.append(d[l:]) else: # nothing in common, so reset the list of directories newdirs = dirlist return commondir, newdirs class Lock(Messenger): def __init__(self, pool, cfg, repos, author, do_lock): self.author = author self.do_lock = do_lock Messenger.__init__(self, pool, cfg, repos, (do_lock and 'lock_subject_prefix' or 'unlock_subject_prefix')) # read all the locked paths from STDIN and strip off the trailing newlines self.dirlist = [x.rstrip() for x in sys.stdin.readlines()] # collect the set of groups and the unique sets of params for the options self.groups = { } for path in self.dirlist: for (group, params) in self.cfg.which_groups(path, None): # turn the params into a hashable object and stash it away param_list = sorted(params.items()) # collect the set of paths belonging to this group if (group, tuple(param_list)) in self.groups: old_param, paths = self.groups[group, tuple(param_list)] else: paths = { } paths[path] = None self.groups[group, tuple(param_list)] = (params, paths) commondir, dirlist = get_commondir(self.dirlist) # compose the basic subject line. later, we can prefix it. dirlist.sort() dirlist = ' '.join(dirlist) if commondir: self.output.subject = '%s: %s' % (commondir, dirlist) else: self.output.subject = '%s' % (dirlist) # The lock comment is the same for all paths, so we can just pull # the comment for the first path in the dirlist and cache it. self.lock = svn.fs.svn_fs_get_lock(self.repos.fs_ptr, self.dirlist[0], self.pool) def generate(self): for (group, param_tuple), (params, paths) in self.groups.items(): self.output.start(group, params) self.output.write('Author: %s\n' '%s paths:\n' % (self.author, self.do_lock and 'Locked' or 'Unlocked')) self.dirlist.sort() for dir in self.dirlist: self.output.write(' %s\n\n' % dir) if self.do_lock: self.output.write('Comment:\n%s\n' % (self.lock.comment or '')) self.output.finish() class DiffSelections: def __init__(self, cfg, group, params): self.add = False self.copy = False self.delete = False self.modify = False gen_diffs = cfg.get('generate_diffs', group, params) ### Do a little dance for deprecated options. Note that even if you ### don't have an option anywhere in your configuration file, it ### still gets returned as non-None. if len(gen_diffs): list = gen_diffs.split(" ") for item in list: if item == 'add': self.add = True if item == 'copy': self.copy = True if item == 'delete': self.delete = True if item == 'modify': self.modify = True else: self.add = True self.copy = True self.delete = True self.modify = True ### These options are deprecated suppress = cfg.get('suppress_deletes', group, params) if suppress == 'yes': self.delete = False suppress = cfg.get('suppress_adds', group, params) if suppress == 'yes': self.add = False class DiffURLSelections: def __init__(self, cfg, group, params): self.cfg = cfg self.group = group self.params = params def _get_url(self, action, repos_rev, change): # The parameters for the URLs generation need to be placed in the # parameters for the configuration module, otherwise we may get # KeyError exceptions. params = self.params.copy() params['path'] = change.path and urllib_parse_quote(change.path) or None params['base_path'] = change.base_path and urllib_parse_quote(change.base_path) \ or None params['rev'] = repos_rev params['base_rev'] = change.base_rev return self.cfg.get("diff_%s_url" % action, self.group, params) def get_add_url(self, repos_rev, change): return self._get_url('add', repos_rev, change) def get_copy_url(self, repos_rev, change): return self._get_url('copy', repos_rev, change) def get_delete_url(self, repos_rev, change): return self._get_url('delete', repos_rev, change) def get_modify_url(self, repos_rev, change): return self._get_url('modify', repos_rev, change) def generate_content(renderer, cfg, repos, changelist, group, params, paths, pool): svndate = repos.get_rev_prop(svn.core.SVN_PROP_REVISION_DATE) ### pick a different date format? date = time.ctime(svn.core.secs_from_timestr(svndate, pool)) diffsels = DiffSelections(cfg, group, params) diffurls = DiffURLSelections(cfg, group, params) show_nonmatching_paths = cfg.get('show_nonmatching_paths', group, params) \ or 'yes' params_with_rev = params.copy() params_with_rev['rev'] = repos.rev commit_url = cfg.get('commit_url', group, params_with_rev) # figure out the lists of changes outside the selected path-space other_added_data = other_replaced_data = other_deleted_data = \ other_modified_data = [ ] if len(paths) != len(changelist) and show_nonmatching_paths != 'no': other_added_data = generate_list('A', changelist, paths, False) other_replaced_data = generate_list('R', changelist, paths, False) other_deleted_data = generate_list('D', changelist, paths, False) other_modified_data = generate_list('M', changelist, paths, False) if len(paths) != len(changelist) and show_nonmatching_paths == 'yes': other_diffs = DiffGenerator(changelist, paths, False, cfg, repos, date, group, params, diffsels, diffurls, pool) else: other_diffs = None data = _data( author=repos.author, date=date, rev=repos.rev, log=repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or '', commit_url=commit_url, added_data=generate_list('A', changelist, paths, True), replaced_data=generate_list('R', changelist, paths, True), deleted_data=generate_list('D', changelist, paths, True), modified_data=generate_list('M', changelist, paths, True), show_nonmatching_paths=show_nonmatching_paths, other_added_data=other_added_data, other_replaced_data=other_replaced_data, other_deleted_data=other_deleted_data, other_modified_data=other_modified_data, diffs=DiffGenerator(changelist, paths, True, cfg, repos, date, group, params, diffsels, diffurls, pool), other_diffs=other_diffs, ) renderer.render(data) def generate_list(changekind, changelist, paths, in_paths): if changekind == 'A': selection = lambda change: change.action == svn.repos.CHANGE_ACTION_ADD elif changekind == 'R': selection = lambda change: change.action == svn.repos.CHANGE_ACTION_REPLACE elif changekind == 'D': selection = lambda change: change.action == svn.repos.CHANGE_ACTION_DELETE elif changekind == 'M': selection = lambda change: change.action == svn.repos.CHANGE_ACTION_MODIFY items = [ ] for path, change in changelist: if selection(change) and (path in paths) == in_paths: item = _data( path=path, is_dir=change.item_kind == svn.core.svn_node_dir, props_changed=change.prop_changes, text_changed=change.text_changed, copied=(change.action == svn.repos.CHANGE_ACTION_ADD \ or change.action == svn.repos.CHANGE_ACTION_REPLACE) \ and change.base_path, base_path=remove_leading_slashes(change.base_path), base_rev=change.base_rev, ) items.append(item) return items class DiffGenerator: "This is a generator-like object returning DiffContent objects." def __init__(self, changelist, paths, in_paths, cfg, repos, date, group, params, diffsels, diffurls, pool): self.changelist = changelist self.paths = paths self.in_paths = in_paths self.cfg = cfg self.repos = repos self.date = date self.group = group self.params = params self.diffsels = diffsels self.diffurls = diffurls self.pool = pool self.diff = self.diff_url = None self.idx = 0 def __nonzero__(self): # we always have some items return True def __getitem__(self, idx): while True: if self.idx == len(self.changelist): raise IndexError path, change = self.changelist[self.idx] self.idx = self.idx + 1 diff = diff_url = None kind = None label1 = None label2 = None src_fname = None dst_fname = None binary = None singular = None content = None # just skip directories. they have no diffs. if change.item_kind == svn.core.svn_node_dir: continue # is this change in (or out of) the set of matched paths? if (path in self.paths) != self.in_paths: continue if change.base_rev != -1: svndate = self.repos.get_rev_prop(svn.core.SVN_PROP_REVISION_DATE, change.base_rev) ### pick a different date format? base_date = time.ctime(svn.core.secs_from_timestr(svndate, self.pool)) else: base_date = '' # figure out if/how to generate a diff base_path = remove_leading_slashes(change.base_path) if change.action == svn.repos.CHANGE_ACTION_DELETE: # it was delete. kind = 'D' # get the diff url, if any is specified diff_url = self.diffurls.get_delete_url(self.repos.rev, change) # show the diff? if self.diffsels.delete: diff = svn.fs.FileDiff(self.repos.get_root(change.base_rev), base_path, None, None, self.pool) label1 = '%s\t%s\t(r%s)' % (base_path, self.date, change.base_rev) label2 = '/dev/null\t00:00:00 1970\t(deleted)' singular = True elif change.action == svn.repos.CHANGE_ACTION_ADD \ or change.action == svn.repos.CHANGE_ACTION_REPLACE: if base_path and (change.base_rev != -1): # any diff of interest? if change.text_changed: # this file was copied and modified. kind = 'W' # get the diff url, if any is specified diff_url = self.diffurls.get_copy_url(self.repos.rev, change) # show the diff? if self.diffsels.modify: diff = svn.fs.FileDiff(self.repos.get_root(change.base_rev), base_path, self.repos.root_this, change.path, self.pool) label1 = '%s\t%s\t(r%s, copy source)' \ % (base_path, base_date, change.base_rev) label2 = '%s\t%s\t(r%s)' \ % (change.path, self.date, self.repos.rev) singular = False else: # this file was copied. kind = 'C' if self.diffsels.copy: diff = svn.fs.FileDiff(None, None, self.repos.root_this, change.path, self.pool) label1 = '/dev/null\t00:00:00 1970\t' \ '(empty, because file is newly added)' label2 = '%s\t%s\t(r%s, copy of r%s, %s)' \ % (change.path, self.date, self.repos.rev, \ change.base_rev, base_path) singular = False else: # the file was added. kind = 'A' # get the diff url, if any is specified diff_url = self.diffurls.get_add_url(self.repos.rev, change) # show the diff? if self.diffsels.add: diff = svn.fs.FileDiff(None, None, self.repos.root_this, change.path, self.pool) label1 = '/dev/null\t00:00:00 1970\t' \ '(empty, because file is newly added)' label2 = '%s\t%s\t(r%s)' \ % (change.path, self.date, self.repos.rev) singular = True elif not change.text_changed: # the text didn't change, so nothing to show. continue else: # a simple modification. kind = 'M' # get the diff url, if any is specified diff_url = self.diffurls.get_modify_url(self.repos.rev, change) # show the diff? if self.diffsels.modify: diff = svn.fs.FileDiff(self.repos.get_root(change.base_rev), base_path, self.repos.root_this, change.path, self.pool) label1 = '%s\t%s\t(r%s)' \ % (base_path, base_date, change.base_rev) label2 = '%s\t%s\t(r%s)' \ % (change.path, self.date, self.repos.rev) singular = False if diff: binary = diff.either_binary() if binary: content = src_fname = dst_fname = None else: src_fname, dst_fname = diff.get_files() try: content = DiffContent(self.cfg.get_diff_cmd(self.group, { 'label_from' : label1, 'label_to' : label2, 'from' : src_fname, 'to' : dst_fname, })) except OSError: # diff command does not exist, try difflib.unified_diff() content = DifflibDiffContent(label1, label2, src_fname, dst_fname) # return a data item for this diff return _data( path=change.path, base_path=base_path, base_rev=change.base_rev, diff=diff, diff_url=diff_url, kind=kind, label_from=label1, label_to=label2, from_fname=src_fname, to_fname=dst_fname, binary=binary, singular=singular, content=content, ) def _classify_diff_line(line, seen_change): # classify the type of line. first = line[:1] ltype = '' if first == '@': seen_change = True ltype = 'H' elif first == '-': if seen_change: ltype = 'D' else: ltype = 'F' elif first == '+': if seen_change: ltype = 'A' else: ltype = 'T' elif first == ' ': ltype = 'C' else: ltype = 'U' if line[-2] == '\r': line=line[0:-2] + '\n' # remove carriage return return line, ltype, seen_change class DiffContent: "This is a generator-like object returning annotated lines of a diff." def __init__(self, cmd): self.seen_change = False # By default we choose to incorporate child stderr into the output self.pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=sys.platform != "win32") def __nonzero__(self): # we always have some items return True def __getitem__(self, idx): if self.pipe is None: raise IndexError line = self.pipe.stdout.readline() if not line: # wait on the child so we don't end up with a billion zombies self.pipe.wait() self.pipe = None raise IndexError line, ltype, self.seen_change = _classify_diff_line(line, self.seen_change) return _data( raw=line, text=line[1:-1], # remove indicator and newline type=ltype, ) class DifflibDiffContent(): "This is a generator-like object returning annotated lines of a diff." def __init__(self, label_from, label_to, from_file, to_file): import difflib self.seen_change = False fromlines = open(from_file, 'U').readlines() tolines = open(to_file, 'U').readlines() self.diff = difflib.unified_diff(fromlines, tolines, label_from, label_to) def __nonzero__(self): # we always have some items return True def __getitem__(self, idx): try: line = self.diff.next() except StopIteration: raise IndexError line, ltype, self.seen_change = _classify_diff_line(line, self.seen_change) return _data( raw=line, text=line[1:-1], # remove indicator and newline type=ltype, ) class TextCommitRenderer: "This class will render the commit mail in plain text." def __init__(self, output): self.output = output def render(self, data): "Render the commit defined by 'data'." w = self.output.write w('Author: %s\nDate: %s\nNew Revision: %s\n' % (data.author, data.date, data.rev)) if data.commit_url: w('URL: %s\n\n' % data.commit_url) else: w('\n') w('Log:\n%s\n\n' % data.log.strip()) # print summary sections self._render_list('Added', data.added_data) self._render_list('Replaced', data.replaced_data) self._render_list('Deleted', data.deleted_data) self._render_list('Modified', data.modified_data) if data.other_added_data or data.other_replaced_data \ or data.other_deleted_data or data.other_modified_data: if data.show_nonmatching_paths: w('\nChanges in other areas also in this revision:\n') self._render_list('Added', data.other_added_data) self._render_list('Replaced', data.other_replaced_data) self._render_list('Deleted', data.other_deleted_data) self._render_list('Modified', data.other_modified_data) else: w('and changes in other areas\n') self._render_diffs(data.diffs, '') if data.other_diffs: self._render_diffs(data.other_diffs, '\nDiffs of changes in other areas also' ' in this revision:\n') def _render_list(self, header, data_list): if not data_list: return w = self.output.write w(header + ':\n') for d in data_list: if d.is_dir: is_dir = '/' else: is_dir = '' if d.props_changed: if d.text_changed: props = ' (contents, props changed)' else: props = ' (props changed)' else: props = '' w(' %s%s%s\n' % (d.path, is_dir, props)) if d.copied: if is_dir: text = '' elif d.text_changed: text = ', changed' else: text = ' unchanged' w(' - copied%s from r%d, %s%s\n' % (text, d.base_rev, d.base_path, is_dir)) def _render_diffs(self, diffs, section_header): """Render diffs. Write the SECTION_HEADER if there are actually any diffs to render.""" if not diffs: return w = self.output.write section_header_printed = False for diff in diffs: if not diff.diff and not diff.diff_url: continue if not section_header_printed: w(section_header) section_header_printed = True if diff.kind == 'D': w('\nDeleted: %s\n' % diff.base_path) elif diff.kind == 'A': w('\nAdded: %s\n' % diff.path) elif diff.kind == 'C': w('\nCopied: %s (from r%d, %s)\n' % (diff.path, diff.base_rev, diff.base_path)) elif diff.kind == 'W': w('\nCopied and modified: %s (from r%d, %s)\n' % (diff.path, diff.base_rev, diff.base_path)) else: # kind == 'M' w('\nModified: %s\n' % diff.path) if diff.diff_url: w('URL: %s\n' % diff.diff_url) if not diff.diff: continue w(SEPARATOR + '\n') if diff.binary: if diff.singular: w('Binary file. No diff available.\n') else: w('Binary file (source and/or target). No diff available.\n') continue for line in diff.content: w(line.raw) class Repository: "Hold roots and other information about the repository." def __init__(self, repos_dir, rev, pool): self.repos_dir = repos_dir self.rev = rev self.pool = pool self.repos_ptr = svn.repos.open(repos_dir, pool) self.fs_ptr = svn.repos.fs(self.repos_ptr) self.roots = { } self.root_this = self.get_root(rev) self.author = self.get_rev_prop(svn.core.SVN_PROP_REVISION_AUTHOR) def get_rev_prop(self, propname, rev = None): if not rev: rev = self.rev return svn.fs.revision_prop(self.fs_ptr, rev, propname, self.pool) def get_root(self, rev): try: return self.roots[rev] except KeyError: pass root = self.roots[rev] = svn.fs.revision_root(self.fs_ptr, rev, self.pool) return root class Config: # The predefined configuration sections. These are omitted from the # set of groups. _predefined = ('general', 'defaults', 'maps') def __init__(self, fname, repos, global_params): cp = configparser.ConfigParser() cp.read(fname) # record the (non-default) groups that we find self._groups = [ ] for section in cp.sections(): if not hasattr(self, section): section_ob = _sub_section() setattr(self, section, section_ob) if section not in self._predefined: self._groups.append(section) else: section_ob = getattr(self, section) for option in cp.options(section): # get the raw value -- we use the same format for *our* interpolation value = cp.get(section, option, raw=1) setattr(section_ob, option, value) # be compatible with old format config files if hasattr(self.general, 'diff') and not hasattr(self.defaults, 'diff'): self.defaults.diff = self.general.diff if not hasattr(self, 'maps'): self.maps = _sub_section() # these params are always available, although they may be overridden self._global_params = global_params.copy() # prepare maps. this may remove sections from consideration as a group. self._prep_maps() # process all the group sections. self._prep_groups(repos) def is_set(self, option): """Return None if the option is not set; otherwise, its value is returned. The option is specified as a dotted symbol, such as 'general.mail_command' """ ob = self for part in option.split('.'): if not hasattr(ob, part): return None ob = getattr(ob, part) return ob def get(self, option, group, params): "Get a config value with appropriate substitutions and value mapping." # find the right value value = None if group: sub = getattr(self, group) value = getattr(sub, option, None) if value is None: value = getattr(self.defaults, option, '') # parameterize it if params is not None: value = value % params # apply any mapper mapper = getattr(self.maps, option, None) if mapper is not None: value = mapper(value) # Apply any parameters that may now be available for # substitution that were not before the mapping. if value is not None and params is not None: value = value % params return value def get_diff_cmd(self, group, args): "Get a diff command as a list of argv elements." ### do some better splitting to enable quoting of spaces diff_cmd = self.get('diff', group, None).split() cmd = [ ] for part in diff_cmd: cmd.append(part % args) return cmd def _prep_maps(self): "Rewrite the [maps] options into callables that look up values." mapsections = [] for optname, mapvalue in vars(self.maps).items(): if mapvalue[:1] == '[': # a section is acting as a mapping sectname = mapvalue[1:-1] if not hasattr(self, sectname): raise UnknownMappingSection(sectname) # construct a lambda to look up the given value as an option name, # and return the option's value. if the option is not present, # then just return the value unchanged. setattr(self.maps, optname, lambda value, sect=getattr(self, sectname): getattr(sect, value.lower(), value)) # mark for removal when all optnames are done if sectname not in mapsections: mapsections.append(sectname) # elif test for other mapper types. possible examples: # dbm:filename.db # file:two-column-file.txt # ldap:some-query-spec # just craft a mapper function and insert it appropriately else: raise UnknownMappingSpec(mapvalue) # remove each mapping section from consideration as a group for sectname in mapsections: self._groups.remove(sectname) def _prep_groups(self, repos): self._group_re = [ ] repos_dir = os.path.abspath(repos.repos_dir) # compute the default repository-based parameters. start with some # basic parameters, then bring in the regex-based params. self._default_params = self._global_params try: match = re.match(self.defaults.for_repos, repos_dir) if match: self._default_params = self._default_params.copy() self._default_params.update(match.groupdict()) except AttributeError: # there is no self.defaults.for_repos pass # select the groups that apply to this repository for group in self._groups: sub = getattr(self, group) params = self._default_params if hasattr(sub, 'for_repos'): match = re.match(sub.for_repos, repos_dir) if not match: continue params = params.copy() params.update(match.groupdict()) # if a matching rule hasn't been given, then use the empty string # as it will match all paths for_paths = getattr(sub, 'for_paths', '') exclude_paths = getattr(sub, 'exclude_paths', None) if exclude_paths: exclude_paths_re = re.compile(exclude_paths) else: exclude_paths_re = None # check search_logmsg re search_logmsg = getattr(sub, 'search_logmsg', None) if search_logmsg is not None: search_logmsg_re = re.compile(search_logmsg) else: search_logmsg_re = None self._group_re.append((group, re.compile(for_paths), exclude_paths_re, params, search_logmsg_re)) # after all the groups are done, add in the default group try: self._group_re.append((None, re.compile(self.defaults.for_paths), None, self._default_params, None)) except AttributeError: # there is no self.defaults.for_paths pass def which_groups(self, path, logmsg): "Return the path's associated groups." groups = [] for group, pattern, exclude_pattern, repos_params, search_logmsg_re in self._group_re: match = pattern.match(path) if match: if exclude_pattern and exclude_pattern.match(path): continue params = repos_params.copy() params.update(match.groupdict()) if search_logmsg_re is None: groups.append((group, params)) else: if logmsg is None: logmsg = '' for match in search_logmsg_re.finditer(logmsg): # Add captured variables to (a copy of) params msg_params = params.copy() msg_params.update(match.groupdict()) groups.append((group, msg_params)) if not groups: groups.append((None, self._default_params)) return groups class _sub_section: pass class _data: "Helper class to define an attribute-based hunk o' data." def __init__(self, **kw): vars(self).update(kw) class MissingConfig(Exception): pass class UnknownMappingSection(Exception): pass class UnknownMappingSpec(Exception): pass class UnknownSubcommand(Exception): pass if __name__ == '__main__': def usage(): scriptname = os.path.basename(sys.argv[0]) sys.stderr.write( """USAGE: %s commit REPOS REVISION [CONFIG-FILE] %s propchange REPOS REVISION AUTHOR REVPROPNAME [CONFIG-FILE] %s propchange2 REPOS REVISION AUTHOR REVPROPNAME ACTION [CONFIG-FILE] %s lock REPOS AUTHOR [CONFIG-FILE] %s unlock REPOS AUTHOR [CONFIG-FILE] If no CONFIG-FILE is provided, the script will first search for a mailer.conf file in REPOS/conf/. Failing that, it will search the directory in which the script itself resides. ACTION was added as a fifth argument to the post-revprop-change hook in Subversion 1.2.0. Its value is one of 'A', 'M' or 'D' to indicate if the property was added, modified or deleted, respectively. """ % (scriptname, scriptname, scriptname, scriptname, scriptname)) sys.exit(1) # Command list: subcommand -> number of arguments expected (not including # the repository directory and config-file) cmd_list = {'commit' : 1, 'propchange' : 3, 'propchange2': 4, 'lock' : 1, 'unlock' : 1, } config_fname = None argc = len(sys.argv) if argc < 3: usage() cmd = sys.argv[1] repos_dir = svn.core.svn_path_canonicalize(sys.argv[2]) try: expected_args = cmd_list[cmd] except KeyError: usage() if argc < (expected_args + 3): usage() elif argc > expected_args + 4: usage() elif argc == (expected_args + 4): config_fname = sys.argv[expected_args + 3] # Settle on a config file location, and open it. if config_fname is None: # Default to REPOS-DIR/conf/mailer.conf. config_fname = os.path.join(repos_dir, 'conf', 'mailer.conf') if not os.path.exists(config_fname): # Okay. Look for 'mailer.conf' as a sibling of this script. config_fname = os.path.join(os.path.dirname(sys.argv[0]), 'mailer.conf') if not os.path.exists(config_fname): raise MissingConfig(config_fname) svn.core.run_app(main, cmd, config_fname, repos_dir, sys.argv[3:3+expected_args]) # ------------------------------------------------------------------------ # TODO # # * add configuration options # - each group defines delivery info: # o whether to set Reply-To and/or Mail-Followup-To # (btw: it is legal do set Reply-To since this is the originator of the # mail; i.e. different from MLMs that munge it) # - each group defines content construction: # o max size of diff before trimming # o max size of entire commit message before truncation # - per-repository configuration # o extra config living in repos # o optional, non-mail log file # o look up authors (username -> email; for the From: header) in a # file(s) or DBM # * get rid of global functions that should properly be class methods |
$ tree . ├── README.txt ├── conf │ ├── authz │ ├── hooks-env.tmpl │ ├── passwd │ └── svnserve.conf ├── db │ ├── current │ ├── format │ ├── fs-type │ ├── fsfs.conf │ ├── min-unpacked-rev │ ├── rep-cache.db │ ├── revprops │ │ └── 0 │ │ ├── 0 │ │ └── 1 │ ├── revs │ │ └── 0 │ │ ├── 0 │ │ └── 1 │ ├── transactions │ ├── txn-current │ ├── txn-current-lock │ ├── txn-protorevs │ ├── uuid │ └── write-lock ├── format ├── hooks │ ├── mailer.py │ ├── post-commit │ ├── post-commit.tmpl │ ├── post-lock.tmpl │ ├── post-revprop-change.tmpl │ ├── post-unlock.tmpl │ ├── pre-commit.tmpl │ ├── pre-lock.tmpl │ ├── pre-revprop-change.tmpl │ ├── pre-unlock.tmpl │ └── start-commit.tmpl ├── locks │ ├── db-logs.lock │ └── db.lock └── mailer.conf 10 directories, 34 files |
커밋하는데.. 먼가 걸렸나 드럽게 진행이 안되네
$ svn ci 추가 t2.c 파일 데이터 전송중 . |
일단 아마도?
$ ps -ef | grep py pi 22559 22558 0 14:37 pts/0 00:00:00 /usr/bin/python /home/pi/repos/hooks/mailer.py commit /home/pi/repos 2 /home/pi/repos/mailer.conf |
pstree 해보니 다음과 같이 구동이 되나본데..
├─sshd─┬─sshd───sshd───bash───svn───post-commit───mailer.py |
회사 메일이 SSL 쓰는 바람에 보안관련 문제인걸려나?
경고: 'post-commit' 훅이 실패했습니다 (분명하게 빠져나가지 않았습니다: apr_exit_why_e 는 2, 종료코드는 2) 출력: Traceback (most recent call last): File "/home/pi/repos/hooks/mailer.py", line 1448, in <module> sys.argv[3:3+expected_args]) File "/usr/lib/python2.7/dist-packages/svn/core.py", line 345, in run_app return func(application_pool, *args, **kw) File "/home/pi/repos/hooks/mailer.py", line 132, in main messenger.generate() File "/home/pi/repos/hooks/mailer.py", line 424, in generate self.output.finish() File "/home/pi/repos/hooks/mailer.py", line 280, in finish server = smtplib.SMTP(self.cfg.general.smtp_hostname) File "/usr/lib/python2.7/smtplib.py", line 256, in __init__ (code, msg) = self.connect(host, port) File "/usr/lib/python2.7/smtplib.py", line 316, in connect self.sock = self._get_socket(host, port, self.timeout) File "/usr/lib/python2.7/smtplib.py", line 291, in _get_socket return socket.create_connection((host, port), timeout) File "/usr/lib/python2.7/socket.py", line 562, in create_connection sock.connect(sa) File "/usr/lib/python2.7/socket.py", line 224, in meth return getattr(self._sock,name)(*args) KeyboardInterrupt svn: E200000: 커밋이 성공하였지만, 오류가 있습니다: svn: E200015: 커밋 후 리비전들을 갱신하는 도중 오류가 발생하였습니다: svn: E200015: 시그널 수신 svn: E200000: 커밋 메시지는 다음 파일에 저장되어 있으며, -F로 재사용 할 수 있습니다. : svn: E200000: '/home/pi/test/svn-commit.tmp' |
[링크 : http://sunnyan.tistory.com/m/4840]
음.. 일단 tls 어쩌구 넣고 하는데도 안되네 머가 문제일려나..
경고: post-commit 훅이 실패했습니다 ( 종료코드 1) 출력: Traceback (most recent call last): File "/home/pi/repos/hooks/mailer.py", line 1452, in <module> sys.argv[3:3+expected_args]) File "/usr/lib/python2.7/dist-packages/svn/core.py", line 345, in run_app return func(application_pool, *args, **kw) File "/home/pi/repos/hooks/mailer.py", line 132, in main messenger.generate() File "/home/pi/repos/hooks/mailer.py", line 428, in generate self.output.finish() File "/home/pi/repos/hooks/mailer.py", line 280, in finish server = smtplib.SMTP(self.cfg.general.smtp_hostname) File "/usr/lib/python2.7/smtplib.py", line 256, in __init__ (code, msg) = self.connect(host, port) File "/usr/lib/python2.7/smtplib.py", line 317, in connect (code, msg) = self.getreply() File "/usr/lib/python2.7/smtplib.py", line 368, in getreply raise SMTPServerDisconnected("Connection unexpectedly closed") smtplib.SMTPServerDisconnected: Connection unexpectedly closed |
[링크 : http://stackoverflow.com/questions/6980631/svn-notifications-via-gmail-smtp]
[링크 : http://sadomovalex.blogspot.com/2009/12/use-gmail-smtp-server-for-post-commit.html]
[링크 : http://pyrasis.com/blog/entry/SubversionMailerPyScriptForGmailSMTP] TLS 방식일 경우
+
[링크 : https://docs.python.org/3.5/library/smtplib.html]
[링크 : https://docs.python.org/3.5/library/smtplib.html#smtplib.SMTP.ehlo]
+
telnet 으로 helo domain.com 해보니 바로 끊어 버리네 머지???
망할 mailplug인가? (구글은 문제 없는데?!)
[링크 : http://jang8584.tistory.com/52]
그리고.. python으로 시도하면 접속도 안된다. 머지?
>>> server = smtplib.SMTP("smtp.mailplug.co.kr:465") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.7/smtplib.py", line 256, in __init__ (code, msg) = self.connect(host, port) File "/usr/lib/python2.7/smtplib.py", line 317, in connect (code, msg) = self.getreply() File "/usr/lib/python2.7/smtplib.py", line 368, in getreply raise SMTPServerDisconnected("Connection unexpectedly closed") smtplib.SMTPServerDisconnected: Connection unexpectedly closed |
+
wireshark로 보니까 그냥 패킷이네..
함수를 바꾸자 -_-
s = smtplib.SMTP_SSL('host:port')
[링크 : http://stackoverflow.com/questions/24672079/send-email-using-smtp-ssl-port-465]
mailer.py 수정
def finish(self): if self.cfg.is_set('general.smtp_use_ssl') and self.cfg.general.smtp_use_ssl.lower() == "true": server = smtplib.SMTP_SSL(self.cfg.general.smtp_hostname) else: server = smtplib.SMTP(self.cfg.general.smtp_hostname) if self.cfg.is_set('general.smtp_username'): server.login(self.cfg.general.smtp_username, self.cfg.general.smtp_password) server.sendmail(self.from_addr, self.to_addrs, self.buffer.getvalue()) server.quit() |
시놀로지에 하니까 안되네.. 췌 ㅠㅠ
+
mailer.py에 이런게 있는데.. 라이브러리가 존재하지 않네.. ㅠㅠ
# Import the Subversion Python bindings, making sure they meet our # minimum version requirements. try: import svn.fs import svn.delta import svn.repos import svn.core except ImportError: sys.stderr.write( "You need version %s or better of the Subversion Python bindings.\n" \ % ".".join([str(x) for x in _MIN_SVN_VERSION])) sys.exit(1) if _MIN_SVN_VERSION > [svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR, svn.core.SVN_VER_PATCH]: sys.stderr.write( "You need version %s or better of the Subversion Python bindings.\n" \ % ".".join([str(x) for x in _MIN_SVN_VERSION])) sys.exit(1) |
[링크 : http://stack.../you-need-version-1-5-0-or-better-of-the-subversion-python-bindings-while-using]
+
synology에서는 걍 포기하면 편해..
libsvn 부터 온갖 so들을 자꾸 요구해서 짜증..
라즈베리에서 복사하는것도 한두개지 어우 ㅠㅠ
| svn commit email - python / synology (0) | 2016.12.30 |
|---|---|
| svn diff 결과물 컬러로 보기 (0) | 2016.12.30 |
| svn console에서 엔터 입력하기 (0) | 2016.11.08 |
| svn add를 취소하기 (0) | 2016.11.04 |
| synology svn+ssh 퍼미션 문제 (0) | 2016.10.09 |
ubuntu 12.04 LTS / svn 1.6.17
테스트 완료
$ svn ci -m $'This is the first line\nThis is the second line' |
[링크 : http://serverfault.com/.../use-linefeed-or-carriage-return-in-...-message-from-the-command-li]
| svn diff 결과물 컬러로 보기 (0) | 2016.12.30 |
|---|---|
| svn commit시 email 알림 (0) | 2016.12.29 |
| svn add를 취소하기 (0) | 2016.11.04 |
| synology svn+ssh 퍼미션 문제 (0) | 2016.10.09 |
| svn://과 svn+ssh:// 경로 차이 (0) | 2016.09.13 |
으아 복잡해 -_-!!!
결론 : svn에서 복구 하려면 복사본 만들고 삭제해라!
cp -a ori back svn delete –force ori svn revert ori cp -a back ori |
[링크 : http://k44.kr/?p=2908]
| svn commit시 email 알림 (0) | 2016.12.29 |
|---|---|
| svn console에서 엔터 입력하기 (0) | 2016.11.08 |
| synology svn+ssh 퍼미션 문제 (0) | 2016.10.09 |
| svn://과 svn+ssh:// 경로 차이 (0) | 2016.09.13 |
| tortoiseSVN에서 svn+ssh 사용하기 (0) | 2016.07.31 |