0
0

在Git中避免commit你的密码

vvoody 发表于 2012年01月14日 03:50 | Hits: 2737

平时写写小项目时通常有这么一个配置文件,如config.php:

... ...
$db['default']['username'] = 'yyy';
$db['default']['password'] = 'ZXCVASDFQWER';
$db['default']['database'] = 'xxoo_db';
... ...
在本地测试时会填上用户名密码之类的信息,不过代码改着改着就容易忘记把这些变量清空,然后commit && push... 已经不只一次在提交代码并push出去后发现里面包含本地测试用的密码等敏感信息,于是决定这次不能忽视之。另外我也希望除了commit时能把这些敏感信息清空外,完了还能方便地把这些变量重新设回来,做到本地开发测试用的配置和项目默认配置分离。

自己没啥头绪,上网搜了一圈后在神站Stack Overflow找到一个类似的问题[0],不过回答里提及的方案我不是很明白,而且隐约觉得有些笨重,特别对于一般的小项目而言。于是上Twitter请教。期间突然想到git的hooks,后来@CSSlayer大牛也提示我用hook check一下[2]。在搜索用git hooks解决我这类问题时又在Stack Overflow找到一篇问答[3],正是这篇文章让我找到了gitarrtibutes这个东西,能比git hooks更好的解决我的问题。


对gitattributes不太了解的可以先看这篇文章Git Attributes[4],特别是Keyword Expansion一节,正是用到了filter。让我们来

  1. 首先建一个git repo,就一个文件config.php,内容为:
    $db['default']['username'] = '';
    $db['default']['password'] = '';
    $db['default']['database'] = '';
    commit之:
    git init
    git add .
    git commit -m 'init commit'

  2. 建立.gitattributes文件,并且把filter相应的smudge和clean行为加到git本地配置中,最后commit:
    echo "config.php filter=noleak" > .gitattributes
    git add .gitattributes
    git commit -m "Testing gitattributes"
    git config filter.noleak.smudge 'config_vars set'
    git config filter.noleak.clean 'config_vars unset'
    git config命令最后的单引号内的可以是单独的命令,比如用perl/sed来过滤,也可以是随便什么脚本,并且可以跟参数。不想.gitattributes放入项目中的话,可以放到.git/info/attributes。

  3. 撰写config_vars脚本:
    #!/bin/sh
    
    case "$1" in
        set)
            sed -e "s/\$db\['default'\]\['username'\] = '';/\$db\['default'\]\['username'\] = 'IAM';/" \
                -e "s/\$db\['default'\]\['password'\] = '';/\$db\['default'\]\['password'\] = 'SHER';/" \
                -e "s/\$db\['default'\]\['database'\] = '';/\$db\['default'\]\['database'\] = 'LOCKED';/" \
                < /dev/stdin
            ;;
        unset)
            sed "s/'[^']*';$/'';/" < /dev/stdin
            ;;
        *)
            echo "Usage: cat somefile | $0 set|unset"
    esac
    set部分应该有更好的写法。。。把它chmod +x并且放到PATH中。奇怪的是放到有在PATH中设置了的~/bin后,git却报如下错误,不知为何:
    error: cannot fork to run external filter config_vars

  4. 好,来看一下效果:
    rm config.php
    git checkout config.php
    cat config.php
    $db['default']['username'] = 'IAM';
    $db['default']['password'] = 'SHER';
    $db['default']['database'] = 'LOCKED';
    变量已经被赋值,然后看看git是怎么看这回事的:
    git status
    # On branch master
    nothing to commit (working directory clean)
    此时如果添加或修改别的文件并提交后,日志显示config.php这个文件和之前没有变化,git在commit时自动运行了filter.noleak.clean也就是"config_var unset"脚本,把这个文件设回原来的样子,hash的结果没有变化。push到origin,config.php里的变量值还是空的。

  5. 如果修改config.php这个文件本身:
    echo "\$db['default']['driver'] = '';" >> config.php
    git status
    # On branch master
    # Changes not staged for commit:
    #   (use "git add <file>..." to update what will be committed)
    #   (use "git checkout -- <file>..." to discard changes in working directory)
    #
    #       modified:   config.php
    #
    no changes added to commit (use "git add" and/or "git commit -a")
    git diff的结果呢:
    diff --git a/config.php b/config.php
    index 8ba4fa3..fe2ae59 100644
    --- a/config.php
    +++ b/config.php
    @@ -1,3 +1,4 @@
     $db['default']['username'] = '';
     $db['default']['password'] = '';
     $db['default']['database'] = '';
    +$db['default']['driver'] = '';
    commit后如果让config.php中的4个变量都被赋值,得先在config_vars脚本的set中增加'driver'的条目,然后:
    cat config.php
    $db['default']['username'] = 'IAM';
    $db['default']['password'] = 'SHER';
    $db['default']['database'] = 'LOCKED';
    $db['default']['driver'] = '';
    rm config.php
    git co config.php
    cat config.php
    $db['default']['username'] = 'IAM';
    $db['default']['password'] = 'SHER';
    $db['default']['database'] = 'LOCKED';
    $db['default']['driver'] = 'mysql';
    如是,git status依然不认为文件有变化。另外,如果手动修改这些变量的值,虽然git status会判定为modified了,但是git diff [--staged]或者git commit都不认为这个文件有变化。
    sed -i 's/SHER/xxxxx/'
    cat config.php
    $db['default']['username'] = 'IAM';
    $db['default']['password'] = 'xxxxx';
    $db['default']['database'] = 'LOCKED';
    $db['default']['driver'] = 'mysql';
    git st
    # On branch master
    # Changes not staged for commit:
    #   (use "git add <file>..." to update what will be committed)
    #   (use "git checkout -- <file>..." to discard changes in working directory)
    #
    #       modified:   config.php
    #
    no changes added to commit (use "git add" and/or "git commit -a")
    git diff
    diff --git a/config.php b/config.php
    (END)
    $ git diff --staged
    (END)
    git commit -a -m "xxxxx"
    # On branch master
    nothing to commit (working directory clean)

目的基本达成,本地的开发配置信息被隔离开,不会进入项目代码树。@CSSlayer大牛说的git hooks也能达到,分别写.git/hooks/{post-checkout,pre-commit}两个脚本,前者干'config_vars set'的活,后者干'config_vars unset',但是在未commit前git会一直认为文件有变化,不像gitattributes filter那么省心。man[5]中例子也是类似的替换应用。

又,有位朋友[8]提到的方法也是可行,只是要额外增加一个文件,并且要修改被跟踪源文件。写到这儿,发现其实这主要是用来分离本地开发环境配置和源码,提交代码时清空各种本地测试时的配置,比如申请的google map key、encryption_key等等,自己在本地测试时自动设置变量配置。不过,话说回来了,对config_vars之类的脚本的管理也是个问题。。。最后,求更简洁方便的方法 XD

UPDATE 2012-01-14:
像wordpress或者git hooks那样建立config.php.example或pre-commit.example形式的配置文件比较好,它们纳入版本管理,而自己测试或者部署的时候建个config.php,这个文件则不纳入版本管理。
本文所述的方法相比之下就复杂多了,也不好维护(config_vars之类的脚本以及git config filter.xxx配置),大家权当是一个如何使用gitattributes filter的例子吧 =,=

参考:
  1. How do you avoid storing passwords in version control?
  2. @vvoody 加个hook check之,记得在哪看过
  3. Can a git hook be used to replace passwords before staging, adding, or pushing?
  4. Git Attributes(中文版)
  5. gitattributes(5)
  6. Git Hooks
  7. githooks(5)
  8. 可以写两个配置文件吧

原文链接: 2012/01/13/%E5%9C%A8Git%E4%B8%AD%E9%81%BF%E5%85%8Dcommit%E4%BD%A0%E7%9A%84%E5%AF%86%E7%A0%81

0     0

我要给这篇文章打分:

可以不填写评论, 而只是打分. 如果发表评论, 你可以给的分值是-5到+5, 否则, 你只能评-1, +1两种分数. 你的评论可能需要审核.

评价列表(1)

  • +0 Pillar voted at 2013-03-08 17:15:26

    不错