之前一直没有研究过音频的相关技术,这次就顺便抽出一点时间去了解了一下Windows上采集音频的相关技术。
对于音频处理的技术,主要有如下几种:
在Windows操作系统上,常用的音频处理技术主要包括:Wave系列API函数、DirectSound、Core Audio。
其中,Core Audio只可以在Vista以上(包括Vista)的操作系统中才能使用,主要用来取代Wave系列API函数和DirectSound。
Core Audio实现的功能也比较强大,能实现对麦克风的采集、声卡输出的采集、控制声音的播放。
而Wave系列的API函数主要是用来实现对麦克风输入的采集(使用WaveIn系列API函数)和控制声音的播放(使用后WaveOut系列函数)。
DirectSound能够实现的功能估计和Wave系列API差不多,可能会更强一些(由于没有使用过DirectSound,不太肯定!)。
为了实现采集模块对操作系统的兼容性更好,基本上对麦克风输入的采集使用WaveIn系列API函数比较多;
在Windows XP系统中,没有直接提供对声卡输出进行采集的API,因此,在Windows XP要实现对声卡输出的采集会比较麻烦。 通常可选用支持混音的声卡,然后通过使用声卡的混音模块来实现采集,但并不是所有的声卡都支持混音的功能,这样的方案不具备通用性。
要实现通用性,可以采用虚拟声卡的方式来实现,从驱动层获取声卡的输出数据,但这种方案实现难度会比较大。
而在Vista以上的系统中,如Win7,则可以使用Core Audio中的API函数来实现采集声卡输出的功能。
对于混音模块的实现,目前基本是使用自定义的混音算法来完成功能,系统没有直接的API函数可供调用。
涉及的API函数:
waveInOpen
开启音频采集设备,成功后会返回设备句柄,后续的API都需要使用该句柄
调用模块需要提供一个回调函数(waveInProc),以接收采集的音频数据
waveInClose
关闭音频采集模块
成功后,由waveInOpen返回的设备句柄将不再有效
waveInPrepareHeader
准备音频采集数据缓存的空间
waveInUnprepareHeader
清空音频采集的数据缓存
waveInAddBuffer
将准备好的音频数据缓存提供给音频采集设备
在调用该API之前需要先调用waveInPrepareHeader
waveInStart
控制音频采集设备开始对音频数据的采集
waveInStop
控制音频采集设备停止对音频数据的采集
音频采集设备采集到音频数据后,会调用在waveInOpen中设置的回调函数。
其中参数包括一个消息类型,根据其消息类型就可以进行相应的操作。
如接收到WIM_DATA消息,则说明有新的音频数据被采集到,这样就可以根据需要来对这些音频数据进行处理。
(示例以后补上)
涉及的接口有:
IMMDeviceEnumerator
IMMDevice
IAudioClient
IAudioCaptureClient
主要过程:
创建多媒体设备枚举器(IMMDeviceEnumerator)
通过多媒体设备枚举器获取声卡接口(IMMDevice)
通过声卡接口获取声卡客户端接口(IAudioClient)
通过声卡客户端接口(IAudioClient)可获取声卡输出的音频参数、初始化声卡、获取声卡输出缓冲区的大小、开启/停止对声卡输出的采集
通过声卡采集客户端接口(IAudioCaptureClient)可获取采集的声卡输出数据,并对内部缓冲区进行控制
(示例以后补上)
混音算法就是将多路音频输入信号根据某种规则进行运算(多路音频信号相加后做限幅处理),得到一路混合后的音频,并以此作为输出的过程。
我目前还做过这一块,搜索了一下基本有如下几种混音算法:
将多路音频输入信号直接相加取和作为输出
将多路音频输入信号直接相加取和后,再除以混音通道数,防止溢出
将多路音频输入信号直接相加取和后,做Clip操作(将数据限定在最大值和最小值之间),如有溢出就设最大值
将多路音频输入信号直接相加取和后,做饱和处理,接近最大值时进行扭曲
将多路音频输入信号直接相加取和后,做归一化处理,全部乘个系数,使幅值归一化
将多路音频输入信号直接相加取和后,使用衰减因子限制幅值
(完)
]]>元旦小长假前几天偶然中接触到Sublime Text 2, 初步使用下来感觉很不错,是又一款比较适合程序员使用的文本编辑器。
在Sublime Text 2之前,一直比较喜欢使用VIM和Notepad++,其中VIM主要用来查看一些源代码文件或编辑一些文本,
而Notepad++更多是用来替代UltraEdit查看Log文件,目前使用下来感觉还不错,搜索功能同样强大。
当然现在VIM还基本属于初步上手阶段,主要是VIM需要记忆的命令太多,而自己在Windows上使用VIM的频率也不怎么高。
到Sublime Text 2官网上下载Sublime Text 2,目前的版本是2.0.1。
Windows上我下载的是portable版本,解压后即可运行,这样我结合同步工具就可以再多台机器上共享Sublime Text 2的配置和插件了。
Ubuntu我是通过在虚拟机VirtualBox中安装的,版本是10.04,虽然版本旧了一点,但相对于新版的UI,我还是喜欢这版的。
Ubuntu上下载Sublime Text 2的Linux 32Bit版本即可,解压后即可运行。
这里C/C++编译器使用的是gcc/g++。
在Windows上使用gcc/g++,可以安装MinGW,安装时要勾选上g++,默认没选择g++,安装好后需要在系统环境变量Path中加上C:\MinGW\bin
,这里是假设MinGW被安装在C盘中。
打开Windows的命令控制台,输入g++ -v
来查看g++是否安装成功。
当然在Windows中也可以使用VC++中的编译器,如何在命令行下使用VC++编译器请自行google之。
如果在命令行下可以使用VC++编译器,这样我们可以在Sublime Text 2中新建一个C++编译配置,以实现在Sublime Text 2中使用VC++编译器。
在Ubuntu下安装gcc/g++,在终端命令行中执行sudo apt-get install build-essential
即可。
Windows下,要在Sublime Text 2中实现编译、运行C/C++代码,需要修改或新建一个C++编译配置。
具体是:
Sublime Text 2中Tools –> Build System –> New Build System…
输入如下内容,并将文件保存为C++Bulider.sublime-bulid。
在Windows中,该文件被保存在Sublime Text 2目录下的Data\Packages\User中。
在Ubuntu下,该文件被保证在当前用户目录下的.Config/sublime-text-2/Packages/User中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
我的机器上直接使用sublime Text 2默认的C++编译配置也是正常的,应该是我之前安装了Git的原因。
ubuntu下也是可以直接使用sublime Text 2默认的C++编译配置的。
搭建好C/C++编译环境后,Sublime Text 2中编译运行C/C++代码了。
如在Sublime Text 2中新一个Demo.cpp文件,在其中输入代码:
1 2 3 4 5 6 7 |
|
勾选Tools –> Build System –>C++或者C++Bulider,使用Ctrl + B
编译代码,Ctrl + Shift + B
执行程序。
和VIM、Notepad++等一样,Sublime Text 2也支持通过插件来扩展其功能。
Sublime Text 2中安装插件前可先安装Package Control,然后通过Package Control来查找、安装插件。
安装Package Control的方法是:
在Sublime Text 2中按Ctrl + `,调出Sublime Text 2的命令行,在其中输入如下内容后,回车即可。
1
|
|
安装成功后,重启Sublime text 2,这时,在Preferences下看到Package Control了。
在Windows中,Package Control被安装在Sublime Text 2目录下的Data\Installed Packages中;
在Ubuntu中,Package Control被安装在当前用户目录下的.Config/sublime-text-2/Installed Packages中。
而Sublime Text 2中其他的插件,在Windows中都被安装在Sublime Text 2目录下的Data\Packages中,
在Ubuntu中被安装在当前用户目录下的.Config/sublime-text-2/Packages中。
执行Ctrl + Shift + P
调出命令窗口,输入install,根据提示选择Package Control: Install Package;
稍等一下,就会弹出Sublime Text 2的插件列表,在其中选择需要的插件即可完成安装。
下面是几个比较适合程序员使用的Sublime Text 2插件:
Alignment: 用于代码对齐
CTags: 用于方便浏览源代码
Git:源代码版本控制
Gist:Github中代码片段管理、分享工具
要使Sublime Text 2中的CTags插件可用,需要在系统中安装CTags工具。
到这里下载CTags工具,Windows选择ctags58.zip,解压后将其中的ctags.exe拷贝到C:\MinGW\Bin下。
Ubuntu选择ctags-5.8.tar.gz,解压后, 在Bash中进入ctags-5.8目录,
通过执行./configure
, make
, make install
来安装ctags。
安装好Gist插件后,需要修改Gist.sublime-settings这个配置文件,在username后输入Github的登陆用户名,在password后输入Github登陆密码,保存即可。
安装好插件后,通过Ctrl + Shift + P
调出命令窗口,然后输入插件名,根据提示可选择相应的插件功能。
这里主要是利用Sublime Text 2中打开文件夹和快速搜索等功能,配合CTags插件来使用。
对于一个已存在的工程,可以通过Sublime Text 2的Open Folder这个功能来打开工程的全部文件,其中目录结构也同样保留,这个功能对于查看开源代码是非常有帮助的。
而Sublime Text 2的快速搜索功能对于定位代码中的函数、变量等是非常有帮助的,结合CTags插件使用则会更加方便。
使用Ctrl + P
可调出Sublime Text 2的快速搜索界面,其功能主要包括:
可以快速跳转到当前项目中的任意文件,可进行关键词匹配
用 @ 可以快速列出/跳转到某个函数
用 # 可以在当前文件中进行搜索
用 : 加上数字可以跳转到相应的行
可通过关键字转到某个文件同时加上 @ 来列出/跳转到目标文件中的某个函数,或是同时加上 # 来在目标文件中进行搜索,或是同时加上 : 和数字来跳转到目标文件中相应的行
通过Ctrl + Shift + P
调出命令窗口,在其中输入Gist,选择Gist: Open Gist会列出Github上Gist中已存在的代码片段,选择一个可用Sublime Text 2打开查看或修改。
修改后通过Gist: Update File上传到Github的Gist中。
要增加一个新的代码片段,可在Sublime Text 2中新建一个文件并在其中放入代码片段,或打开一个已存在的文件。
然后使用Gist: Create Public Gist,然后输入描述文件和文件名即可。
这里,顺便推荐一款在Chrome浏览器中使用Github Gist的插件 – EasyGist。
使用下来感觉还不错,很适合在Chrome中来管理代码片段。
在Google网上应用点里搜索EasyGist,选择扩展程序,然后安装即可。
初次使用需要登录一下。
通过Ctrl + Shift + P
调出命令窗口,在其中输入Preferences,选择Preferences Settings - User
将打开的文件内容修改为如下:
1 2 3 4 5 6 |
|
这样,在Sublime Text 2中也可以使用VIM的相关命令了。
如插入文本前需要使用i
进入插入模式,用Esc
回到正常模式中。
在Windows中,云同步工具一直在使用坚果云,在国内算是做的不错的一个,支持多目录同步。
由于我使用的是Sublime Text 2的portable版,这样将Sublime Text 2放入到坚果云的同步目录中。
这样,我在Sublime Text 2中安装的插件和修改的相关配置都会被同步到云端,
这样家中的机器也会自动进行同步,然后可继续使用Sublime Text 2。
为了使用方便,我一直使用HK4WIN来管理键盘快捷键。
在HK4WIN的配置文件中加入启动Sublime Text 2的快捷键,这样,以后启动Sublime Text 2就很方便了。
(完)
]]>因为工作以来基本没有接触过UI方面的编程,加之以前也没有使用过Custom Control,这次做一个简单学习和总结,以便后用。
在VS2005中创建一个基于Dialog的MFC工程,DemoUI;
在DemoUI上右击选择Add->Class,在类向导中选择MFC Class, 再点击Add,在Class Name中填入类名,如CMyPad,选择Base class为CWnd,点击Finish, 这时自动生成MyPad.h和MyPad.cpp。
设置如下属性,其他属性使用默认即可:
1 2 |
|
其中,这里的MyDrawPad必须与注册窗口时使用的类名一致,否则编译可通过,但无法运行。
具体实现为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
其中,MYWNDCLASS是在MyPad.h中定义的宏:
1
|
|
(工程属性中设置为Unicode,API会调用对应的Unicode版本,因此字符串前需加上L)
首先在CDemoUIDlg类中添加一个成员:CMyPad m_Pad
;
然后进行关联,方法有两种:
在CDemoUIDlg ::OnInitDialog
中关联:m_Pad .SubclassDlgItem(IDC_CUSTOM_PAD, this)
;
在CDemoUIDlg ::DoDataExchange
中关联:DDX_Control(pDX, IDC_CUSTOM_PAD, m_Pad)
;
两种方法只能用其中一种,不可同时使用。
在Class View中选择CMyPad,右击打开其属性界面,选择消息一栏,选择需要响应的消息,并增加对应的消息处理函数。
如选择WM_PAINT
,选择<Add>OnPaint
,这样就在CMyPad中自动添加了响应WM_PAINT消息的处理函数OnPaint()。
简单测试一下, OnPaint函数的实现如下:
1 2 3 4 5 6 7 |
|
编译运行,即可看到在Custom Control中绘制出”test123”字符。
(完)
]]>随着项目的积累、经验的丰富,不同阶段,自己对设计的理解也是不一样的,就像看一本好书,每读一遍,对其中内容的理解往往也会不同一样。
下面是自己对如何设计良好软件的一点理解:
1.模块/类要尽可能只做一件事,并且要把事做好(符合单一职责原则、高内聚低耦合),这样也有利于提高重用性(KISS原则、DRY原则)
模块/类设计的注意点:
模块所处层次是否分明[分层设计(横行划分)],功能是否清晰[模块化设计(纵向划分)、正交设计]
模块接口要仔细设计
接口要提供哪些功能,接口函数/参数命名是否直达其意(命令查询分离原则),有什么限制,如何返回结果,是否易用,尽可能多从用户使用角度考虑接口的设计;
接口设计要符合“接口隔离原则”,大而全的接口往往没有多个功能分类明确的接口要灵活、易用。
信息隐藏要做好,模块的边界(接口)要清晰,用户对模块的了解要尽可能少(最少知识原则)
模块内部实现要尽量符合“迪米特法则”和“契约式设计”
只和必要的模块产生依赖,模块之间绝不可出现循环依赖
一组功能相关的模块可放在一起,组成功能包,这样便于修改和维护(共同关闭原则)
2.设计时,应权衡一下是否要考虑可扩展性(需求将来是否会变化,如果比较稳定则不用考虑,避免过度设计)
如何设计来保证模块/函数具备一定的可扩展性(要符合开闭原则)?
找出可能的变化,合理抽象(封装变化),提炼接口, 针对接口编程(依赖稳定的抽象/接口), 不依赖具体的实现
组合优先继承(模块之间的依赖优先考虑是否可采用组合,使用继承主要是以使用多态为目的[里氏替换原则/针对接口编程])
模块之间要正交(模块间功能要尽可能不重叠)
高层模块和低层模块之间要符合”依赖倒置原则”和“好莱坞原则”
高层模块提供低层模块所需的接口,高层模块实现时针对这些接口编程,不依赖低层的具体实现;
而低层模块需要实现这些接口,实际运行时以多态的形式为高层模块提供功能(好莱坞原则)。
3.层次是一种软件抽象,分层设计可提高软件的灵活性,很多设计手法,其本质都是在软件的结构中加入更多的层次。
如对文件操作进行封装,提供一个文件操作类来代理文件操作请求,这样可屏蔽不同操作系统下文件操作API的不同,增加可移植性。
但也要充分考虑到层次带来的复杂性,会增加对软件理解和维护的难度。
因此,要努力控制层次在三层左右,否则宁愿牺牲一点抽象(软件设计是一个权衡和选择的过程)。
另外,层次抽象要保持内在的一致性,一个层次内部应该只做自己应该做的事,而不要去做其他层应该做的事。
如在设计DirectShow Filter时,应尽量把功能层次的模块与Filter层次分开,在Filter层次中只做DirectShow框架下filter应实现的功能,
如媒体类型的协商、与其前端/后端filter的连接、Filter之间的数据流传递等,而不涉及具体功能逻辑部分的实现;
同样,功能实现层也不应该依赖DirectShow框架(如数据的输出可用回调来实现解耦)。
这样做的好处是,以后可单独提取出具体的功能模块,移植到其他非DirectShow框架中,即Filter层仅是一层壳而已。
否则,这样的剥离将难以实现,同时也会因为层次边界的模糊而影响程序的可读性。
4.代码实现时,要有良好的编码风格,且风格要统一,函数和变量命名要准确表达其意;
逻辑实现要简洁利索,避免功能大而全的复杂函数,函数实现也要尽量符合KISS原则,只负责一件事;
总之,要时刻考虑软件维护者的感受(说不定几个月、几年之后你就是软件的维护者呢),尽可能减少让人产生怪异的感觉。
即尽一切方法提供程序的可读性。
附上一份关于软件设计基本原则的思维导图,以便参考。
图片比较大,请移步这里直接查看原始文件。
(完)
]]>为了便于查看,特在MindPin上绘制了一张个人技术规划的思维导图。
如果图片不清晰的话,请移步这里直接查看原始文件。
使用下来感觉还是蛮不错的,UI简洁、快捷键也随手、自动保存、支持多种格式导出。
(完)
]]>1
|
|
在ubuntu的shell下,使用上述git命令来下载ffmpeg,下载所需的时间会有点长。
等ffmpeg下载完成,ubuntu上就已经存在了一份完整的ffmpeg源代码了。
下面就可以进行编译ffmpeg了。
编译前我们可以使用下面命令对ffmpeg的源代码进行备份:
1
|
|
这样就在当前目录下产生一个ffmpeg.tar.gz文件。
在以后需要的时候可以通过下面的命令提取出ffmpeg源代码:
1
|
|
在编译ffmpeg之前,请先安装好基本的C/C++的编译开发环境,请参考上篇文章。
另外,ffmpeg编译时需要使用yasm,如果系统中没有安装,可以通过下面的命令来安装:
1
|
|
上述的编译工具安装好后,就可开始编译ffmpeg了。
ffmpeg是采用autoconfig和automake等工具自动生成makefile,然后再通过make进行编译,具体的编译的过程如下:
使用./configure
产生makefile文件
使用make
进行编译
使用make install
将ffmpeg安装到系统中
安装ffmpeg到系统中,需要root权限,在shell下执行下面的命令切换到root账户下:
1
|
|
根据提示输入root密码,然后还需再次进入到ffmpeg源代码目录中,再执行make install
即可。
默认是安装在/user/local下,其中:
头文件放在/user/local/include目录下
编译好的libs放在/user/local/lib目录下,其中,在该目录下还有一个pkgconfig目录,里面存放着每个lib的配置文件
编译好的可执行文件(ffmpeg、ffprobe、ffserver)放在/user/local/bin目录下
文档在/user/local/share/man/man1目录下,同时在/user/local有一个指向此目录的链接
后面计划研究一下ffmpeg的架构,如何抽取出ffmpeg中某个Codec,如何增加自己的Codec到ffmpeg中,以及如何基于ffmpeg开发一个简单的播放器。
(全文完)
]]>后来,跟其他实验室的同学借了Linux的安装光盘,在自己的机器上第一次装上Linux,不过后来好像也没什么深入的使用。
工作以后,一直是在Windows上做开发,基本不涉及Linux,不过自己对Linux还是蛮感兴趣的,在家里的电脑上也一直安装着Ubuntu,偶尔也会上去玩一玩。
随着和开源软件的接触,发现很多的开源Code都是来自于Linux,自己也一直想学习一下Linux中C/C++开发的方法。
由于自己大部分的工作和家人对电脑的使用都是集中在Windows上,不希望来回切换系统,故多数是在Windows上使用VirtualBox虚拟机安装Ubuntu来使用。
虚拟机上的Ubuntu,我只会安装Linux C/C++开发所必要的一些软件,如代码编辑器Vim, C/C++编译调试工具,代码版本控制工具Git等。
我使用的Ubuntu版本是10.04,之前安装过11.10这个版本,不过在虚拟机上运行速度不是很好。
如何使用VirtualBox安装Ubuntu,请Google参考相关教程,要注意的是下载下来的Ubuntu的ISO镜像文件,最好验证一下MD5码,以确保镜像文件是完整正确的,否则安装可能会失败。
另外,中文拼音输入法ibus-pinyin还算比较好用,在Ubuntu软件中心搜索ibus-pinyin并安装,然后“系统->首选项->IBUS首选项”切换到输入法选项卡,添加“汉语->拼PINYIN”即可。
下面主要是讲一讲Linux下C/C++开发的一些话题:
安装C/C++编译工具,包括gcc, g++, gdb, make等,只需要执行下面这条命令即可:
1
|
|
使用”gcc -v”来检查一下安装是否成功。
安装自动生成makefile的相关工具,只需要执行下面这条命令:
1
|
|
这样,依赖的工具也会被安装,包括autoscan、aclocal、autoconf、automake等。 有了这些工具,在大的开发项目中,就可以不用自己去编写makefile了。
安装源代码编辑器Vim,会比Ubuntu原本自带的vi更好用些。 关于如何将Vim打造成一个IDE,以后再研究吧,网上也有相关的文章可参考,主要是使用Vim插件并修改Vim配置文件。 安装Vim执行下面这条命令:
1
|
|
这样以后在shell下可直接使用”vim 文件名”,在当前目录下创建一个新文件并打开文件进行编辑或直接打开已存在的文件进行编辑。
安装源代码版本控制工具Git,只需要执行下面这条命令即可:
1
|
|
上述工具都安装成功后,在Ubuntu上就已经具备开发C/C++程序的基本环境了。
当然,在Linux上做开发也是有IDE可供选择的,如Eclipse、NetBeans等,据同事介绍,似乎NetBeans还算好用,和Windows下VC比较相似了,而做Android开发一般都是用Eclipse。
目前, 我还不打算使用这些IDE,而是想从底层学习这种在shell下用Vim编写源代码和makefile来完成Code编译和调试的开发模式。
这样,不但有助于掌握Vim和常用的shell命令,而且对相关编译工具的使用、makefile的编写以及理解编译的过程都是有帮助的,当然这样感觉上也会更酷。
Linux中的命令有很多,这里只列出在开发过程中一些比较常用的命令。
每一个命令可能都有一些选项参数可以使用,用man 命令
或者命令 --help
可来查看相关说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
被尊称为程序员神器的Vim有着比较高的学习曲线,不过掌握一些基本的Vim命令,足以让你应付一般的文本编辑。
这里有一份Vim命令的速查卡:
常用的Vim操作请参考陈皓的简明Vim练级攻略,按照攻略来练习吧。
(未完待续)
]]>这里,列出我最常用的一些软件,以备后忘。
思维导图使用FreeMind绘制。
在Chrome出来之前,通常使用Firefox,有了Chrome,就再也没变过。不过,网购一般还是要回到IE的。
常用的Chrome插件:
AdBlock、FaWave(发微)、Google Mail Checker、IE Tab、Google阅读器通知程序等
以前在网上看到不错的文章一般会收藏到360doc,自从接触了Evernote,个人知识管理就由Evernote负责了。
另外,我比较多的阅读时间是花在Google阅读器上,看到喜欢的文章会“加星”标识。
这样通过ifttt这个网络工具就可以自动将加星的文章发送到Evernote中,因为每个Evernote账户都对应一个个人Evernote邮件地址,向这个地址发送内容会自动出现在Evernote中。
随着时间的推移,Evernote中的文章会越来越多,如果不加分类,以后会很难找到需要的文章。
因此,最好一开始的时候就建立好笔记的结构,并利用标签对文章分类。
知乎上有一些关于如何用好Evernote的讨论,可以去参考一下。
我的Evernote笔记分类结构:
另外,Evernote每个月只有60M的同步流量,不过对于保存一般的文本和少量的图片是完全够用的。
以前要在网络上备份文件,一般都是用网络硬盘,不过一般空间都不大。
而如今的云计算产品是越来越多了,特别近两年,云储存的产品遍地开花,如来头大的有Google Drive、微软的SkyDrive等,我想最出名应该是非Dropbox莫属。
我正式使用云储存应该是今年初,开始用的是金山T盘,不过使用下来并不太满意,而且金山似乎对该产品不太重视,直到在善用佳软的博客上看到坚果云的介绍,才慢慢转到坚果,并一直使用至今。
坚果云总体使用下来还不错,同步在背后默默进行,只需将要同步的文件放到指定的文件夹就可以了,而且速度很快。 支持多同步文件夹,同步的文件有版本控制,网页界面很简洁,感觉是用心在做这款产品。
不过,对于免费用户,每个月只有1G的上传流量,3G的下载流量,刚开始转过来的时候上传流量不太够用,后来就基本够用了。
对于文本编辑器,目前使用最多的应该是Notepad++,主要用来查看、编写一些文本文件,偶尔也会用一用VIM,主要是一些命令总是不太记得住,以后还是要好好学习一下VIM的。
在查看Log文件目前使用比较多的还是UltraEdit,不过慢慢应该会转到Notepad++和VIM上的。
我现在越来越喜欢用键盘快捷键来代替鼠标操作,这样日常的操作效率会高很多。
一个用AutoHotkey开发的HK4WIN2是我用来管理快捷键的主要工具,可以定制打开指定软件和文件夹等快捷键。
正式使用Git也是今年初开始的,前面有几篇关于Git学习的文章,这里,这里。
目前使用Git最多的还是来管理我的这个博客,以后慢慢会在工作中引入使用。
偶尔也会用SVN来下载一些开源代码。
PDF阅读,我一直使用免费小巧的Foxit Reader; 而PDF打印,则常用使用PDF Creator。
系统垃圾文件的清理使用CCleaner。
磁盘碎片整理Defraggler是不错的选择。
文件恢复工具Recuva值得一试。
文件加密非TrueCrypt莫属,非常强大。
文件的快速搜索Everything很好用,速度非常快。
文件压缩和解压缩一直在使用7-Zip。
虚拟机目前只使用过VirtualBox,操作很简便。
中文输入法尝试过很多,如紫光拼音、Google输入法等,目前一直在使用搜狗输入法。
Google Reader是我每天开始阅读的地方,好的博客我会订阅在这里。
Google gmail是我的主要邮箱,每天会收到不少邮件,对于交流的邮件我一般会即时回复。
微博(新浪微博和腾讯微博)是我每天了解这个社会热点事件的主要场所,在Chrome中主要是使用FaWave(发微),很少去使用网页版。
偶尔会用用Google日历,重要的日子给个提醒。
目前Blog的图片基本是放到Flickr上,因此,Flickr我是做为图床来使用的。
(暂完)
]]>由于和我一起做这个项目的同事,会使用Python,编写的Python脚本能够将Log中的相关数据以图形的方式显示出来,这样就便于我们快速检测出是否输入、输出有异常情况发生。 这样,也就加快了解决问题的效率。
为此,在这里简要记录一下具体的方法,以便以后在需要的时候能够做参考。
需要的工具如下:
Python(版本是2.7.3,自带一个简单的IDE)
数值运算库numpy,matplotlib依赖这个库
类似matlab的图形库matplotlib
文本编辑工具sed
有了这些工具,还需要了解一些正则表达式,可参看这里。
当然,基本的Python脚本编程功底的需要的。网上关于Python的教程有很多,我参考了如下几篇:
从这几天学习的效果来看,Python应该算是比较容易上手的语言。
可能是Python里面的语法和概念和C++有相似之处,所以很多语法基本只有了解一下怎么写就可以了。
其中,列表、序列、字典这几种Python中的数据结构要用熟,另外,在Python中不需要再关心数据的类型,直接用就好了。
终于体会到一点“学好C++之后再学其他语言要相对容易”的感觉。
看了上述的教程,现在写一些简单的脚本也是很容易的了。
参考下面的代码,其中,Sed命令中引号的内容为要匹配文本的正则表达式,正则表达式在两个斜杠之间,后面的p是打印的命令,Sed命令执行的结果被重定向到后面的文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
这个批处理的脚本要和要分析的Log文件放在同一文件夹下,这样双击执行脚本,文件夹中会多了5个文件,这几个文件就是我们下面Python脚本需要用到的。
下面这个例子要完成的功能就是前端输入PTS的总体趋势是否正确,有无明显跳变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
运行这个Python脚本后,可看到输入的Video/Audio PTS以图形的方式被显示出来。
在这个例子中,我们可以看到要使用到Python的正则表达式库re,图形绘制库matplotlib,还需要了解python中文件的操作,列表的使用等。
具体的用法请google其他资料。
(完)
]]>这时遇到问题,要自己逐行去分析log定位问题往往是不太现实的事情。
为了提高log文件分析的效率,我们利用Python编写分析log的脚本,每个脚本分析log文件中一类信息,如从前端Encoder中接受到的PTS,分析PTS是否有错误或跳变等情况。
为了从原始的log文件中提取出我们关心的log部分,使用了一个命令行工具Sed。
Sed(streams editor)的具体使用方法可参考这篇文章sed命令+正则表达式。
在一个数据量很大的文本中要查找出所有符合特定规则的文本,使用文本编辑器中简单查找功能往往很难达到要求。
不过现在的文本编辑器也越来越强大,我熟悉的如VIM、Notepad++、UltraEdit等都是支持使用正则表达式来完成复杂的、自定义规则的文本查找功能。 上述的Sed也是使用正则表达式来实现强大的文本查找功能。
下面将会简单介绍一些关于正则表达式的知识,以后有时间会写一篇关于如何使用Python和matplotlib库来分析log数据并图形化显示分析结果的文章。
对于正则表达式,我基本也是属于初学,这里主要是记录一下正则表达式的基本语法,学会如何使用这些基本语法来构建符合自己查找意图的正则表达式。
网上关于正则表达式的教程有很多,我主要参考了如下几篇:
正则表达式(Regular Expression),实际上就是一个描述待查找文本特征的字符串,这些字符串就构成了查找的规则,如下面的这些例子:
abc -> 描述了待查找文本的特征是:待查找的就是包含abc的文本
\d…\s123 -> 描述了待查找文本的特征是:以数字开关(在0至9范围内),后面跟3个任意字符,再跟一个任意类型的空格,之后跟着123
[1-9][0-9]{4, 11} -> 描述了待查找文本的特征是:以1至9范围内任一数字为开头,后面最少跟4个,最多跟11个数字(在0至9范围内)
\d{4}-?\d{7, 8} -> 描述了待查找文本的特征是:以4个数字开头,中间可以有也可以没有-,后面最少跟7个,最多跟8个数字(在0至9范围内)
如果之前没有接触过正则表达式,初次看到上述的这几个字符串,肯定不知所云,觉得莫名其妙。 没关系,我相信大多数人在开始学习正则表达式时会有这样的感觉。
实际上,通过上述的一些解释,我们可以看到正则表达式中使用了一些特殊的符号来表示一类字符,如使用\d来表示0至9范围内的数字等。
要想对正则表达式有深入了解,我们需要去学习一下正则表达式的基本语法,熟悉之后,我们就可以根据实际的需要来构建自己的正则表达式。
注:文中的部分例子来自于正则表达式30分钟入门教程。
元字符是正则表达式中一种特殊的字符,是用来表示其他字符的字符,实际查找的文本并不包含自身,相当于是信息的一种描述。
在正则表达式中,有如下几种元字符:
1 2 3 4 5 6 7 8 9 10 11 |
|
元字符是用来表示其他的字符,而不能用来表达自身,因此,如果我们查找的文本中的确是要包含元字符时,这时就需要使用转义字符。
熟悉C/C++的同学应该对转义字符会很熟悉,转义字符就是使用反斜杠’'来取消元字符的特殊意义,而表达自身的一种方法,类似上述元字符中的\w,\s,\d,\b。
上述的这几个在形式上类似转义字符的元字符在实际中是不需要转义的,因为它们相当于是2个字符,而如果要查找的文本中要包含., ^, $,则需要转义., \, \$。
在正则表达式中,允许使用特殊的符合来指定其面前一个字符或一个组合单元重复的次数。
有如下几种指定重复次数的方法:
1 2 3 4 5 6 7 8 9 10 |
|
如果我们事先知道在查找文本规则的特定位置的字符是在某个具体范围内,如某个位置上出现的字符只能是a,b,c,1,2,3中的一个时,正则表达式允许我们指定出这个字符集。
方法也很简单,就是将可能出现的字符放在方括号之中,如[abc123]即可。
对于连续的数字和字母,也可以用更简单的方法,如[0-9]表示出现的字符在数字0至9中的一个,类似的有[a-z],[A-Z]。
还可以将上面的组合起来,如[0-9a-zA-Z]。
正则表达式中的那些有特殊意义的符合也可以不用转义直接放在其中,如[.?+]可匹配., ?, +。
我们已经知道,要重复单个字符,可直接在这个字符后加上指定重复次数的限定符即可。
如果我们要指定一组连续的字符的重复次数,该如何做呢?
方法是先将这组连续的字符放到圆括号()中,然后在后面加上指定重复次数的限定符就可以了。
如(abc){2},就描述了重复2次abc, 即abcabc。
更复杂的一个例子:(?0\d{2}[) -]?\d{8}
这个正则表达式可以这样来分析:
首先是一个转义字符(, 它能出现0次或1次(?), 然后是一个0,后面跟着2个数字(\d{2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(\d{8})。
有时候一个正则表达式可能匹配出比我们预想要多的文本,如用于匹配电话号码的正则表达式(?0\d{2}[)-]?\d{8},不仅可匹配(010)88886666、022-22334455、02912345678等正常的号码,还会匹配出010)12345678或(022-87654321等这样的”不正确”的格式。
要解决这个问题,我们可以明确指定出每一种电话号码的匹配规则,各个匹配规则之间用’|’来分隔,这样只有符合指定规则中的一种时才会被匹配,这有点类似于C/C++中的’或’运算。
例如,0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0512-6733445)。
如果我们事先知道要查找的文本中肯定没有哪些字符,或者是要匹配除了某某字符之外的任意字符这种情况时,我们可以用类似C/C++中的’取反’运算来实现。
正则表达式允许的’取反’:
1 2 3 4 5 6 7 8 9 10 |
|
一些例子:
\S+ 匹配不包含空白符的字符串
<a[>]+> 匹配用尖括号括起来的以a开头的字符串
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。
例如,”a.*b” 它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
如果我们希望匹配尽可能少的字符,只需在之前的限定符后加个问号’?’,这样将会以懒惰模式进行匹配。
例如,a.*?b 匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。
1 2 3 4 5 6 7 8 9 |
|
这些语法应该是正则表达式的最基础部分,还有一些高级的特性文中并没有涉及。
如果能够掌握并熟练使用上述的这些正则表达式的基本语法的话,我相信对于大多数的情况,正则表达式能够帮助我们快速的从复杂的文本中匹配出我们需要的内容。
从现在开始,慢慢感受正则表达式的强大吧!
(完)
]]>随着越来越多的人开始使用Octopress搭建自己的个人博客,网络上出现了很多外观基本相同的博客。 虽然Octopress默认的主题设计的很简洁、美观,但为了打造一个属于自己的博客,还是希望能够做的更美观一些。 当然,借此机会自己也能够学习一点Web前端设计方面的知识。
Octopress支持SASS语法,改造Octopress主题基本是通过修改“sass\custom“下以scss为后缀名的文件来完成,大多数的改造是在_styles.scss这个文件中来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 |
|
在”source_includes\asides“下创建侧边栏相关模块的html文件,修改博客根目录下的_config.yml文件,主要是default_asides、blog_index_asides、post_asides这几项。
1 2 3 |
|
1 2 3 4 5 6 7 8 |
|
1 2 3 4 |
|
其中,iframe中的代码是来自新浪微博中”账号->我的工具->微博秀“,可以做一些简单的设置,并自动产生出嵌入代码。
1 2 3 4 5 6 7 8 |
|
其中,div中的代码来自豆瓣。 同时,要在_config.yml中添加douban_user: XXX (XXX为你的豆瓣用户名)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 |
|
其中,script中的代码由Disqus产生。 同时,需要修改_config.yml中Disqus的相关参数: disqus_short_name: XXX (XXX为你的Disqus用户名) disqus_show_comment_count: true
增加文章的评论需要将该文章对应的markdown文件开始部分中的comments设置为true(冒号后需有一个空格)。
我目前使用的是bshare。
在_config.yml中增加bshare: true。 在“source_includes\post”下的sharing.html中增加如下代码:
1 2 3 |
|
上述的代码由bshare产生,可以自己选择所需的外观。 这样,在每一篇文章的最下方会出现一个分享的小工具。
这部分需要第三方的plugin支持,目前我还没有解决中文标签在上传到github上连接出错的问题,在本地是OK的。 具体可参看这篇文章。
(完)
]]>目前为止,我使用过的文本编辑软件主要有:UltraEdit、VIM、Notepad++。
虽然UltaEdit功能强大,但可惜是收费软件,而且许多功能平时也用不上,在如今优秀开源软件蓬勃发展的今天完全是可以找到一款替代软件。
在查看Log文件过程中,UltraEdit的搜索功能是比较好用的,所以当然也希望替代的软件能够也拥有强大的搜索功能。
被誉为程序员编辑器的VIM,功能非常强大,开源软件,而且插件也很丰富,完全可以改造为一款IDE,但VIM的上手比较难,需要记住很多命令,且不同的编辑模式也是常人难以习惯的原因之一。 现在常被用来查看一些开源项目中文件。
Notepad++是最近才开始使用,在Windows下使用代替系统的Notepad是非常不错的,支持多文件同时打开,打开大文件的速度比较快,支持多种语法高亮,搜索功能也比较好用,支持插件扩展,其中HEX-Editor用来以16进制查看文件也是可以和UltraEdit相媲美的。
使用下来,Notepad++还是比较顺手的,也将会是我今后的常用软件之一。
(完)
]]>带着这个目的,我先尝试着找到VSS到Git迁移的可行方案,因为我之前的项目一直也是用VSS进行版本管理,相对于从没有使用过的CVS而言,应该更容易上手一些。至于CVS如何迁移到Git会在后面进行研究。
之前在学习使用GitStack搭建Git服务器时,在其官方网站上看到一篇讲如何迁移SVN版本库到Git的文章,文中有提到git svn命令,可见Git内部已经有了对SVN版本库进行操作的支持,继而顺着这个思路,试着去找找有没有支持对VSS和CVS的操作,结果发现Git没有对VSS的操作支持,对于CVS倒是有git cvsimport,但在Git Bash中却不认识该命令,运行git help vcsimport
也没有相关的文档,而使用git help vcs-migration
倒是有文档,其中也提到了git cvsimport,难道是我是在Windows上使用的Git(安装的是msysGit 1.7.10),而在Windows上还不支持该命令吗?不知道在Linux上的情况是怎么样的?
这个问题暂时先搁置,先研究一下如何将原有的VSS版本库迁移到Git上。
既然Git没有对VSS的支持,那么VSS转Git应该是需要第三方的工具来帮助实现这个转换工作(该工具要读取并分析VSS仓库的数据,然后按照Git版本库的内部结构来存放之前VSS版本库中的数据),按照这个思路,在Google上搜索了一下,果然发现有一个这样的工具,那就是vss2git,而且是个开源的项目,很对我的胃口。
下载下来并试用了一下,结果成功将之前的一个用VSS管理的项目转换为Git仓库,并且之前VSS的历史信息也被保留下来,基本是满足了要求。
打开VSS2Git,其界面就是一个对话框,在其中作一些必要的设置,主要是设置VSS版本库所在的路径、要转换VSS版本库中哪一个项目(也可以是整个VSS版本库中全部的项目)、转换结果的存放位置、转换Log存放的位置、转换过程中创建Git提交对象时所需的email的domain等,设置好这些之后,点击Go按键即可开始VSS到Git仓库的转换。
转换所需的时间长短取决于VSS仓库的大小,如果VSS仓库中项目众多,且开发的时间都比较久的话,那么完成整个仓库的转换应该需要的时间会比较长,但如果只是转换其中一个项目到Git,那么时候会比较短。
转换后,通过git log
可以看到之前VSS仓库中的历史信息,而且这些历史信息在Git仓库中仍然是按照提交时间的先后来排列的。
在转换过程中,打开存放Git仓库的目录,你会发现工作目录随着时间会在变化,这是因为VSS2Git在转换时会先分析并收集VSS仓库中的各种信息,然后再从VSS仓库中记录的最早的时间点开始Replay(演绎)整个VSS仓库数据形成的过程。
在演绎过程中,应该是先从VSS中取出最初的版本(Git仓库最初为一个空仓库),并将其版本中的文件作为Git工作目录的文件,然后按照Git先对工作目录进行文件快照再提交到Git仓库的做法,按照时间顺序逐个版本进行提交(提交时会取出VSS中对应版本的历史信息),最终将VSS版本库转换为Git仓库。
上述的演绎过程是我自己初步推断的大概过程,实际是否这么实现,请参考VSS2Git的源代码(用C#实现的)。 另外,VSS仓库内部的数据到底是如何组织的我也没有研究过。
要注意的几点:
如果是要将整个VSS仓库中的所有项目都迁移到Git中,那么在VSS2Git界面的Project一项中填上$
如果只是要将VSS仓库中的某一个项目迁移到Git上,则在Project一项中应填上$/Project1
(其中Project1为假设的项目名),甚至可以仅转换子项目,这样$/Project1/32Bit
VSS2Git一次要么完成VSS仓库的整体转换,要么只能转换VSS仓库中其中一个项目,这样要实现多个项目的转换就要多次执行VSS2Git
VSS2Git的每一次转换会产生一个Git仓库,如果是VSS仓库一次整体转换,则生成的Git仓库中将包含VSS仓库中所有的项目,这样带来的问题是,由于Git仓库中包含了所有的项目,这样以后不同项目的开发人员无法从Git仓库中只checkout出自己负责的那个项目文件,而是要checkout出所有项目的文件,这样对于公司的代码安全是非常不利的,那么,这样就不应该一次将VSS仓库整体转换,而是应该以项目为单位进行逐个转换,这样,对于不同的项目,对其Git仓库就可以为不同的开发人员设置不同的操作权限
VSS2Git在生成Git提交信息所需的email时,只是根据从VSS中提取出的User名加上在界面Email domain中填写的内容组合而成,实际生成的email地址未必是有效的(不一定能用来发送email)
更多的注意事项可至VSS2Git的主页查看。
通过VSS2Git,我们可以将VSS仓库中的项目逐一转换为该项目的Git仓库,这时我们可以将其放置到Git服务器上了,以便后续的开发和维护。
这里,我们假设使用GitStack搭建了Git服务器(如何搭建见上篇),现在的问题就相当于如何将一个现有的Git仓库导入到GitStack搭建的服务器上。因为正常的流程是在GitStack上先创建裸仓库,然后再向其中提交,如果这样做的话,我们之前的VSS中的历史信息恐怕就要丢失了,而这是我们不希望的结果。
不过幸运的是,GitStack支持导入一个现有的Git仓库到其中,其步骤也很简单,就是先将现有的Git仓库拷贝至C:\GitStack\repositories目录下面,然后登陆到GitStack上,在Repositories列表中会发现我们的Git仓库已经在其中,但Action栏则出现了一个绿色的安装,而非添加用户、查看仓库的那些按钮,这时我们只有点击这个绿色按钮就完成了该Git仓库的导入,之后我们会发现绿色的按钮被正常的添加用户、查看仓库的那些按钮替换掉,这时我们就可以为该Git仓库添加用户并设置访问权限了。
具体的过程,在GitStack的文档中有详细的阐述,请见Import an existing repository,这里不再赘述。
至此,如何将VSS仓库迁移到Git,并将生成的Git仓库导入到GitStack搭建的服务器上的大概过程就结束了。
(全文完)
]]>在Git常用命令的使用情景中提到过多人协作的项目往往是需要一个中心服务器来同步多人之间的工作成果,另外,最终的工作成果通常也是中心服务器上的代码为准(为了项目管理的需要),因此,备份时往往需要对中心服务器上的仓库进行刻盘。
中心服务器上创建的项目仓库一般为裸仓库(没有工作目录),且需要为项目开发人员设置访问和操作中心服务器上仓库的权限(访问、读、写)。
本文将介绍如何使用开源软件GitStack在Windows上搭建Git中心服务器。
为了学习如何在Windows上搭建Git服务器,用Google搜索了一下,发现多数的方案是采用CopSSH + msysgit + PuTTY的方式来实现,这种方案使用SSH协议(采用公钥和私钥进行身份验证,用PuTTY可以产生公钥和私钥,关于公钥和私钥可参考这篇入门介绍)与Git服务器通信,在安全性上应该来说是比较高的,但缺点就是搭建过程比较麻烦,且要清楚一些概念才知道自己在做什么,因此对新手来说有一定的难度。
另外,让我暂时没采用这种的方案的原因是CopSSH已经不再免费了(找到一个免费的版本Copssh 3.0.3,需要将icwbase-2.0.3-patch-100.exe这个补丁拷贝到Copssh的安装目录下运行来修复回退键和左右方向键不能正常的问题),对于喜欢开源软件的我来说,还是希望能够找到其他的开源软件来代替。
关于这种方案的几篇文章:
另外,还有一种采用Gitolite来搭建的方案,可参考Gitolite构建Git服务器,讲的很详细。
无意之中,通过google发现了GitStack,查看了官方文档,感觉不需要做什么特殊的设置就可以在Windows上搭建Git服务器,并且对用户权限的设置也很简单,故决定下载下来试用一下,测试下来果然很方便,且在Client端也成功进行了clone和push等操作(虽然中间遇到一个问题,后面会提到)。
为了以后有个参考,特在此记录下用GitStack搭建Git服务器的主要过程。
工具列表:
到其官方网站上下载最新版的GitStack 1.4.1。
安装文件有100M,要注意的是,目前GitStack只支持下面几个系统(不支持Windows XP):
另外,GitStack是一个新的开源软件(可以看看release的历史),目前有些功能可能还不是很完善,文档也不是很全面,好在GitStack并不复杂。
安装和普通的Windows软件一样,双击安装包自动进行安装,要注意的是最好其安装路径中不要包括空格,所以不建议安装到C:\Program Files下,默认是安装到C:\GitStack下。
安装好GitStack后,下面主要就是配置GitStack和仓库管理。
提醒一下,只需要在服务器上安装GitStack即可,其他的客户机上是不要安装的。安装好GitStack后,可以在任意机器上通过浏览器登录到Git服务器上(当然实际上只有仓库的管理员才有权限登录)。
在服务器上,可以通过开始菜单找到GitStack打开,也可以直接打开浏览器,在地址栏里输入http://localhost/gitstack/打开登录界面。
另外,也可以通过server机的IP地址来登录,如server的IP地址为:192.168.0.105,则可以直接在浏览器的地址栏中输入http://192.168.0.105/gitstack/打开登录界面(注意在客户机上只能使用这种方式来打开登录界面,通过ipconfig可以查看本机的IP地址)。
初始状态下,默认的登录账户为admin,登录密码也为admin。管理员登录后可在Settings->General中修改admin的登录密码。
勾选Enable web based repository browsing选项开启在浏览器中直接查看Git仓库的内容。
另外还有两个Repositories和Users & Groups两个界面,其中在Repositories中可以在服务器上创建项目的裸仓库,直接输入仓库名(如输入ProjectRepos),然后点击Create按钮即可(会在服务器C:\GitStack\repositories下创建一个ProjectRepos.git裸仓库),创建好的仓库也会在Repositories中显示出来,并显示出该仓库的clone的地址git clone http://localhost/ProjectRepos.git,之后就可以在Action下通过浏览器查看仓库、添加用户/Group并设置用户/Group权限等。
在Users & Groups中,Users下是用来创建用户或修改用户密码等,每个用户对应一个Username和其Password,已有的用户会在上面的列表中显示出来;Groups下用于创建组,可以在每个Group下添加或移除用户,已有的Group也会在列表中显示出来。
上述已经在服务器上创建了一个ProjectRepos.git裸仓库,现在我们在服务器上来克隆该仓库。
1 2 3 4 |
|
默认的是80端口,可以修改为其他端口。 这里,会提示输入用户名和密码,注意输入的用户名和密码不会被显示出来。
cd LocalRepos 进入了工作目录,我们可以添加文件到工作区,并提交到本地仓库中。
然后,将本地修改推送到服务器的仓库里:git push origin master,这里会提示输入用户名和密码,注意输入的用户名和密码不会被显示出来。 通过git remote -v,我们可以查看origin对应的服务器上的仓库地址。
这时打开GitStack,可以看到服务器上仓库有了提交的内容。
先在客户机上安装msysgit 1.7.10。
1 2 3 4 |
|
这里,会提示输入用户名和密码,注意输入的用户名和密码不会被显示出来。
cd LocalRepos 进入了工作目录,我们可以添加文件到工作区,并提交到本地仓库中。
然后,将本地修改推送到服务器的仓库里:git push origin master,这里会提示输入用户名和密码,注意输入的用户名和密码不会被显示出来。 通过git remote -v,我们可以查看origin对应的服务器上的仓库地址。
这时打开GitStack,可以看到服务器上仓库有了提交的内容。
在客户机上也可以打开GitStack,直接在浏览器的地址栏中输入http://192.168.0.105/gitstack/打开登录界面,当然这需要知道管理员密码。
(这里要注意的是,要保证在客户机上能够成功打开GitStack或者从服务器上克隆仓库,必须将服务器的防火墙关闭,否则在客户机上的这些操作就会失败。这个问题一直困扰了我好几个小时。)
可见,服务器和客户机在操作上已经没有什么区别了,这正是Git作为分布式版本控制系统的体现。
(全文完)
]]>在此,记录一下读后感,总结一下在设计库接口时应该考虑的事项,以备后用。
参考的文章如下:
另外,作者将其博文整理出一份C++工程实践经验谈,很值得一读。
以往开发directshow filter时,其接口通常都是采用由纯虚函数组成接口类的方式来实现,其主要原因应该是directshow是以COM为基础而设计出来的框架。
对于一个filter,在发布时,通常要提供一个header file和一个.ax结尾的dll文件,header file告诉AP该filter提供了哪些接口和相关数据结构。
这时,如果有一个新功能需求,需要在原filter的基础上增加,并且新版本的filter要能够直接在AP中直接替换老版本,而不影响AP原有功能(新功能会在下一版AP中被添加),这时,我们该怎么办?
参考陈硕文章中的做法,应该可以这样实现: 新的功能在新的接口中定义,并且新的接口要继承于旧的接口。
大概的实现应该是这样的(略去了其他细节):
老版本:
1 2 3 4 5 6 7 8 |
|
新版本:
1 2 3 4 5 6 7 8 9 |
|
这样做基本满足了要求(二进制兼容的),但这种做法引进了新的接口,而且为了向下兼容,还需要继承老的接口,如果日后新的需求不断出现,则会有更多的接口被引入,长期下去容易引起混乱,对于后续维护的人而言,也可能会造成困惑,因此,这种带版本的接口似乎不是一个很完美的方案。
对于那些老的采用虚函数作为接口的项目,如果后来又新需要,则上述的方法是可取的,因为,你不太可能去修改之前经过测试的稳定代码。
如果是新的项目,可以在设计的时候多考虑一些库的可扩展性和二进制兼容性问题。
二进制兼容性问题是在库扩展新功能或修复bug后更新版本时需要考虑的。
为什么要考虑这样的问题?
因为使用库的项目有很多个,不太能因为某个库的更新而要求所有使用该库的项目全部重新编译,通常,只是要告之用新版本替换相应的老版本库即可,而要达到这样的目的,就需要该库做到接口的二进制兼容。
典型的例子就是那些支持热插拔的plug-in系统,更新插件不需要其宿主也跟着要改变。
下面是不影响二进制兼容的安全做法:
增加新的class
增加non-virtual成员函数或者static成员函数
修改数据成员名称(二进制兼容,但会引起源代码级的不兼容,也容易产生问题)
增加新的全局函数(自由函数)
下面是一定影响二进制兼容的不安全做法(容易造成crash问题):
接口采用虚函数,并在接口中添加新的虚函数(影响了vtable[offset]),就算在尾部添加也是有问题的,因为不能保证该接口没有被继承
修改接口中使用的数据结构,如数据类型从short改为int(改变的内部布局)
为了保证库的可扩展性和二进制兼容,在设计库的接口时,可考虑下面的做法:
1. 用class的non-virtual成员函数作为接口,并且所有的操作放在一个内部class来代理实现
老版本:
1 2 3 4 5 6 7 8 9 10 |
|
新版本:
1 2 3 4 5 6 7 8 9 10 11 |
|
2. 使用全局函数作为接口(这种做法在C语言中是最为常见的),内部还是按C++的方式来实现
3. 不得以的情况下,可以使用虚函数做为接口
(全文完)
]]>“荷塘里有一片荷叶,它每天会增长一倍。假使30天会长满整个荷塘,请问第28天,荷塘里有多少荷叶?”
答案要从后往前推,即有四分之一荷塘的荷叶。
这时,如果站在荷塘的对岸,也会发现荷叶是那样的少,似乎只有那么一点点,但是,第29天就会占满一半,第30天就会长满整个荷塘。
在荷叶长满荷塘的整个过程中,荷叶每天变化的速度都是一样的,可是前期花了漫长的28天。那时,我们看到荷叶只能占领一个小小的角落。
在积累知识、实现自己梦想的过程中,即使我们每天都在进步,然而,前面那漫长的”28天”因无法享受到结果,常常令人难以忍受, 大多数人在这28天以前就放弃。人们常常只对”第29天”的希望与”第30天”的结果感兴趣,但是不愿忍受漫长的成功过程。
(以上内容来自酷啡生活,作者hellen)
本文尽量指出在不同情景下如何选择合适的Git命令来达到自己的目的,当然,由于Git太过强大、灵活,有时同一问题可以有多种不同的解决方案,这里不太可能全部列出,只会选择一些自己测试可用的方法。
文中会稍微谈一些Git内部实现的细节,但不会太具体,同时这些内容大部分是基于自己在学习过程中的一些理解,所以也不能保证相关的解释一定是正确的。
如发现有误,请告之,在此谢过!
Git仓库的创建通常有如下几种情况:
进入项目的根目录,直接执行git init
,这样就在根目录下创建了Git仓库,表现就是在根目录下多了一个隐藏的.git目录,它对应的就是git仓库。
如果项目已经进行了一段时间或者是之前的项目想使用git进行版本管理,这时要创建Git仓库,也是直接到项目的根目录下,执行git init
即可
如果项目是多人协作,通常需要一个中心服务器来协作多人之间的工作成果
在中心服务器上通常是创建一个裸仓库(没有工作目录),方法是执行在项目根目录下执行git init --bare
。
git clone
从中心服务器上获得该项目的git仓库我们在工作目录中进行工作,然后需要将工作区中文件的变化情况告诉git。git与其他的版本控制系统的区别在于如果工作区的文件改动过,则git会在提交前进行一次文件快照,记录当前工作目录下所有文件和文件夹的拓扑结构及内容。为了效率和节省空间,内容相同的文件在git仓库中只会有一个blob对象来保存其内容,对于变化的文件,git会在内部创建新的blob对象,而没有变化的文件则在快照中引用之前仓库中的blob对象,这样,从每一次的提交中我们就能够知道当时工作区中文件和目录的情况。而其他版本控制系统则是保存文件的差异。
在工作区,文件的状态一般有如下几种:
给工作区进行文件快照,使用git add
命令,常见用法有:
git add .
工作区的文件变动全部被添加到git的index file中git add -u
将工作区中已经纳入git仓库的文件变动添加到index file中,但新增加的文件不会被添加git add file
手动一个一个地将指定的文件添加到index file中要查看当前文件快照的内容,可以使用git ls-files --stage
命令来实现,它会列出index file中文件的blob对象id和文件名。
对工作目录进行文件快照后,其修改的文件并未真正纳入git仓库,需要使用git commit命令将其真正提交保存到git仓库中。
每一次提交,git都会要求输入相应的commit信息,这样也便于今后能够快速找到指定的版本。
如果已经对工作区进行了文件快照,则直接执行git commit -m "commit information"
, 这里用单引号或双引号都可以,如果这时没有加上-m选项,那么git会自动打开编辑器,让你输入提交信息,复杂的提交信息可以这样使用。
如果并没有对工作区进行文件快照,且只希望将已经纳入git管理的文件的变更提交到仓库中,这时,可以直接执行git commit -a -m 'commit information'
,实际上,这只是将git add 和git commit两条命令合并起来执行。
git会为每一次的提交产生一个commit对象,该commit对象指向了当前的文件快照(由tree对象和blob对象组成),并且还会指向其前一次的commit对象,以形成commit提交历史。
另外,git内部有一个HEAD始终会指向当前分支最新的一次commit对象,每一次提交,HEAD也会跟着移动。
要了解当前工作区中的文件有没有被修改或有没有增加或者删除文件,都可以通过git status
来查看,同时还能够了解到修改的文件没有被放入到index file中或者没有被提交到git仓库中。如果当前工作区中文件的变化都已经被提交到仓库中,则此时的工作区就是处于一个clean的状态。
对于软件开发,常常会遇到要修正bug、增加新功能等需求,为了不影响正常Release出去的版本,我们往往会希望自己修改的部分最好与之前的版本分开,这时候,使用分支是个不错的选择。在git中,分支是非常强大的,并且实现的成本的也很低,这是其他版本控制系统无法比拟的。灵活使用git的分支,可以帮助我们实现比以往更好、更简单的开发管理。
git branch
可以列出当前git仓库中已经存在的分支名;知道分支名,我们可以使用git checkout 分支名
将工作区切换到指定的分支,这样我们就可以在这个分支上进行开发或者修正bug等工作,同时,在该分支所作的改动不会影响到其他分支。
要创建新分支,可以实现git branch 新分支名
来创建,但此时并不会自动切换到新创建的分支,需要继续使用git checkout 分支名
来切换。
如果想在创建分支后自动切换到该分支的话,则可以使用git checkout -b 新分支名
。
上述的分支创建都是默认基于当前所在分支来创建新的分支,因此,创建并切换到新分支后工作区的文件和之前是一样的。
如果想基于某个指定的版本来创建分支,则需要在创建时明确地指出希望是基于哪个起点来建立新分支,这个起点可以是某个分支的最新commit,某次commit或者指定的tag等,即git checkout -b 新分支名 [分支的起点]
。
在切换分支时,如果当前所在分支的工作区有文件被改动,则必须将这些改动提交到仓库或者使用git stash将当前分支状态暂时保存起来,否则分支切换就会失败。
当开发分支的功能已经完成,则可以将最新成果合并到主分支或者release分支。
要合并开发分支,需要先将分支切换到主分支或release分支,然后执行git merge 开发分支名
来完成。
如果开发分支的修改是在主分支增加内容,不修改之前的内容(可以增加新文件但没有删除文件、同一文件没有修改之前的内容)时,这时合并应该是成功的(新增的部分自动合并在一起)且自动提交到git仓库中。如果同一文件在同一行的同位置有变动,则这时合并就发生的冲突(git会指示出哪个文件有冲突),自动合并过程被中止,这时就需要我们手动打开有冲突的文件(有冲突的部分可以git会在文件中加上特殊的符号标示出来,具体可以使用git help merge
查看相关文档),并自己决定如何两个分支的内容。冲突解决后,需要自己提交到仓库中。
如果冲突的内容比较多时,我们可以借助git mergetool
打开配置的工具来协作解决冲突。
分支合并完成后,我们可以使用git branch -d 分支名
删除不需要的分支,要使该命令执行成功,应保证要被删除的分支已经被其他分支合并,否则会失败。如果要强制删除,不管该分支有没有被合并,则应该使用git branch -D 分支名
,这种情况通常是用于删除那些试验失败的分支。
要查看分支,除了在Bash中使用git branch
来列出已存在的分支,还可以使用gitk
命令打开图形界面比较直观的查看各分支的提交历史和分支间的相互关系。
在git中,各种命令的执行会影响到工作区、暂存区、git仓库三者的状态。
如果发生了误操作,想撤销这个操作,并恢复到某个希望的状态时,这时就需要使用git reset
命令了。
如果工作区中的一些文件被修改,并且已经暂存到index中(还未提交),这时发现有些文件的修改还未真正完成,想撤销之前的git add并在工作区中保留这些文件之前的修改内容时,我们git reset --mixed HEAD
,这时index中内容和HEAD对应的index内容一致,但工作区还是我们修改后的状态,用git status
会显示这些文件的状态时已修改未暂存,注意此时提交并没有被撤销。(注意,–mixed是缺省的选项,因此git reset HEAD
和git reset --mixed HEAD
是等同的)
对于上述情况,如果仅仅是要index中撤销某个文件,可以使用git checkout -- file
来完成。
如果上述已经提交,想撤销最近一次的提交,则可以使用git reset --mixed HEAD^
,这时commit和index都被恢复到HEAD之前的一次提交状态,但工作区没有改变。
如果仅是想撤销最近一次的提交,但想保留当前index和工作区的状态,则使用git reset --soft HEAD^
,而git reset –soft HEAD执行后,commit、index、work tree的状态都没有改变,因此,没有实际意思。
如果想将commit、index、work tree的状态都恢复到前一个版本的状态,则可以使git reset --hard HEAD^
,这时要注意的是之前的所有修改被被丢弃,因此,这条命令使用时要确保当前的修改已经被提交到仓库中或者确定要放弃这些修改。另外,从这里也可以看出,使用git reset --hard 指定的版本ID
可以将指定的版本的code从仓库中取出来查看。
如果发现提交时,填写的提交信息有误,则可以使用git commit --amend
来修改;另外,如果发现提交时,漏掉某些文件,并希望将这些文件也追加到上次的提交中,可以先使用git add .
将这些文件先暂存起来,然后使用git commit -amend
来完成提交。
在git中,每一个提交在git仓库中都会有相应的历史记录,这样就便于我们今后在需要的时候来查看,例如,我们想知道某个版本对应的commit对象ID,这样我们就可以恢复到这个版本来查看这个版本的状态。
通过git log可以查看所有分支所有的提交历史信息。这些信息中,主要包括commit对象id、提交人的信息和提交时间、提交时附加上的提交信息等。
在经过一段时候后,可能分支上已经有很多次提交,这样git仓库中保留的历史也很多,因此,在查看时,我们需要经过一些过滤来选出我们需要的内容。
git log -p
会显示出每次提交做了哪些改动,git log -3
只列出最近三次的提交历史,git log --pretty=format:"%H : %s"
只列出每次提交的ID和提交信息,
git log --graph
会以图形化的方式显示各分支的提交历史,git log commit1..commit4
列出commit1和commit4之间的提交历史(不包括commit1,但包括commit4),git log commit1...commit4
列出commit1和commit4之间的提交历史(不包括commit1和commit4)。
上述的选项可以组合起来使用,如git log -5 --graph
就会以图形的方式显示最近5次的提交历史。
更多的git log的选项的使用方法请使用git help log来查看。
另外,用git show
可以查看指定的某次提交历史,通过git reflog
可以查看HEAD曾经指向的commit对象的ID。它们的详细用法请查看其帮助文档。
通过git shortlog -s -n
会显示出总的提交次数。
如果我们想看看指定版本的文件快照内容,则我们需要先知道该版本的commit对象的ID。
通过git log
或git reflog
来找出指定版本的commit对象的ID。
通过git cat-file -t ID
来确认该对象的类型:commit(提交)、tree(目录)、blob(文件)、tag(标签)。
通过git cat-file commit commit-ID
来查看commit对象的内容,主要包括:tree对象、前一个commit对象、提交人信息、提交附加信息等,其中,tree对象就是文件快照的根目录。
通过commit对象知道了文件快照根目录对应的tree对象,使用git ls-tree tree-id
来查看tree对象的内容,主要包括:其他tree对象、blob对象。
知道了blob对象,我们就可以通过git cat-file blob blob-id
来查看文件的内容。
note:git的这些内部对象都存储在.git/object目录下,其中,每个对象用其ID前2个字符作为存储该对象的文件夹名,并在该文件夹下使用其ID剩余的38个字符作为存储该对象的文件名,这个文件就是真正存储对象内容的地方,其中的内容时被压缩过的,需要使用上述的git命令来查看。如果执行过git gc,.git/object目录下的文件会被压缩存放到.git/object/pack和.git/object/info目录下。通过find可以列出.git目录下所有目录和文件,这样就可以清楚地知道当前仓库的目录结构。
要查看当前分支的index内容,使用git ls-files --stage
,它列出了文件名及对应的blob对象的ID。
git ls-files
根据不同的选项可以查看到文件,如将工作目录中的某些文件误删除了,这时可以使用git ls-files -d |xargs git checkout --
即可恢复这些被删除的文件。
通常情况下,每个release版本都会有一个版本号与之对应,在git中,我们可以将该版本号作为tag来代表某个正式的release版本。这样的方便之处在于,以后我们可以通过tag快速定位到指定的版本,而不需要通过在冗长的提交历史中慢慢查找。
通过git tag
可以列出仓库中所有的tag,要为当前分支最近一次的提交(HEAD)创建新的tag,使用git tag [-a] 标签名 -m "附加信息"
,这样以后就可以用该标签名来代替当前分支的这次提交commit对象的ID。
要删除某个tag,使用git tag -d 标签名
即可。
note:在想远程仓库push时,tag并不会自动被push到远程仓库中,需要自己手动去push,如git push origin v1.0.0.1
。
如果我们本地机器上没有某个项目的仓库,但中心服务器上已经有项目的仓库,我们可以用git clone 中心服务器上仓库地址 本地文件夹
来从中心服务器上克隆一份项目的仓库,并在工作目录中进行开发工作。
如果我们本地机器上已经有了项目的仓库,则要从服务器上抓取最新的内容,可以使用git pull
或者git fetch
来实现,区别是git pull会将服务器上抓回来的内容与本地分支进行合并,而git fetch则不会进行合并。
在我们本地完成工作,需要将最新的成果放到服务器上时,我们可以使用git push
来实现。
上述的操作都需要事先知道服务器上项目仓库的地址,并使用git remote add
保存在本地并起了个别名,这样以后就可以直接使用别名来代替服务器上项目仓库的地址。
在实际工作中,对比文件在不同版本中有何差异是经常发生的事情。
在git中,我们可以通过git diff
来比较工作区和index、index和git仓库、工作区和git仓库、不同版本同一文件等之间的差异。
git diff
比较了工作区与index的差异git diff HEAD
比较了工作区与仓库中最近一次的提交间的差异git diff --cached
比较了index与仓库中最近一次的提交间的差异git diff HEAD^ HEAD
比较了当最近的这次提交与上一次提交之间的差异git diff 指定的分支
比较了当前分支的HEAD与指定分支的HEAD之间的差异git diff 分支1..分支2
比较了分支1到分支2之间的变动更复杂的比较可以使用git difftool
打开配置的diff工具来进行对比,上述的选项或参数也同样适用于git difftool,如git difftool HEAD^ HEAD
。
如果正在一个develop分支上正在开发新功能,但这时master分支(稳定版本)突然发现了bug,并需要及时修复,而develop分支此时的工作还没有完成,且不希望将之前的工作就这样提交到仓库中时,这时就可以用git stash来暂时保存这些状态到Git内部栈中,并用当前分支上一次的提交内容来恢复工作目录,然后切换到master分支进行bug修复工作,等修复完毕并提交到仓库上后,再使用git stash apply [stash@{0}]
或者git stash pop
将工作目录恢复到之前的状态,继续之前的工作。
同时也可以多次使用git stash
将未提交的代码压入到Git栈中,但当多次使用’git stash’命令后,Git栈里将充满了未提交的代码,这时候到底要用哪个版本来恢复工作目录呢?git stash list
命令可以将当前的Git栈信息打印出来,我们只需要将找到对应的版本号,例如使用git stash apply stash@{1}
就可以用版本号为stash@{1}的内容来恢复工作目录。
当Git栈中所有的内容都被恢复后,可以使用git stash clear
来将栈清空。
假设master和develop是一个项目的两个分支,其中master是主分支,develop是从master而来的开发分支,如果在develop分支上提交过2次,之后又切换到master分支,做了一些修改并提交2次,这时,如果想将master分支的最新修改内容合并到develop分支,但同时也不能影响master分支时,就需要使用git rebase了,这时的上游分支为master。
1 2 3 4 5 6 7 8 9 |
|
如果找到某个版本出现了问题,而之前的版本没问题,我们可以用git blame找出文件是何时被何人怎么修改的。
git blame [-L 行号1, 行号2] file
git archive --format=zip -o arch.zip HEAD
或 git arch --format zip head>arch.zip
只备份了当前工作区的所有文件,不包括.git目录,会在工作目录中生成一个arch.zip文件。
如果我们发现有个问题在某些版本没问题,而在有些版本有问题时,我们可以借助git bisect来帮助我们定位问题。
git bisect start
git bisect good commit-id1
git bisect bad commit-id2
这时,git会按照二分法找出good版本和bad版本中间的那个提交版本,并自动将工作状态切换到那个版本,这时我们可以验证这个版本是不是有问题,如果有问题,通过git bisect bad
告诉git,这时git会继续找出一个中间版本让我们来验证,直到我们找出,并通过git bisect good
告诉git为止。
这样一步一步我们就可以找出引入问题的版本,最后,我们可以使用git bisect reset
结束查找,git会删除查找过程中在仓库中生成的临时文件,并将状态恢复到。
执行git gc
,git会帮助我们清除仓库中垃圾,释放一些空间,并以pack的压缩方式存储对象内容,其中,.git/refs目录中内容和.git/objects目录下的对象文件会被压缩存放到.git/objects/pack目录下,而在.git/objects/info目录下会有一个packs文件用于指向.git/objects/pack目录下的一个pack文件,而这个pack文件应该是存放压缩分支、tag等信息后的文件。
(全文完)
]]>刚好,这几天x64移植项目告一段落,有了点空闲时间,想想还是系统地去学习一下Git吧。
当然,学习Git,我也希望在今后的开发中能够用Git来管理自己的Code,结束之前那种最原始的、靠每天备份的笨方法。
关于Git的教程,网上有不少,感觉用的人也挺多的,所以一般的资料和问题解决方法基本通过Google都能够获得。
这里,关于Git的历史、原理等不会涉及太多,主要是从自身日常管理Code这个角度去谈谈如何使用Git管理代码,让自己先达到能够熟练Git这个目标。
我学习Git的起点是从阅读Pro Git开始,感觉各种概念讲解的还是蛮清楚的,涉及Git的很多方面,是一份很不错的资料。
另外,git community book和Git Magic也值得参考一下。
还有一份Git使用指南 作为使用Git管理Code的入门资料也是很不错的。
我学习和使用Git的平台是Windows,所以安装一下msysGit提供的安装文件即可,我安装的是Git-1.7.7-preview20111014.exe,不过目前已经更新到Git 1.7.10了。
安装完,就可以通过命令行工具Git Bash来使用Git了,我使用Git大多数时候是通过命令行的方式,当然在Windows下也支持图形界面的方式来使用Git,使用Git GUI就可以了。
不过,在Git GUI里有些特殊的功能可能没有支持,需要这些功能还是要切换到Git Bash中。
该工具还集成到了Windows的资源管理器中,在文件夹上右键,可以点击Git Bash Here和Git GUI Here快速启动Git并自动切换到指定的文件夹,方便了不少。
但有时,不知怎么突然无法通过该方法启动Git,上Google查了一下,发现是与Git相关的注册表项被修改了,可能是我使用了注册表清理工具造成的吧?
知道了原因,修复的方法也很简单,将下面的内容复制到一个文本文件中,保存后,将文件的扩展名修改为.reg,双击注册即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
值得一提的是,如果开发工具是MS Virtual Studio的话,则有另外一个Git图形界面工具Git Extension可以使用,安装之后会集成到VS2005、VS2008、VS2010中,当然脱离MS Virtual Studio也可以单独使用,其图形界面的功能比上述的Git GUI要强大不少;同时,也集成到Windows资源管理器中,可以通过在文件夹上右键进入。
为了更深入地了解和掌握Git,下面基本是采用Git命令才演示各种操作。
Git作为一个版本控制软件,相比其他版本控制软件有什么不同呢?
Git是一个分布式的版本控制系统,一般来讲,各个Git仓库没有主次之分;
大多数的操作可以在本地完成,事后方便时,再推送到中心服务器的仓库中;
采用“直接记录快照,而非差异比较”的版本控制策略,内部只关心文件数据的整体是否发生改变,而不是文件内容的具体差异(Git内部被实现为一种微型的文件系统);
Git工作时就是在工作目录(工作区、work tree)、暂存区(索引、index file)、本地仓库三者之间管理文件的变化情况,Git会监视工作目录中的文件变化(增加新文件,删除文件,修改文件等),需要我们自己手动将变化的文件添加(git add)到暂存区中(这就是文件快照),然后再提交(git commit)到本地仓库中;上述过程,涉及Git内部的三种对象:commit对象、tree对象和blob对象,blob对象会对应的文件快照中那些变化的文件内容,tree对象记录了文件快照中各个目录和文件的结构关系,从概念上讲,tree对象和blob对象组成了文件快照,commit对象则记录了这次要提交到本地仓库的文件快照,同时也会指向上次的commit对象,它也是Git内部进行版本控制的重点(Git内部会记录各个commit对象,并用HEAD来指示当前分支中最新的commit对象),很多重要的功能,如分支、版本回溯、Git仓库内部状态等都是在commit对象基础上实现的;上述的每一个对象都对应一个独一无二的ID,该ID是一个由40个字符组成的哈希码,由SHA-1算法计算而来;Git能够通过ID的前几个字符就识别出对应的对象;
Git的分支功能很强大,很灵活,切换速度非常快,并且实现成本很低,这也是Git比其他版本控制软件要受欢迎的原因之一;
Git拥有丰富的、功能强大的命令,一个命令通过配置不同的选项可以实现不同的功能;要学好Git,关键就是要掌握这些命令,并灵活使用它们;
Git中有许多命令,并且每种命令都有一些功能选项可被选择,因此,在不熟悉Git这些命令的时候,查阅这些命令的使用说明是不错的选择。
要查询Git命令,可以使用git help 命令或者git 命令 –help的方式,它会自动打开浏览器来查看。
git init
在项目的开始,必须使用该命令来创建和初始化Git仓库,它会在项目的文件夹下生成一个隐藏的.git文件夹,这就是这个项目的Git本地仓库,后面所有的Git命令操作都是针对该文件夹里的内容。执行过该命令后,原来的文件夹就成为了Git的工作目录。
Note:在一个已经初始化过的文件夹下再次执行git init
,Git并不会将之前的.git文件夹的内容清除,这应该是Git的一种保护。
.git文件夹的初始组成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
初始状态下,Git默认处于master分支,HEAD文件的内容为ref: refs/heads/master,但在refs/heads目录下却没有master文件;而objects文件夹下则没有文件。
在有过一次提交后,.git文件夹就会产生变化,如增加了logs文件夹,里面记录了git各种操作产生的log,我们通过git命令,如git log, git show, git reflog等可以查看这些log内容; 产生了一个index文件,这就是暂存区对应的文件; objects文件夹下新增了很多文件夹和文件,它们实际就是文件快照(tree对象和blob对象)存放的地方; refs/heads文件夹下这时生成了master文件,其内容就是master分支最新commit对象对应的ID;如果有其他分支,则在refs/heads文件夹下也会生成以分支名命名的文件,里面存储着该分支最新commit对象的ID。
git add .
在Git中,工作目录下文件的状态可以分为已跟踪和未跟踪两大类状态,其中,已跟踪的文件是指已经被提交到git仓库的那些文件,而未跟踪的文件是指还没被提交到Git仓库中的那些非Git忽略的文件(Git可以通过在项目根目录下产生一个.gitignore文件,在里面指定要忽略的文件类型,这样Git就不会去监视这些文件的变化),如果工作目录中已跟踪的文件被修改或者删除,或者有新的文件(包括非空文件夹)加入,则通过git status
可以查看到Git监视到文件变化情况,然后通过git add .
做一次文件快照,并将其存储到暂存区(index文件)中,等待被提交到Git仓库中。
git commit -m '本次提交文件变化的描述信息'
如果工作目录中的文件变化已经被暂存(也可以同git status
来查看),则说明这次的文件快照可以被提交到仓库中,并一直保存。
提交时,需要添加一些信息,这里最好要将这次的文件变化情况描述清楚,以便以后在版本回溯时能够了解到各版本之间的差别。
如果工作目录中仅是已跟踪的文件被修改或被删除,则可以不用先git add .
,直接使用git commit -am "描述信息"
即可。
git status
在git命令执行后,要养成通过git status
查看git状态的习惯,以便及时了解文件变化的情况。通过git status
可以知道文件的状态(已修改未暂存、已删除、已修改并已暂存等待提交、未跟踪)。
git log
通过git log
可以查看当前分支的所有提交历史,知道每次提交的commit对象的ID以及提交时附加的描述信息等。要显示更多的信息,需要使用其支持的选项,如git log -p
可以将每次提交的文件变化也显示出来。
Note : git log
显示的内容可能会比较多,但git bash上显示不下时,最下面会有一个冒号:,指示还有更多的内容,这是通过上下箭头就可以选择内容进行查看,要退出按q键即可,要查看其他命令,按h键。
git show commit-id
通过git log
可以显示整个提交历史,而通过git show commit-id
则可以查看指定的某次提交内存,当然git show -all
也可以显示出提交历史,另外还可以格式化显示内容。具体请查看其help。
Note : commit-id可以是commit对象对应的ID,也可以是HEAD,分支名,tag等。
git diff
是比较工作目录与暂存区的差异git diff HEAD
则是比较工作目录与仓库中最近一次的提交间的差异git diff --cached
比较了暂存区与仓库中最近一次的提交间的差异。Git一个比较吸引人的功能就是其强大的分支和合并功能。初始状态下,Git默认的分支为master。
git branch
可以查看目前Git仓库中已有的分支;git branch 新分支名 [分支起点]
,没有分支起点的话,则默认在当前分支的最新的提交上创建分支git checkout 分支名
git checkout -b 新分支名 [分支起点]
git checkout master
git merge 要被合并的分支名
,合并过程中如果发生冲突则需要自己手动解决冲突,然后再提交。有冲突时,Git会显示哪个文件有冲突,并在冲突的文件中加上特殊的标识符号,解决完冲突后,要手动去掉这些被添加的标识符号。如果冲突比较复杂的话,最好使用其他工具来协助,通过git mergetool来启动。冲突一般是在不同的分支上对同一文件的同一位置内容进行了改动,并已提交到仓库中,这样在合并的时候就会发生冲突。git branch -d 要删除的分支
名,如果分支没有被合并过,该命令会执行失败git branch -D 要删除的分支名
gitk
充分利用好分支,可以帮助我们进行很好的版本控制与管理,如何用好分支其实是门艺术。
基于分支的版本控制模型有一篇文章进行了很好的阐述。
A succeddful Git branching model
中文翻译:Git分支管理是一门艺术
标签可以在需要的地方,为某个提交对象创建别名,这样以后我们就可以通过标签来查看一些信息,创建分支等。
git tag
git tag 标签名
git tag -a 标签名 -m '附加信息'
git show 标签名
git tag -d 标签名
Git相比其他版本控制软件的一个优点就是大多数的操作都可以在本地进行,而不用管远程的仓库,因为操作是在本地,且操作的数据也是在本地,所以执行的速度就会比较快。 在多人协作的项目中,就需要涉及与远程仓库交互的问题,主要是如何从远程仓库抓取最新数据合并到自己的本地分支上,将自己的最新成果分享给其他人或让别人审查等 。
git remote
仅显示已添加的远程仓库名,git remote -v
可以一并查看远程仓库的地址git remote add 远程仓库名 远程仓库地址
git remote rm 远程仓库名
git remote rename 原名 新名
git clone 远程仓库地址 [克隆到指定文件夹]
git fetch 远程仓库名
git pull 远程仓库名 本地要合并的分支名
git push 远程仓库名 本地分支名
git remote show 远程仓库名
git push 远程仓库名 标签名
, 默认Git是不会将标签推送到远程仓库的通过git reflog
可以帮助我们获得将工作目录恢复到某个状态所需的ID(可以用HEAD@{数字}来表示对应的ID)。
有时候,由于我们的误操作,产生了一些错误,我们发现后希望能够及时纠正这些因为误操作而产生的结果,将工作目录恢复到某个正常状态。
git checkout -- filename
或git reset HEAD
,修改的文件会被恢复到上次提交时的状态,修改的内容会丢失git checkout 分支名或标签名
;
[方法2] 先通过git reflog
找到某个版本的commit-ID,然后用git reset --hard commit-ID
将工作目录的文件恢复到指定的版本git checkout -- filename
或 git checkout -f
或 git ls-files -d | xargs git checkout --
git stash
git stash list
git stash apply
git stash pop
git stash clear
如果正在一个develop分支上正在开发新功能,但这时master分支(稳定版本)突然发现了bug,并需要及时修复,而develop分支此时的工作还没有完成,且不希望将之前的工作就这样提交到仓库中时,这时就可以用git stash来暂时保存这些状态到Git内部栈中,并用当前分支上一次的提交内容来恢复工作目录,然后切换到master分支进行bug修复工作,等修复完毕并提交到仓库上后,再使用git stash apply [stash@{0}]
或者git stash pop
将工作目录恢复到之前的状态,继续之前的工作。
同时也可以多次使用git stash
将未提交的代码压入到Git栈中,但当多次使用’git stash’命令后,Git栈里将充满了未提交的代码,这时候到底要用哪个版本来恢复工作目录呢?git stash list
命令可以将当前的Git栈信息打印出来,我们只需要将找到对应的版本号,例如使用git stash apply stash@{1}
就可以用版本号为stash@{1}的内容来恢复工作目录。
当Git栈中所有的内容都被恢复后,可以使用git stash clear
来将栈清空。
git bisect
git gc
git archive --format=zip -o arch.zip HEAD
或 git arch --format zip head>arch.zip
git rebase 上游分支名
假设master和develop是一个项目的两个分支,其中master是主分支,develop是从master而来的开发分支,如果在develop分支上提交过2次,之后又切换到master分支,做了一些修改并提交2次,这时,如果想将master分支的最新修改内容合并到develop分支,但同时也不能影响master分支时,就需要使用git rebase了,这时的上游分支为master。
1 2 3 4 5 6 7 8 9 |
|
git shortlog -s -n
会显示出总的提交次数。
在我们将文件提交到Git仓库后,我们可以通过每次的commit对象的ID来查看文件快照的内容。
具体的方法就是:
先通过git log
查看提交历史,选择需要查看的commit-id
git cat-file -t id
可以知道拥有该ID的对象是属于哪种类型:commit、tree、blob
git cat-file commit id
可以查看到该commit对象指向的tree对象的ID
git ls-tree tree-id
可以查看该tree中的blob对象的ID和其他tree对象的ID(如果有)
git cat-file blob blob-id
通过git ls-files --stage
可以查看当前分支的index文件中有哪些文件,它列出了文件名及对应的blob对象的ID。
find
可以列出.git目录下所有目录和文件,这样就可以清楚地知道当前仓库的目录结构。
git blame filename
可以列出该文件每次被修改的时间和内容。
mkdir
rmdir
ls
cat
echo "hello" >> file.txt
上述的这些命令应该能够帮助我们实现多数的版本控制需求,当然其中的每一个命令都会有一些其他的选项功能这里没有提到,希望在以后使用Git的过程中能够慢慢发掘,感受Git的强大!
很多内容是基于自己的理解,如有误请指正。
]]>32位的filter移植到x64平台上,对于没有汇编的filter,工作比较简单,主要就是编译选项的设置和修改指针与整形数相互强制转换的地方,以及部分数据类型不匹配等。
对于从VC6转换到VS2005的Project,其中x64编译选项主要注意以下几个地方:
C/C++->Preprocessor->Preprocessor Definitions : 检查或移除、添加编译器预定义常量;对于Release版的filter,一般会包含WIN32;NDEBUG;WINDOWS;USRDLL;UNICODE版本要添加UNICODE;
C/C++->Code Generation->Runtime Library : 对于Release版本这里可能选择Multi-threaded(/MT)或者Multi-threaded DLL(/MD),如果选择Multi-threaded(/MT),如果编译报错则在Linker->Input->Addtional Dependencies中增加MSVCRT.LIB试试;
C/C++->Optimization->Whole Program Optimization : 如果该选项为No,则用/MT编译出来的文件会比用/MD编译出来的文件大很多,因此,Release版本,该选项要设置为Enable link-time code generation(/GL),不过对于Debug版本,该选择一般设为No,否则编译器有可能会报/GL与/ZI冲突;
Linker->Input->Addtional Dependencies : 至少包括 strmbase.lib strmiids.lib winmm.lib,其中 strmbase.lib根据编译环境(是不是预定义了UNICODE或Release版本/Debug版本)的不同可能为ustrmbase.lib, strmbasd.lib或 ustrmbasd.lib;
Linker->Input->Ignore All Default Libraries : 要选择No,否则编译可能会报错;
Linker->Advanced->Entry Point : 如果填的是DllEntryPointer@12,则在x64下编译器会报错,win32下则不会报错(建议不要这样设定,而应该在代码中指定DLL入口为DllMain,并在DllMain中调用DllEntryPoint即可);在x64下不要填;
Linker->Advanced->Base Address : 在x64下不要填,win32下如果Linker->Advanced->Entry Point填了DllEntryPointer@12,则这里通常设为0x1c400000;(建议不要这样设定)
指针部分重点是要检查使用回调函数的地方,在Win32下,指针和int/long转换是可以的,因为都是32位长度,而在x64下,指针的长度为64位,而int/long仍是32位,这样原有的转换在x64下就会出现问题。在这些地方,要将int/long替换为ULONG_PTR,ULONG_PTR在Win32下为32位,而在x64下则为64位。
不对编译器报出的关于数据类型不匹配的Warnings,要检查并确认不会发生溢出或者截断,没有改变原有的值的情况下可以不用修改。当然,最好Coding的过程中就要避免数据类型可能存在的不匹配问题,否则出现问题时,查找原有就会麻烦了。
对于有汇编的filter,移植64位版本,除了上述的问题,还涉及汇编代码的修改。
在VS2005 x64中不再支持内联汇编,编译器不再认识__asm关键字。
所以,对于内联汇编的代码,要将函数提取到单独的.asm文件中,并修改函数参数传递、局部变量的分配及堆栈、寄存器管理。这部分可参看上篇内容。
对于寄存器,在x64平台中,原来的32位寄存器被扩展到64位,并新增了8个64位的通用寄存器R8~R15,另外还增加了8个128位的XMM寄存器XMM8~XMM15。
同时,原来的32位通用寄存器仍然可以使用,但32位寄存器和新的64位寄存器混合使用时,要注意相互赋值的地方,应该要匹配。
另外,在内存访问的地方,x64中必须要通过PTR指明数据的类型,否则编译器会报错;而在Win32下则可以省略。
下面是ARGB转AYUV的一部分x64汇编代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
|
由于平时的开发环境是Windows + VS2005,所以,下面的内容也主要是讨论VS2005下64位编程的一些主要事项。不过,对于其他平台下的64位编程也有参考价值。
之前已经讲过如何搭建VS2005下64位编程环境,看这里。
Windows 32位平台下使用的是ILP32模型,而Windows 64位平台下使用LLP64模型。
在LLP64模型中,只有指针为64位,其余的类型则保证和32位平台一致:
1 2 3 4 |
|
Windows平台为开发者提供了很多的数据类型别名,合理使用这些数据类型为我们编写32位和64位共用代码是有帮助的。
长度固定的类型: INT32、UINT32、LONG32、ULONG32、DWORD32、INT64、UINT64、LONG64、ULONG64、DWORD64
随平台长度变化的类型(32位平台->32位,64位平台->64位): INT_PTR、UINT_PTR、LONG_PTR、ULONG_PTR、DWORD_PTR、SIZE_T、SSIZE_T、HALF_PTR、UHALF_PTR
指针: POINTER_32、POINTER_64
由于数据类型模型的改变,因此,如果原代码中有涉及指针与整数类型之间相互转换(如在设置回调函数指针时,通常会将原模块的this指针作为long型保存起来),则这部分代码必须要改变(将long改为ULONG_PTR),即任何在代码中有假设指针与整数类型位数相同的代码在64位下都会有问题。
涉及指针运算的地方,要检测偏移量是否可能会溢出的问题。(偏移量是通过无符号数和有符号数运算得来的,特别容易出现问题)
另外,64位系统的API函数参数的数据类型可能发生了改变,如LPARAM,WPARAM,LRESULT在32位平台下是32位整型,而在64位平台下则为64位整型,这也是在移植过程中需要注意的地方,防止出现数据截断。
如果API或者自己代码中使用了size_t和ptrdiff_t,要注意它们的长度在32位平台为32位,在64位平台则是64位,这是特别容易出现数据截断的地方。
函数重载(多态)也要留意是否是仅依靠参数的数据类型来区分的。
代码中魔术数也是要check的地方,如果是魔术数是用来假设数据类型的size时,最好要使用sizeof来确定数据类型的size; 另外,魔术数不管是在32位平台还是在64位平台都是被当作一个32位的整型数来处理,特别要留意将-1作为错误码的地方,在32位平台下为0xFFFFFFFF, 而在64为平台下则应该为0xFFFFFFFFFFFFFFFF。如果代码中使用了0xFFFFFFFF这个整型数,那么在32位平台下为-1, 但在64位下却是一个很大的值(0x00000000FFFFFFFF)。因此,代码中有移位操作和使用MASK的地方,也是容易出现数据溢出的地方。
在代码中,打印整型数和指针的地方也需要注意,打印UINT_PTR整数将%u改为%Iu, 对于指针使用%p。
在VS2005下64位编程不再支持内联汇编,汇编代码需要提取到一个单独的.ASM文件中。
编写64位平台汇编代码需要注意以下几个与32位平台汇编的不同的地方:
[32位] EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP -> [64位] RAX、RBX、RCX、RDX、RSI、RDI、RBP、RSP
可以在64位程序中调用32位的寄存器,如RAX(64位)、EAX(低32)、AX(低16位)、AL(低8位)、AH(8到15位), 相应的有R8、R8D、R8W和R8B,不过不要在程序中使用如AH之类的寄存器
增加了以下寄存器:R8 ~ R15 XMM8 ~ XMM15
x64平台函数calling convention与x86平台函数calling convention是不同的。
在x86平台下,函数调用约定有:__cdecl、__stdcall、__fastcall、__thiscall
等,而x64下的调用约定只作如下限制:
对于浮点参数,前4个参数将传入XMM0到XMM3的寄存器,后续的浮点参数也是通过堆栈传递。
即使参数可以是通过寄存器传递,但在堆栈上仍需为其预留空间,每个函数至少要在堆栈上预留32个字节(为前4个参数预留空间), 该空间允许将传递函数函数的寄存器轻松地复制到堆栈中。
当然,如果要传递4个以上的参数,则必须为其预额外的堆栈空间。
注意,被调用函数中有局部变量和保存其他寄存器时,其空间是由被调用函数来分配,并在结束时由自己去回收这部分堆栈空间
1 2 3 4 5 6 7 8 |
|
如int foo(int a, int b, int c, int d, int e, int f) { int i, j; return 0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|