标题: linux git常见命令整理 [打印本页] 作者: compare2000 时间: 2014-04-22 15:34 标题: linux git常见命令整理 git常见命令整理
【基本概念】
A) git本地由三个部分组成:工作区 和 暂存区 和 本地库。
B) .git目录才是仓库的灵魂,.git目录里面包括 暂存区 和 本地库,.git目录外的地方是工作区。
C) git中包含4类对象 (commit 提交对象, tree 目录, blob 文件, tag 标记)
【git模型】
工作区 = working directory
暂存区 = snapshot/stage/index
本地库 = local repo
【.git目录】
HEAD 指向当前分支
config 项目配置文件
description 供GitWeb程序使用
hooks/ 客户端与服务端钩子脚本
info/ 忽略模式
index 暂存区域信息
objects/ 所有数据内容
refs/ 指向所有commit的指针
#if defined(__STDC__) || defined(WIN32) || defined (hpux)
#define dsproto(x) x
#else
#define dsproto(x) ()
#endif
DllImport int DSServerMessage(const char *MsgIdStr, const char *DefMsg, const char *Prms[10], char *pMessage, int SizeMessage);
DllImport int DSCloseJob dsproto((DSJOB));
删所有文件
#!/bin/bash
count=0
SCANFILE()
{
local dir
local FILES
dir=$1
cd ${dir}
for FILES in `ls -l | awk '{print $8}'`
do
#屏蔽掉当前目录和上一级目录以及.svn,便于遍历#
if [ "." = "${FILES}" -o ".." = "${FILES}" -o ".svn" = "${FILES}" ]
then
echo "the file is ${FILES}"
continue
fi
#如果是目录进入并且遍历E
if [ -d ${FILES} ]
then
pwd
SCANFILE ${FILES}
continue
fi
判断依据:
1.具有相同core id的cpu是同一个core的超线程。
2.具有相同physical id的cpu是同一颗cpu封装的线程或者cores。
英文版:
1.Physical id and core id are not necessarily consecutive but they are unique. Any cpu with the same core id are hyperthreads in the same core.
2.Any cpu with the same physical id are threads or cores in the same physical socket.
/proc/cpuinfo 文件包含系统上每个处理器的数据段落。/proc/cpuinfo 描述中有 6 个条目适用于多内核和超线程(HT)技术检查:processor, vendor id, physical id, siblings, core id 和 cpu cores。
processor 条目包括这一逻辑处理器的唯一标识符。
physical id 条目包括每个物理封装的唯一标识符。
core id 条目保存每个内核的唯一标识符。
siblings 条目列出了位于相同物理封装中的逻辑处理器的数量。
cpu cores 条目包含位于相同物理封装中的内核数量。
如果处理器为英特尔处理器,则 vendor id 条目中的字符串是 GenuineIntel。
1.拥有相同 physical id 的所有逻辑处理器共享同一个物理插座。每个 physical id 代表一个唯一的物理封装。
2.Siblings 表示位于这一物理封装上的逻辑处理器的数量。它们可能支持也可能不支持超线程(HT)技术。
3.每个 core id 均代表一个唯一的处理器内核。所有带有相同 core id 的逻辑处理器均位于同一个处理器内核上。
4.如果有一个以上逻辑处理器拥有相同的 core id 和 physical id,则说明系统支持超线程(HT)技术。
5.如果有两个或两个以上的逻辑处理器拥有相同的 physical id,但是 core id 不同,则说明这是一个多内核处理器。cpu cores 条目也可以表示是否支持多内核。
判断CPU是否64位,检查cpuinfo中的flags区段,看是否有lm标识。
Are the processors 64-bit?
A 64-bit processor will have lm ("long mode" in the flags section of cpuinfo. A 32-bit processor will not.
.git包含的目录含义
UPDATE: I’ve recieved some very helpful comments regarding corrections to the various files and what they do. Thanks for letting me know and keeping me on my toes.
One of the things I like best about Git is that it keeps all of its information in one place: your .git directory in your project’s root. If you haven’t been digging around it yet, don’t fret! There’s plenty of goodies to be had within it. Let’s take a look into the important files and folders and try to get a better sense of what’s going on under the hood.
The basic structure looks like this:
.|-- COMMIT_EDITMSG|-- FETCH_HEAD|-- HEAD|-- ORIG_HEAD|-- branches|-- config|-- description|-- hooks| |-- applypatch-msg| |-- commit-msg| |-- post-commit| |-- post-receive| |-- post-update| |-- pre-applypatch| |-- pre-commit| |-- pre-rebase| |-- prepare-commit-msg| `-- update|-- index|-- info| `-- exclude|-- logs| |-- HEAD| `-- refs|-- objects`-- refs |-- heads |-- remotes |-- stash `-- tagsLet’s go over some of the normal files that you may see living in the base directory:
•COMMIT_EDITMSG: This is the last commit’s message. It’s not actually used by Git at all, but it’s there mostly for your reference after you made a commit.
•config: Contains settings for this repository. Specific configuration variables can be dumped in here (and even aliases!) What this file is most used for is defining where remotes live and some core settings, such as if your repository is bare or not.
•description: If you’re using gitweb or firing up git instaweb, this will show up when you view your repository or the list of all versioned repositories.
•FETCH_HEAD: The SHAs of branch/remote heads that were updated during the last git fetch
•HEAD: The current ref that you’re looking at. In most cases it’s probably refs/heads/master
•index: The staging area with meta-data such as timestamps, file names and also SHAs of the files that are already wrapped up by Git.
•packed-refs: Packs away dormant refs, this is not the definitive list of refs in your repository (the refs folder has the real ones!) Take a look at gitster’s comment to see more information on this.
•ORIG_HEAD: When doing a merge, this is the SHA of the branch you’re merging into.
•MERGE_HEAD: When doing a merge, this is the SHA of the branch you’re merging from.
•MERGE_MODE: Used to communicate constraints that were originally given to git merge to git commit when a merge conflicts, and a separate git commit is needed to conclude it. Currently --no-ff is the only constraints passed this way.
•MERGE_MSG: Enumerates conflicts that happen during your current merge.
•RENAMED-REF: Still trying to track this one down. From a basic grep through the source, it seems like this file is related to errors when saving refs.
There’s plenty of directories as well:
•hooks: A directory that will fast become your best friend: this contains scripts that are executed at certain times when working with Git, such as after a commit or before a rebase. An entire series of articles will be coming about hooks.
•info: Relatively uninteresting except for the exclude file that lives inside of it. We’ve seen this before in the ignoring files article, but as a reminder, you can use this file to ignore files for this project, but beware! It’s not versioned like a .gitignore file would be.
•logs: Contains history for different branches. Seems to be used mostly with the reflog command.
•objects: Git’s internal warehouse of blobs, all indexed by SHAs.
•rebase-apply: The workbench for rebasing and for git am. You can dig into its patch file when it does not apply cleanly if you’re brave.
•refs: The master copy of all refs that live in your repository, be they for stashes, tags, remote tracking branches, or local branches.
Just one word of wisdom when messing around with Git internals: make sure you know what you’re doing, and if not, have a backup! Messing around with the config files or hooks is pretty simple but I wouldn’t go spelunking in the datastore if I didn’t have to. If for some reason you are as part of your normal workflow, you might be doing it wrong.
There’s plenty more about the internals of Git that we haven’t covered yet. One of the best guides is the Git Community Book, and of course, you can just download the source for yourself and take a look. If you have more information about what’s going on in the .git folder, let us know in the comments! 作者: compare2000 时间: 2014-05-22 16:44
Git详解二: Git基础
Git 基础
读完本章你就能上手使用 Git 了。本章将介绍几个最基本的,也是最常用的 Git 命令,以后绝大多数时间里用到的也就是这几个命令。读完本章,你就能初始化一个新的代码仓库,做一些适当配置;开始或停止跟踪某些文件;暂存或提交某些更 新。我们还会展示如何让 Git 忽略某些文件,或是名称符合特定模式的文件;如何既快且容易地撤消犯下的小错误;如何浏览项目的更新历史,查看某两次更新之间的差异;以及如何从远程仓库 拉数据下来或者推数据上去。
像之前说的,暂存 benchmarks.rb 后再编辑,运行 git status 会看到暂存前后的两个版本:
$ git add benchmarks.rb$ echo '# test line' >> benchmarks.rb$ git status# On branch master## Changes to be committed:## modified: benchmarks.rb## Changed but not updated:## modified: benchmarks.rb#现在运行 git diff 看暂存前后的变化:
# Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: README # modified: benchmarks.rb ~ ~ ~ ".git/COMMIT_EDITMSG" 10L, 283C可以看到,默认的提交消息包含最后一次运行 git status 的输出,放在注释行里,另外开头还有一空行,供你输入提交说明。你完全可以去掉这些注释行,不过留着也没关系,多少能帮你回想起这次更新的内容有哪些。(如果觉得这还不够,可以用-v 选项将修改差异的每一行都包含到注释中来。)退出编辑器时,Git 会丢掉注释行,将说明内容和本次更新提交到仓库。
另外也可以用 -m 参数后跟提交说明的方式,在一行命令中提交更新:
$ git commit -m "Story 182: Fix benchmarks for speed"[master]: created 463dc4f: "Fix benchmarks for speed" 2 files changed, 3 insertions(+), 0 deletions(-) create mode 100644 README好,现在你已经创建了第一个提交!可以看到,提交后它会告诉你,当前是在哪个分支(master)提交的,本次提交的完整 SHA-1 校验和是什么(463dc4f),以及在本次提交中,有多少文件修订过,多少行添改和删改过。
如果只是简单地从工作目录中手工删除文件,运行 git status 时就会在 “Changed but not updated” 部分(也就是_未暂存_清单)看到:
$ rm grit.gemspec $ git status # On branch master # # Changed but not updated: # (use "git add/rm ..." to update what will be committed) # # deleted: grit.gemspec #然后再运行 git rm 记录此次移除文件的操作:
$ git rm grit.gemspec rm 'grit.gemspec' $ git status # On branch master # # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # deleted: grit.gemspec #最后提交的时候,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f(译注:即 force 的首字母),以防误删除文件后丢失修改的内容。
$ git mv README.txt README $ git status # On branch master # Your branch is ahead of 'origin/master' by 1 commit. # # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # renamed: README.txt -> README #其实,运行 git mv 就相当于运行了下面三条命令:
$ git log commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon Date: Mon Mar 17 21:52:11 2008 -0700 changed the version number commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40:33 2008 -0700 removed unnecessary test code commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon Date: Sat Mar 15 10:31:28 2008 -0700 first commit 默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面。看到了吗,每次更新都有一个 SHA-1 校验和、作者的名字和电子邮件地址、提交时间,最后缩进一个段落显示提交说明。
git log 有许多选项可以帮助你搜寻感兴趣的提交,接下来我们介绍些最常用的。
我们常用 -p 选项展开显示每次提交的内容差异,用 -2 则仅显示最近的两次更新:
$ git log -p -2 commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon Date: Mon Mar 17 21:52:11 2008 -0700 changed the version number diff --git a/Rakefile b/Rakefile index a874b73..8f94139 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ require 'rake/gempackagetask' spec = Gem::Specification.new do |s| - s.version = "0.1.0" + s.version = "0.1.1" s.author = "Scott Chacon" commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40:33 2008 -0700 removed unnecessary test code diff --git a/lib/simplegit.rb b/lib/simplegit.rb index a0a60ae..47c6340 100644 --- a/lib/simplegit.rb +++ b/lib/simplegit.rb @@ -18,8 +18,3 @@ class SimpleGit end end - -if $0 == __FILE__ - git = SimpleGit.new - puts git.show -end \ No newline at end of file 在做代码审查,或者要快速浏览其他协作者提交的更新都作了哪些改动时,就可以用这个选项。此外,还有许多摘要选项可以用,比如 --stat,仅显示简要的增改行数统计:
$ git log --stat commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon Date: Mon Mar 17 21:52:11 2008 -0700 changed the version number Rakefile | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40:33 2008 -0700 removed unnecessary test code lib/simplegit.rb | 5 ----- 1 files changed, 0 insertions(+), 5 deletions(-) commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon Date: Sat Mar 15 10:31:28 2008 -0700 first commit README | 6 ++++++ Rakefile | 23 +++++++++++++++++++++++ lib/simplegit.rb | 25 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 0 deletions(-) 每个提交都列出了修改过的文件,以及其中添加和移除的行数,并在最后列出所有增减行数小计。还有个常用的 --pretty 选项,可以指定使用完全不同于默认格式的方式展示提交历史。比如用oneline 将每个提交放在一行显示,这在提交数很大时非常有用。另外还有 short,full 和fuller 可以用,展示的信息或多或少有些不同,请自己动手实践一下看看效果如何。
$ git log --pretty=onelineca82a6dff817ec66f44342007202690a93763949 changed the version number085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test codea11bef06a3f659402fe7563abf99ad00de2209e6 first commit但最有意思的是 format,可以定制要显示的记录格式,这样的输出便于后期编程提取分析,像这样:
$ git log --pretty=format:"%h - %an, %ar : %s"ca82a6d - Scott Chacon, 11 months ago : changed the version number085bb3b - Scott Chacon, 11 months ago : removed unnecessary test codea11bef0 - Scott Chacon, 11 months ago : first commit表 2-1 列出了常用的格式占位符写法及其代表的意义。
$ git log --pretty=format:"%h %s" --graph* 2d3acf9 ignore errors from SIGCHLD on trap* 5e3ee11 Merge branch 'master' of git://github.com/dustin/grit|\| * 420eac9 Added a method for getting the current branch.* | 30e367c timeout code and tests* | 5a09431 add timeout protection to grit* | e1193f8 support for heads with slashes in them|/* d6016bc require time for xmlschema* 11d191e Merge branch 'defunkt' into local以上只是简单介绍了一些 git log 命令支持的选项。表 2-2 还列出了一些其他常用的选项及其释义。
取消已经暂存的文件
接下来的两个小节将演示如何取消暂存区域中的文件,以及如何取消工作目录中已修改的文件。不用担心,查看文件状态的时候就提示了该如何撤消,所以不需要死记硬背。来看下面的例子,有两个修改过的文件,我们想要分开提交,但不小心用git add . 全加到了暂存区域。该如何撤消暂存其中的一个文件呢?其实,git status 的命令输出已经告诉了我们该怎么做:
$ git add . $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: README.txt # modified: benchmarks.rb #就在 “Changes to be committed” 下面,括号中有提示,可以使用 git reset HEAD ... 的方式取消暂存。好吧,我们来试试取消暂存 benchmarks.rb 文件:
$ git reset HEAD benchmarks.rb benchmarks.rb: locally modified $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: README.txt # # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: benchmarks.rb # 这条命令看起来有些古怪,先别管,能用就行。现在 benchmarks.rb 文件又回到了之前已修改未暂存的状态。
取消对文件的修改
如果觉得刚才对 benchmarks.rb 的修改完全没有必要,该如何取消修改,回到之前的状态(也就是修改之前的版本)呢?git status 同样提示了具体的撤消方法,接着上面的例子,现在未暂存区域看起来像这样:
# Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: benchmarks.rb # 在第二个括号中,我们看到了抛弃文件修改的命令(至少在 Git 1.6.1 以及更高版本中会这样提示,如果你还在用老版本,我们强烈建议你升级,以获取最佳的用户体验),让我们试试看:
$ git checkout -- benchmarks.rb $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: README.txt #可以看到,该文件已经恢复到修改前的版本。你可能已经意识到了,这条命令有些危险,所有对文件的修改都没有了,因为我们刚刚把之前版本的文件复制过 来重写了此文件。所以在用这条命令前,请务必确定真的不再需要保留刚才的修改。如果只是想回退版本,同时保留刚才的修改以便将来继续工作,可以用下章介绍 的 stashing 和分支来处理,应该会更好些。
$ git tag -l 'v1.4.2.*'v1.4.2.1v1.4.2.2v1.4.2.3v1.4.2.4新建标签
Git 使用的标签有两种类型:轻量级的(lightweight)和含附注的(annotated)。轻量级标签就像是个不会变化的分支,实际上它就是个指向特 定提交对象的引用。而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标 签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。
含附注的标签
创建一个含附注类型的标签非常简单,用 -a (译注:取 annotated 的首字母)指定标签名字即可:
$ git tag -a v1.4 -m 'my version 1.4'$ git tagv0.1v1.3v1.4而 -m 选项则指定了对应的标签说明,Git 会将此说明一同保存在标签对象中。如果没有给出该选项,Git 会启动文本编辑软件供你输入标签说明。
可以使用 git show 命令查看相应标签的版本信息,并连同显示打标签时的提交对象。
$ git show v1.4 tag v1.4 Tagger: Scott Chacon Date: Mon Feb 9 14:45:11 2009 -0800 my version 1.4 commit 15027957951b64cf874c3557a0f3547bd83b3ff6 Merge: 4a447f7... a6b4c97... Author: Scott Chacon Date: Sun Feb 8 19:02:46 2009 -0800 Merge branch 'experiment' 我们可以看到在提交对象信息上面,列出了此标签的提交者和提交时间,以及相应的标签说明。
签署标签
如果你有自己的私钥,还可以用 GPG 来签署标签,只需要把之前的 -a 改为 -s (译注: 取 signed 的首字母)即可:
$ git tag -s v1.5 -m 'my signed 1.5 tag' You need a passphrase to unlock the secret key for user: "Scott Chacon " 1024-bit DSA key, ID F721C45A, created 2009-02-09现在再运行 git show 会看到对应的 GPG 签名也附在其内:
$ git show v1.5 tag v1.5 Tagger: Scott Chacon Date: Mon Feb 9 15:22:20 2009 -0800 my signed 1.5 tag -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.8 (Darwin) iEYEABECAAYFAkmQurIACgkQON3DxfchxFr5cACeIMN+ZxLKggJQf0QYiQBwgySN Ki0An2JeAVUCAiJ7Ox6ZEtK+NvZAj82/ =WryJ -----END PGP SIGNATURE----- commit 15027957951b64cf874c3557a0f3547bd83b3ff6 Merge: 4a447f7... a6b4c97... Author: Scott Chacon Date: Sun Feb 8 19:02:46 2009 -0800 Merge branch 'experiment' 稍后我们再学习如何验证已经签署的标签。
$ git tag v1.4-lw$ git tagv0.1v1.3v1.4v1.4-lwv1.5现在运行 git show 查看此标签信息,就只有相应的提交对象摘要:
$ git show v1.4-lw commit 15027957951b64cf874c3557a0f3547bd83b3ff6 Merge: 4a447f7... a6b4c97... Author: Scott Chacon Date: Sun Feb 8 19:02:46 2009 -0800 Merge branch 'experiment'验证标签
可以使用 git tag -v [tag-name] (译注:取 verify 的首字母)的方式验证已经签署的标签。此命令会调用 GPG 来验证签名,所以你需要有签署者的公钥,存放在 keyring 中,才能验证:
$ git tag -v v1.4.2.1 object 883653babd8ee7ea23e6a5c392bb739348b1eb61 type commit tag v1.4.2.1 tagger Junio C Hamano 1158138501 -0700 GIT 1.4.2.1 Minor fixes since 1.4.2, including git-mv and git-http with alternates. gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A gpg: Good signature from "Junio C Hamano " gpg: aka "[jpeg image of size 1513]" Primary key fingerprint: 3565 2A26 2040 E066 C9A7 4A7D C0C6 D9A4 F311 9B9A 若是没有签署者的公钥,会报告类似下面这样的错误:
gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9Agpg: Can't check signature: public key not founderror: could not verify the tag 'v1.4.2.1'后期加注标签
你甚至可以在后期对早先的某次提交加注标签。比如在下面展示的提交历史中:
$ git log --pretty=oneline15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support0d52aaab4479697da7686c15f77a3d64d9165190 one more thing6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function4682c3261057305bdd616e23b64b0857d832627b added a todo file166ae0c4d3f420721acbb115cc33848dfcc2121a started write support9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme我们忘了在提交 “updated rakefile” 后为此项目打上版本号 v1.2,没关系,现在也能做。只要在打标签的时候跟上对应提交对象的校验和(或前几位字符)即可:
$ git tag -a v1.2 9fceb02可以看到我们已经补上了标签:
$ git tag v0.1 v1.2 v1.3 v1.4 v1.4-lw v1.5 $ git show v1.2 tag v1.2 Tagger: Scott Chacon Date: Mon Feb 9 15:32:16 2009 -0800 version 1.2 commit 9fceb02d0ae598e95dc970b74767f19372d61af8 Author: Magnus Chacon Date: Sun Apr 27 20:43:35 2008 -0700 updated rakefile ... 分享标签
默认情况下,git push 并不会把标签传送到远端服务器上,只有通过显式命令才能分享标签到远端仓库。其命令格式如同推送分支,运行git push origin [tagname] 即可:
$ git last commit 66938dae3329c7aebe598c2246a8e6af90d04646 Author: Josh Goebel Date: Tue Aug 26 19:48:51 2008 +0800 test for current head Signed-off-by: Scott Chacon 可以看出,实际上 Git 只是简单地在命令中替换了你设置的别名。不过有时候我们希望运行某个外部命令,而非 Git 的附属工具,这个好办,只需要在命令前加上 ! 就行。如果你自己写了些处理 Git 仓库信息的脚本的话,就可以用这种技术包装起来。作为演示,我们可以设置用 git visual 启动gitk:
$ git checkout -b 'hotfix'Switched to a new branch "hotfix"$ vim index.html$ git commit -a -m 'fixed the broken email address'[hotfix]: created 3a0874c: "fixed the broken email address" 1 files changed, 0 insertions(+), 1 deletions(-)
$ git checkout iss53Switched to branch "iss53"$ vim index.html$ git commit -a -m 'finished the new footer [issue 53]'[iss53]: created ad82d7a: "finished the new footer [issue 53]" 1 files changed, 1 insertions(+), 0 deletions(-)
$ git merge iss53Auto-merging index.htmlCONFLICT (content):Merge conflict in index.htmlAutomatic merge failed; fix conflicts and then commit the result.Git 作了合并,但没有提交,它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突,可以用 git status 查阅:
[master*]$ git status index.html: needs merge # On branch master # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # unmerged: index.html # 任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。可以看到此文件包含类似下面这样的部分:
$ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: index.html #如果觉得满意了,并且确认所有冲突都已解决,也就是进入了暂存区,就可以用 git commit 来完成这次合并提交。提交的记录差不多是这样:
Merge branch 'iss53'Conflicts: index.html## It looks like you may be committing a MERGE.# If this is not correct, please remove the file# .git/MERGE_HEAD# and try again.#如果想给将来看这次合并的人一些方便,可以修改该信息,提供更多合并细节。比如你都作了哪些改动,以及这么做的原因。有时候裁决冲突的理由并不直接或明显,有必要略加注解。
$ git branch -d testingerror: The branch 'testing' is not an ancestor of your current HEAD.If you are sure you want to delete it, run 'git branch -D testing'.不过,如果你确实想要删除该分支上的改动,可以用大写的删除选项 -D 强制执行,就像上面提示信息中给出的那样。
$ git checkout -b serverfix origin/serverfixBranch serverfix set up to track remote branch refs/remotes/origin/serverfix.Switched to a new branch "serverfix"这会切换到新建的 serverfix 本地分支,其内容同远程分支 origin/serverfix 一致,这样你就可以在里面继续开发了。
$ git checkout --track origin/serverfixBranch serverfix set up to track remote branch refs/remotes/origin/serverfix.Switched to a new branch "serverfix"要为本地分支设定不同于远程分支的名字,只需在前个版本的命令里换个名字:
$ git checkout -b sf origin/serverfixBranch sf set up to track remote branch refs/remotes/origin/serverfix.Switched to a new branch "sf"现在你的本地分支 sf 会自动向 origin/serverfix 推送和抓取数据了。
$ git checkout experiment$ git rebase masterFirst, rewinding head to replay your work on top of it...Applying: added staged command它的原理是回到两个分支最近的共同祖先,根据当前分支(也就是要进行衍合的分支 experiment)后续的历次提交对象(这里只有一个 C3),生成一系列文件补丁,然后以基底分支(也就是主干分支master)最后一个提交对象(C4)为新的出发点,逐个应用之前准备好的补丁文件,最后会生成一个新的合并提交对象(C3’),从而改写 experiment 的提交历史,使它成为 master 分支的直接下游,如图 3-29 所示:
在进行衍合的时候,实际上抛弃了一些现存的提交对象而创造了一些类似但不同的新的提交对象。如果你把原来分支中的提交对象发布出去,并且其他人更新下载后在其基础上开展工作,而稍后你又用git rebase 抛弃这些提交对象,把新的重演后的提交对象发布出去的话,你的合作者就不得不重新合并他们的工作,这样当你再次从他们那里获取内容时,提交历史就会变得一团糟。
$ cd ~/.ssh$ lsauthorized_keys2 id_dsa known_hostsconfig id_dsa.pub关键是看有没有用 something 和 something.pub 来命名的一对文件,这个 something 通常就是 id_dsa 或 id_rsa。有 .pub 后缀的文件就是公 钥,另一个文件则是密 钥。假如没有这些文件,或者干脆连.ssh 目录都没有,可以用 ssh-keygen 来创建。该程序在 Linux/Mac 系统上由 SSH 包提供,而在 Windows 上则包含在 MSysGit 包里:
$ ssh-keygen Generating public/private rsa key pair.Enter file in which to save the key (/Users/schacon/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /Users/schacon/.ssh/id_rsa.Your public key has been saved in /Users/schacon/.ssh/id_rsa.pub.The key fingerprint is:43:c5:5b:5f:b1:f1:50:43:ad:20:a6:92:6a:1f:9a:3a schacon@agadorlaptop.local它先要求你确认保存公 钥的位置(.ssh/id_rsa),然后它会让你重复一个密 码两次,如果不想在使用公钥的时候输入密 码,可以留空。
$ ssh git@gitserverfatal: What do you think I am? A shell?Connection to gitserver closed.
4.5 公共访问
匿 名的读取权限该怎么实现呢?也许除了内部私有的项目之外,你还需要托 管一些开 源项目。或者因为要用一些自动化的服务器来进行编译,或者有一些经常变化的服务器群组,而又不想整天生成新的 SSH 密 钥 — 总之,你需要简单的匿 名读取权限。
或许对小型的配置来说最简单的办法就是运行一个静态 web 服务,把它的根目录设定为 Git 仓库所在的位置,然后开启本章第一节提到的 post-update 挂 钩。这里继续使用之前的例子。假设仓库处于/opt/git 目录,主机上运行着 Apache 服务。重申一下,任何 web 服务程序都可以达到相同效果;作为范例,我们将用一些基本的 Apache 设定来展示大体需要的步骤。
[group mobile]writable = iphone_projectmembers = scott john josie jessica如果你提交并推送这个修改,四个用户将同时具有该项目的读写权限。
Gitosis 也具有简单的访问控制功能。如果想让 John 只有读权限,可以这样做:
[group mobile]writable = iphone_projectmembers = scott josie jessica[group mobile_ro]readonly = iphone_projectmembers = john现在 John 可以克隆和获取更新,但 Gitosis 不会允许他向项目推送任何内容。像这样的组可以随意创建,多少不限,每个都可以包含若干不同的用户和项目。甚至还可以指定某个组为成员之一(在组名前加上@ 前缀),自动继承该组的成员:
[group mobile_committers]members = scott josie jessica[group mobile]writable = iphone_projectmembers = @mobile_committers[group mobile_2]writable = another_iphone_projectmembers = @mobile_committers john如果遇到意外问题,试试看把 loglevel=DEBUG 加到 [gitosis] 的段落(译注:把日志设置为调试级别,记录更详细的运行信息。)。如果一不小心搞错了配置,失去了推送权限,也可以手工修改服务器上的/home/git/.gitosis.conf 文件 — Gitosis 实际是从该文件读取信息的。它在得到推送数据时,会把新的 gitosis.conf 存到该路径上。所以如果你手工编辑该文件的话,它会一直保持到下次向 gitosis-admin 推送新版本的配置内容为止。 作者: compare2000 时间: 2014-05-22 17:35
4.8 Gitolite
Note: the latest copy of this section of the ProGit book is always available within thegitolite documentation. The author would also like to humbly state that, while this section is accurate, andcan (and often has) been used to install gitolite without reading any other documentation, it is of necessity not complete, and cannot completely replace the enormous amount of documentation that gitolite comes with.
Git has started to become very popular in corporate environments, which tend to have some additional requirements in terms of access control. Gitolite was originally created to help with those requirements, but it turns out that it’s equally useful in the open source world: the Fedora Project controls access to their package management repositories (over 10,000 of them!) using gitolite, and this is probably the largest gitolite installation anywhere too.
Gitolite allows you to specify permissions not just by repository, but also by branch or tag names within each repository. That is, you can specify that certain people (or groups of people) can only push certain “refs” (branches or tags) but not others.
Installing
Installing Gitolite is very easy, even if you don’t read the extensive documentation that comes with it. You need an account on a Unix server of some kind; various Linux flavours, and Solaris 10, have been tested. You do not need root access, assuming git, perl, and an openssh compatible ssh server are already installed. In the examples below, we will use thegitolite account on a host called gitserver.
Gitolite is somewhat unusual as far as “server” software goes – access is via ssh, and so every userid on the server is a potential “gitolite host”. As a result, there is a notion of “installing” the software itself, and then “setting up” a user as a “gitolite host”.
Gitolite has 4 methods of installation. People using Fedora or Debian systems can obtain an RPM or a DEB and install that. People with root access can install it manually. In these two methods, any user on the system can then become a “gitolite host”.
People without root access can install it within their own userids. And finally, gitolite can be installed by running a scripton the workstation, from a bash shell. (Even the bash that comes with msysgit will do, in case you’re wondering.)
We will describe this last method in this article; for the other methods please see the documentation.
You start by obtaining public key based access to your server, so that you can log in from your workstation to the server without getting a password prompt. The following method works on Linux; for other workstation OSs you may have to do this manually. We assume you already had a key pair generated using ssh-keygen.
$ ssh-copy-id -i ~/.ssh/id_rsa gitolite@gitserverThis will ask you for the password to the gitolite account, and then set up public key access. This isessential for the install script, so check to make sure you can run a command without getting a password prompt:
$ ssh gitolite@gitserver pwd/home/gitoliteNext, you clone Gitolite from the project’s main site and run the “easy install” script (the third argument is your name as you would like it to appear in the resulting gitolite-admin repository):
$ git clone git://github.com/sitaramc/gitolite$ cd gitolite/src$ ./gl-easy-install -q gitolite gitserver sitaramAnd you’re done! Gitolite has now been installed on the server, and you now have a brand new repository calledgitolite-admin in the home directory of your workstation. You administer your gitolite setup by making changes to this repository and pushing.
That last command does produce a fair amount of output, which might be interesting to read. Also, the first time you run this, a new keypair is created; you will have to choose a passphrase or hit enter for none. Why a second keypair is needed, and how it is used, is explained in the “ssh troubleshooting” document that comes with Gitolite. (Hey the documentation has to be good forsomething!)
Repos named gitolite-admin and testing are created on the server by default. If you wish to clone either of these locally (from an account that has SSH console access to the gitolite account viaauthorized_keys), type:
$ git clone gitolite:gitolite-admin$ git clone gitolite:testingTo clone these same repos from any other account:
$ git clone gitolite@servername:gitolite-admin$ git clone gitolite@servername:testingCustomising the Install
While the default, quick, install works for most people, there are some ways to customise the install if you need to. If you omit the-q argument, you get a “verbose” mode install – detailed information on what the install is doing at each step. The verbose mode also allows you to change certain server-side parameters, such as the location of the actual repositories, by editing an “rc” file that the server uses. This “rc” file is liberally commented so you should be able to make any changes you need quite easily, save it, and continue. This file also contains various settings that you can change to enable or disable some of gitolite’s advanced features.
Config File and Access Control Rules
Once the install is done, you switch to the gitolite-admin repository (placed in your HOME directory) and poke around to see what you got:
$ cd ~/gitolite-admin/$ lsconf/ keydir/$ find conf keydir -type fconf/gitolite.confkeydir/sitaram.pub$ cat conf/gitolite.conf#gitolite conf# please see conf/example.conf for details on syntax and featuresrepo gitolite-admin RW+ = sitaramrepo testing RW+ = @allNotice that “sitaram” (the last argument in the gl-easy-install command you gave earlier) has read-write permissions on thegitolite-admin repository as well as a public key file of the same name.
The config file syntax for gitolite is liberally documented in conf/example.conf, so we’ll only mention some highlights here.
You can group users or repos for convenience. The group names are just like macros; when defining them, it doesn’t even matter whether they are projects or users; that distinction is only made when youuse the “macro”.
@oss_repos = linux perl rakudo git gitolite@secret_repos = fenestra pear@admins = scott # Adams, not Chacon, sorry @interns = ashok # get the spelling right, Scott!@engineers = sitaram dilbert wally alice@staff = @admins @engineers @internsYou can control permissions at the “ref” level. In the following example, interns can only push the “int” branch. Engineers can push any branch whose name starts with “eng-“, and tags that start with “rc” followed by a digit. And the admins can do anything (including rewind) to any ref.
repo @oss_repos RW int$ = @interns RW eng- = @engineers RW refs/tags/rc[0-9] = @engineers RW+ = @adminsThe expression after the RW or RW+ is a regular expression (regex) that the refname (ref) being pushed is matched against. So we call it a “refex”! Of course, a refex can be far more powerful than shown here, so don’t overdo it if you’re not comfortable with perl regexes.
Also, as you probably guessed, Gitolite prefixes refs/heads/ as a syntactic convenience if the refex does not begin withrefs/.
An important feature of the config file’s syntax is that all the rules for a repository need not be in one place. You can keep all the common stuff together, like the rules for alloss_repos shown above, then add specific rules for specific cases later on, like so:
repo gitolite RW+ = sitaramThat rule will just get added to the ruleset for the gitolite repository.
At this point you might be wondering how the access control rules are actually applied, so let’s go over that briefly.
There are two levels of access control in gitolite. The first is at the repository level; if you have read (or write) access toany ref in the repository, then you have read (or write) access to the repository.
The second level, applicable only to “write” access, is by branch or tag within a repository. The username, the access being attempted (W or+), and the refname being updated are known. The access rules are checked in order of appearance in the config file, looking for a match for this combination (but remember that the refname is regex-matched, not merely string-matched). If a match is found, the push succeeds. A fallthrough results in access being denied.
Advanced Access Control with “deny” rules
So far, we’ve only seen permissions to be one or R, RW, orRW+. However, gitolite allows another permission: -, standing for “deny”. This gives you a lot more power, at the expense of some complexity, because now fallthrough is not theonly way for access to be denied, so the order of the rules now matters!
Let us say, in the situation above, we want engineers to be able to rewind any branchexcept master and integ. Here’s how to do that:
RW master integ = @engineers - master integ = @engineers RW+ = @engineersAgain, you simply follow the rules top down until you hit a match for your access mode, or a deny. Non-rewind push to master or integ is allowed by the first rule. A rewind push to those refs does not match the first rule, drops down to the second, and is therefore denied. Any push (rewind or non-rewind) to refs other than master or integ won’t match the first two rules anyway, and the third rule allows it.
Restricting pushes by files changed
In addition to restricting what branches a user can push changes to, you can also restrict what files they are allowed to touch. For example, perhaps the Makefile (or some other program) is really not supposed to be changed by just anyone, because a lot of things depend on it or would break if the changes are not done just right. You can tell gitolite:
Personal Branches
Gitolite also has a feature called “personal branches” (or rather, “personal branch namespace”) that can be very useful in a corporate environment.
A lot of code exchange in the git world happens by “please pull” requests. In a corporate environment, however, unauthenticated access is a no-no, and a developer workstation cannot do authentication, so you have to push to the central server and ask someone to pull from there.
This would normally cause the same branch name clutter as in a centralised VCS, plus setting up permissions for this becomes a chore for the admin.
Gitolite lets you define a “personal” or “scratch” namespace prefix for each developer (for example,refs/personal/ /* ); see the “personal branches” section in doc/3-faq-tips-etc.mkd for details.
“Wildcard” repositories
Gitolite allows you to specify repositories with wildcards (actually perl regexes), like, for exampleassignments/s[0-9][0-9]/a[0-9][0-9], to pick a random example. This is avery powerful feature, which has to be enabled by setting $GL_WILDREPOS = 1; in the rc file. It allows you to assign a new permission mode (”C”) which allows users to create repositories based on such wild cards, automatically assigns ownership to the specific user who created it, allows him/her to hand out R and RW permissions to other users to collaborate, etc. This feature is documented indoc/4-wildcard-repositories.mkd.
Other Features
We’ll round off this discussion with a sampling of other features, all of which, and many more, are described in great detail in the “faqs, tips, etc” and other documents.
Logging: Gitolite logs all successful accesses. If you were somewhat relaxed about giving people rewind permissions (RW+) and some kid blew away “master”, the log file is a life saver, in terms of easily and quickly finding the SHA that got hosed.
Git outside normal PATH: One extremely useful convenience feature in gitolite is support for git installed outside the normal$PATH (this is more common than you think; some corporate environments or even some hosting providers refuse to install things system-wide and you end up putting them in your own directories). Normally, you are forced to make theclient-side git aware of this non-standard location of the git binaries in some way. With gitolite, just choose a verbose install and set$GIT_PATH in the “rc” files. No client-side changes are required after that
Access rights reporting: Another convenient feature is what happens when you try and just ssh to the server. Gitolite shows you what repos you have access to, and what that access may be. Here’s an example:
hello sitaram, the gitolite version here is v1.5.4-19-ga3397d4 the gitolite config gives you the following access: R anu-wsd R entrans R W git-notes R W gitolite R W gitolite-admin R indic_web_input R shreelipi_converterDelegation: For really large installations, you can delegate responsibility for groups of repositories to various people and have them manage those pieces independently. This reduces the load on the main admin, and makes him less of a bottleneck. This feature has its own documentation file in the doc/ directory.
Gitweb support: Gitolite supports gitweb in several ways. You can specify which repos are visible via gitweb. You can set the “owner” and “description” for gitweb from the gitolite config file. Gitweb has a mechanism for you to implement access control based on HTTP authentication, so you can make it use the “compiled” config file that gitolite produces, which means the same access control rules (for read access) apply for gitweb and gitolite.
Mirroring: Gitolite can help you maintain multiple mirrors, and switch between them easily if the primary server goes down.
目前,可供选择的托管服务数量繁多,各有利弊。在 Git 官方 wiki 上的 Githosting 页面有一个最新的托管服务列表:
http://git.or.cz/gitwiki/GitHosting由于本书无法全部一一介绍,而本人(译注:指本书作者 Scott Chacon。)刚好在其中一家公司工作,所以接下来我们将会介绍如何在 GitHub 上建立新账户并启动项目。至于其他托管服务大体也是这么一个过程,基本的想法都是差不多的。
$ git log --no-merges origin/master ^issue54 commit 738ee872852dfaa9d6634e0dea7a324040193016 Author: John Smith Date: Fri May 29 16:01:27 2009 -0700 removed invalid default value现在,Jessica 可以将特性分支上的工作并到 master 分支,然后再并入 John 的工作(origin/master)到自己的master 分支,最后再推送回服务器。当然,得先切回主分支才能集成所有数据:
$ git checkout masterSwitched to branch "master"Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.要合并 origin/master 或 issue54 分支,谁先谁后都没有关系,因为它们都在上游(upstream)(译注:想像分叉的更新像是汇流成河的源头,所以上游 upstream 是指最新的提交),所以无所谓先后顺序,最终合并后的内容快照都是一样的,而仅是提交历史看起来会有些先后差别。Jessica 选择先合并issue54:
$ git log origin/featureA ^featureA commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6 Author: John Smith Date: Fri May 29 19:57:33 2009 -0700 changed log output to 30 from 25最后,她将 John 的工作合并到自己的 featureA 分支中:
$ git request-pull origin/master myforkThe following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40: John Smith (1): added a new functionare available in the git repository at: git://githost/simplegit.git featureAJessica Smith (2): add limit to log function change log output to 30 from 25 lib/simplegit.rb | 10 +++++++++- 1 files changed, 9 insertions(+), 1 deletions(-)输出的内容可以直接发邮件给管理者,他们就会明白这是从哪次提交开始旁支出去的,该到哪里去抓取新的代码,以及新的代码增加了哪些功能等等。
$ git send-email *.patch 0001-added-limit-to-log-function.patch 0002-changed-log-output-to-30-from-25.patch Who should the emails appear to be from? [Jessica Smith ] Emails will be sent from: Jessica Smith Who should the emails be sent to? jessica@example.com Message-ID to be used as In-Reply-To for the first email? y 接下来,Git 会根据每个补丁依次输出类似下面的日志:
(mbox) Adding cc: Jessica Smith from \line 'From: Jessica Smith ' OK. Log says: Sendmail: /usr/sbin/sendmail -i jessica@example.com From: Jessica Smith To: jessica@example.com Subject: [PATCH 1/2] added limit to log function Date: Sat, 30 May 2009 13:29:15 -0700 Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com> X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty In-Reply-To: References: Result: OK 最后,到 Gmail 上打开 Drafts 文件夹,编辑这些邮件,修改收件人地址为邮件列表地址,另外给要抄送的人也加到 Cc 列表中,最后发送。
对于 format-patch 制作的新式补丁,应当使用 git am 命令。从技术上来说,git am 能够读取 mbox 格式的文件。这是种简单的纯文本文件,可以包含多封电邮,格式上用 From 加空格以及随便什么辅助信息所组成的行作为分隔行,以区分每封邮件,就像这样:
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001 From: Jessica Smith Date: Sun, 6 Apr 2008 10:17:23 -0700 Subject: [PATCH 1/2] add limit to log function Limit log functionality to the first 20这是 format-patch 命令输出的开头几行,也是一个有效的 mbox 文件格式。如果有人用 git send-email 给你发了一个补丁,你可以将此邮件下载到本地,然后运行git am 命令来应用这个补丁。如果你的邮件客户端能将多封电邮导出为 mbox 格式的文件,就可以用 git am 一次性应用所有导出的补丁。
如果贡献者将 format-patch 生成的补丁文件上传到类似 Request Ticket 一样的任务处理系统,那么可以先下载到本地,继而使用git am 应用该补丁:
$ git am 0001-limit-log-function.patch Applying: add limit to log function你会看到它被干净地应用到本地分支,并自动创建了新的提交对象。作者信息取自邮件头 From 和 Date,提交消息则取自Subject 以及正文中补丁之前的内容。来看具体实例,采纳之前展示的那个 mbox 电邮补丁后,最新的提交对象为:
$ git log --pretty=fuller -1 commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0 Author: Jessica Smith AuthorDate: Sun Apr 6 10:17:23 2008 -0700 Commit: Scott Chacon CommitDate: Thu Apr 9 09:19:06 2009 -0700 add limit to log function Limit log functionality to the first 20 Commit 部分显示的是采纳补丁的人,以及采纳的时间。而 Author 部分则显示的是原作者,以及创建补丁的时间。
有时,我们也会遇到打不上补丁的情况。这多半是因为主干分支和补丁的基础分支相差太远,但也可能是因为某些依赖补丁还未应用。这种情况下,git am 会报错并询问该怎么做:
$ git am 0001-seeing-if-this-helps-the-gem.patch Applying: seeing if this helps the gemerror: patch failed: ticgit.gemspec:1error: ticgit.gemspec: patch does not applyPatch failed at 0001.When you have resolved this problem run "git am --resolved".If you would prefer to skip this patch, instead run "git am --skip".To restore the original branch and stop patching run "git am --abort".Git 会在有冲突的文件里加入冲突解决标记,这同合并或衍合操作一样。解决的办法也一样,先编辑文件消除冲突,然后暂存文件,最后运行 git am --resolved 提交修正结果:
$ (fix the file)$ git add ticgit.gemspec $ git am --resolvedApplying: seeing if this helps the gem如果想让 Git 更智能地处理冲突,可以用 -3 选项进行三方合并。如果当前分支未包含该补丁的基础代码或其祖先,那么三方合并就会失败,所以该选项默认为关闭状态。一般来说,如果该补丁是基于某个公开的提交制作而成的话,你总是可以通过同步来获取这个共同祖先,所以用三方合并选项可以解决很多麻烦:
$ git am -3 0001-seeing-if-this-helps-the-gem.patch Applying: seeing if this helps the gemerror: patch failed: ticgit.gemspec:1error: ticgit.gemspec: patch does not applyUsing index info to reconstruct a base tree...Falling back to patching base and 3-way merge...No changes -- Patch already applied.像上面的例子,对于打过的补丁我又再打一遍,自然会产生冲突,但因为加上了 -3 选项,所以它很聪明地告诉我,无需更新,原有的补丁已经应用。
对于一次应用多个补丁时所用的 mbox 格式文件,可以用 am 命令的交互模式选项 -i,这样就会在打每个补丁前停住,询问该如何操作:
$ git am -3 -i mboxCommit Body is:--------------------------seeing if this helps the gem--------------------------Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all 在多个补丁要打的情况下,这是个非常好的办法,一方面可以预览下补丁内容,同时也可以有选择性的接纳或跳过某些补丁。
$ git pull git://github.com/onetimeguy/project.gitFrom git://github.com/onetimeguy/project * branch HEAD -> FETCH_HEADMerge made by recursive.决断代码取舍
现在特性分支上已合并好了贡献者的代码,是时候决断取舍了。本节将回顾一些之前学过的命令,以看清将要合并到主干的是哪些代码,从而理解它们到底做了些什么,是否真的要并入。
$ git log contrib --not master commit 5b6235bd297351589efc4d73316f0a68d484f118 Author: Scott Chacon Date: Fri Oct 24 09:53:59 2008 -0700 seeing if this helps the gem commit 7482e0d16d04bea79d0dba8988cc78df655f16a0 Author: Scott Chacon Date: Mon Oct 22 19:38:36 2008 -0700 updated the gemspec to hopefully work better 还可以查看每次提交的具体修改。请牢记,在 git log 后加 -p 选项将展示每次提交的内容差异。
$ git cherry-pick e43a6fd3e94888d76779ad79fb568ed180e5fcdfFinished one cherry-pick.[master]: created a0a41a9: "More friendly message when locking the index fails." 3 files changed, 17 insertions(+), 3 deletions(-)这将会引入e43a6的代码,但是会得到不同的SHA-1值,因为应用日期不同。现在你的历史看起来像图 5-27.
$ git tag -s v1.5 -m 'my signed 1.5 tag' You need a passphrase to unlock the secret key for user: "Scott Chacon " 1024-bit DSA key, ID F721C45A, created 2009-02-09完成签名之后,如何分发PGP公钥(public key)是个问题。(译者注:分发公钥是为了验证标签)。还好,Git的设计者想到了解决办法:可以把key(既公钥)作为blob变量写入Git库,然后把它的内容直接写在标签里。gpg --list-keys命令可以显示出你所拥有的key:
$ gpg --list-keys /Users/schacon/.gnupg/pubring.gpg --------------------------------- pub 1024D/F721C45A 2009-02-09 [expires: 2010-02-09] uid Scott Chacon sub 2048g/45D02282 2009-02-09 [expires: 2010-02-09]然后,导出key的内容并经由管道符传递给git hash-object,之后钥匙会以blob类型写入Git中,最后返回这个blob量的SHA-1值:
$ gpg -a --export F721C45A | git hash-object -w --stdin659ef797d181633c87ec71ac3f9ba29fe5775b92现在你的Git已经包含了这个key的内容了,可以通过不同的SHA-1值指定不同的key来创建标签。
$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92在运行git push --tags命令之后,maintainer-pgp-pub标签就会公布给所有人。如果有人想要校验标签,他可以使用如下命令导入你的key:
$ git show maintainer-pgp-pub | gpg --import人们可以用这个key校验你签名的所有标签。另外,你也可以在标签信息里写入一个操作向导,用户只需要运行git show 查看标签信息,然后按照你的向导就能完成校验。
生成内部版本号
因为Git不会为每次提交自动附加类似’v123’的递增序列,所以如果你想要得到一个便于理解的提交号可以运行git describe命令。Git将会返回一个字符串,由三部分组成:最近一次标定的版本号,加上自那次标定之后的提交次数,再加上一段SHA-1值of the commit you’re describing:
$ git shortlog --no-merges master --not v1.0.1Chris Wanstrath (: Add support for annotated tags to Grit::Tag Add packed-refs annotated tag support. Add Grit::Commit#to_patch Update version and History.txt Remove stray `puts` Make ls_tree ignore nilsTom Preston-Werner (4): fix dates in history dynamic version method Version bump to 1.0.2 Regenerated gemspec for version 1.0.2这就是自从v1.0.1版本以来的所有提交的简介,内容按照作者分组,以便你能快速的发e-mail给他们。
$ git show ca82a6dff817ec66f44342007202690a93763949$ git show topic1如果你想知道某个分支指向哪个特定的 SHA,或者想看任何一个例子中被简写的 SHA-1,你可以使用一个叫做 rev-parse 的 Git 探测工具。在第 9 章你可以看到关于探测工具的更多信息;简单来说,rev-parse 是为了底层操作而不是日常操作设计的。不过,有时你想看 Git 现在到底处于什么状态时,它可能会很有用。这里你可以对你的分支运执行rev-parse。
$ git rev-parse topic1ca82a6dff817ec66f44342007202690a93763949引用日志里的简称
在你工作的同时,Git 在后台的工作之一就是保存一份引用日志——一份记录最近几个月你的 HEAD 和分支引用的日志。
你可以使用 git reflog 来查看引用日志:
$ git reflog734713b... HEAD@{0}: commit: fixed refs handling, added gc auto, updatedd921970... HEAD@{1}: merge phedders/rdocs: Merge made by recursive.1c002dd... HEAD@{2}: commit: added some blame and merge stuff1c36188... HEAD@{3}: rebase -i (squash): updating HEAD95df984... HEAD@{4}: commit: # This is a combination of two commits.1c36188... HEAD@{5}: rebase -i (squash): updating HEAD7e05da5... HEAD@{6}: rebase -i (pick): updating HEAD每次你的分支顶端因为某些原因被修改时,Git 就会为你将信息保存在这个临时历史记录里面。你也可以使用这份数据来指明更早的分支。如果你想查看仓库中 HEAD 在五次前的值,你可以使用引用日志的输出中的@{n} 引用:
$ git show HEAD@{5}你也可以使用这个语法来查看一定时间前分支指向哪里。例如,想看你的 master 分支昨天在哪,你可以输入
$ git show master@{yesterday}它就会显示昨天分支的顶端在哪。这项技术只对还在你引用日志里的数据有用,所以不能用来查看比几个月前还早的提交。
想要看类似于 git log 输出格式的引用日志信息,你可以运行 git log -g:
$ git log -g mastercommit 734713bc047d87bf7eac9674765ae793478c50d3Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)Reflog message: commit: fixed refs handling, added gc auto, updated Author: Scott Chacon <schacon@gmail.com>Date: Fri Jan 2 18:32:33 2009 -0800 fixed refs handling, added gc auto, updated testscommit d921970aadf03b3cf0e71becdaab3147ba71cdefReflog: master@{1} (Scott Chacon <schacon@gmail.com>)Reflog message: merge phedders/rdocs: Merge made by recursive.Author: Scott Chacon <schacon@gmail.com>Date: Thu Dec 11 15:08:43 2008 -0800 Merge commit 'phedders/rdocs'需要注意的是,日志引用信息只存在于本地——这是一个你在仓库里做过什么的日志。其他人的仓库拷贝里的引用和你的相同;而你新克隆一个仓库的时候,引用日志是空的,因为你在仓库里还没有操作。只有你克隆了一个项目至少两个月,git show HEAD@{2.months.ago} 才会有用——如果你是五分钟前克隆的仓库,将不会有结果返回。
$ git show HEAD^commit d921970aadf03b3cf0e71becdaab3147ba71cdefMerge: 1c002dd... 35cfb2b...Author: Scott Chacon <schacon@gmail.com>Date: Thu Dec 11 15:08:43 2008 -0800 Merge commit 'phedders/rdocs'你也可以在 ^ 后添加一个数字——例如,d921970^2 意思是“d921970 的第二父提交”。这种语法只在合并提交时有用,因为合并提交可能有多个父提交。第一父提交是你合并时所在分支,而第二父提交是你所合并的分支:
$ git show d921970^commit 1c002dd4b536e7479fe34593e72e6c6c1819e53bAuthor: Scott Chacon <schacon@gmail.com>Date: Thu Dec 11 14:58:32 2008 -0800 added some blame and merge stuff$ git show d921970^2commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548Author: Paul Hedderly <paul+git@mjr.org>Date: Wed Dec 10 22:22:03 2008 +0000 Some rdoc changes另外一个指明祖先提交的方法是 ~。这也是指向第一父提交,所以 HEAD~ 和 HEAD^ 是等价的。当你指定数字的时候就明显不一样了。HEAD~2 是指“第一父提交的第一父提交”,也就是“祖父提交”——它会根据你指定的次数检索第一父提交。例如,在上面列出的历史记录里面,HEAD~3 会是
$ git show HEAD~3commit 1c3618887afb5fbcbea25b7c013f4e2114448b8dAuthor: Tom Preston-Werner <tom@mojombo.com>Date: Fri Nov 7 13:47:59 2008 -0500 ignore *.gem也可以写成 HEAD^^^,同样是第一父提交的第一父提交的第一父提交:
$ git show HEAD^^^commit 1c3618887afb5fbcbea25b7c013f4e2114448b8dAuthor: Tom Preston-Werner <tom@mojombo.com>Date: Fri Nov 7 13:47:59 2008 -0500 ignore *.gem你也可以混合使用这些语法——你可以通过 HEAD~3^2 指明先前引用的第二父提交(假设它是一个合并提交)。
diff --git a/lib/simplegit.rb b/lib/simplegit.rbindex dd5ecc4..57399e0 100644--- a/lib/simplegit.rb+++ b/lib/simplegit.rb@@ -22,7 +22,7 @@ class SimpleGit end def log(treeish = 'master')- command("git log -n 25 #{treeish}")+ command("git log -n 30 #{treeish}") end def blame(path)Stage this hunk [y,n,a,d,/,j,J,g,e,?]? 此处你有很多选择。输入?可以显示列表:
Stage this hunk [y,n,a,d,/,j,J,g,e,?]? ?y - stage this hunkn - do not stage this hunka - stage this and all the remaining hunks in the filed - do not stage this hunk nor any of the remaining hunks in the fileg - select a hunk to go to/ - search for a hunk matching the given regexj - leave this hunk undecided, see next undecided hunkJ - leave this hunk undecided, see next hunkk - leave this hunk undecided, see previous undecided hunkK - leave this hunk undecided, see previous hunks - split the current hunk into smaller hunkse - manually edit the current hunk? - print help如果你想暂存各个区块,通常你会输入y或者n,但是暂存特定文件里的全部区块或者暂时跳过对一个区块的处理同样也很有用。如果你暂存了文件的一个部分而保留另外一个部分不被暂存,你的状态输出看起来会是这样:
What now> 1 staged unstaged path 1: unchanged +0/-1 TODO 2: +1/-1 nothing index.html 3: +1/-1 +4/-0 lib/simplegit.rbsimplegit.rb的状态非常有意思。它显示有几行被暂存了,有几行没有。你部分地暂存了这个文件。在这时,你可以退出交互式脚本然后运行git commit来提交部分暂存的文件。
$ git status# On branch master# Changes to be committed:# (use "git reset HEAD <file>..." to unstage)## modified: index.html## Changed but not updated:# (use "git add <file>..." to update what will be committed)## modified: lib/simplegit.rb#现在你想切换分支,但是你还不想提交你正在进行中的工作;所以你储藏这些变更。为了往堆栈推送一个新的储藏,只要运行 git stash:
$ git stashSaved working directory and index state \ "WIP on master: 049d078 added the index file"HEAD is now at 049d078 added the index file(To restore them type "git stash apply"你的工作目录就干净了:
$ git status# On branch masternothing to commit (working directory clean)这时,你可以方便地切换到其他分支工作;你的变更都保存在栈上。要查看现有的储藏,你可以使用 git stash list:
$ git stash liststash@{0}: WIP on master: 049d078 added the index filestash@{1}: WIP on master: c264051... Revert "added file_size"stash@{2}: WIP on master: 21d80a5... added number to log在这个案例中,之前已经进行了两次储藏,所以你可以访问到三个不同的储藏。你可以重新应用你刚刚实施的储藏,所采用的命令就是之前在原始的 stash 命令的帮助输出里提示的:git stash apply。如果你想应用更早的储藏,你可以通过名字指定它,像这样:git stash apply stash@{2}。如果你不指明,Git 默认使用最近的储藏并尝试应用它:
$ git stash apply# On branch master# Changed but not updated:# (use "git add <file>..." to update what will be committed)## modified: index.html# modified: lib/simplegit.rb#你可以看到 Git 重新修改了你所储藏的那些当时尚未提交的文件。在这个案例里,你尝试应用储藏的工作目录是干净的,并且属于同一分支;但是一个干净的工作目录和应用到相同的分支上并不是应用储藏的必要条件。你可以在其中一个分支上保留一份储藏,随后切换到另外一个分支,再重新应用这些变更。在工作目录里包含已修改和未提交的文件时,你也可以应用储藏——Git 会给出归并冲突如果有任何变更无法干净地被应用。
$ git stash apply --index# On branch master# Changes to be committed:# (use "git reset HEAD <file>..." to unstage)## modified: index.html## Changed but not updated:# (use "git add <file>..." to update what will be committed)## modified: lib/simplegit.rb#apply 选项只尝试应用储藏的工作——储藏的内容仍然在栈上。要移除它,你可以运行 git stash drop,加上你希望移除的储藏的名字:
$ git stash liststash@{0}: WIP on master: 049d078 added the index filestash@{1}: WIP on master: c264051... Revert "added file_size"stash@{2}: WIP on master: 21d80a5... added number to log$ git stash drop stash@{0}Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)你也可以运行 git stash pop 来重新应用储藏,同时立刻将其从堆栈中移走。
Un-applying a Stash
In some use case scenarios you might want to apply stashed changes, do some work, but then un-apply those changes that originally came form the stash. Git does not provide such astash unapply command, but it is possible to achieve the effect by simply retrieving the patch associated with a stash and applying it in reverse:
$ git stash show -p stash@{0} | git apply -RAgain, if you don’t specify a stash, Git assumes the most recent stash:
$ git stash show -p | git apply -RYou may want to create an alias and effectively add a stash-unapply command to your git. For example:
$ git config --global alias.stash-unapply '!git stash show -p | git apply -R'$ git stash$ #... work work work$ git stash-unapply从储藏中创建分支
如果你储藏了一些工作,暂时不去理会,然后继续在你储藏工作的分支上工作,你在重新应用工作时可能会碰到一些问题。如果尝试应用的变更是针对一个你那之后修改过的文件,你会碰到一个归并冲突并且必须去化解它。如果你想用更方便的方法来重新检验你储藏的变更,你可以运行git stash branch,这会创建一个新的分支,检出你储藏工作时的所处的提交,重新应用你的工作,如果成功,将会丢弃储藏。
$ git stash branch testchangesSwitched to a new branch "testchanges"# On branch testchanges# Changes to be committed:# (use "git reset HEAD <file>..." to unstage)## modified: index.html## Changed but not updated:# (use "git add <file>..." to update what will be committed)## modified: lib/simplegit.rb#Dropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359)这是一个很棒的捷径来恢复储藏的工作然后在新的分支上继续当时的工作。
例如,你想修改最近三次的提交说明,或者其中任意一次,你必须给git rebase -i提供一个参数,指明你想要修改的提交的父提交,例如HEAD~2或者HEAD~3。可能记住~3更加容易,因为你想修改最近三次提交;但是请记住你事实上所指的是四次提交之前,即你想修改的提交的父提交。
$ git rebase -i HEAD~3再次提醒这是一个衍合命令——HEAD~3..HEAD范围内的每一次提交都会被重写,无论你是否修改说明。不要涵盖你已经推送到中心服务器的提交——这么做会使其他开发者产生混乱,因为你提供了同样变更的不同版本。
运行这个命令会为你的文本编辑器提供一个提交列表,看起来像下面这样
pick f7f3f6d changed my name a bitpick 310154e updated README formatting and added blamepick a5f4a0d added cat-file# Rebase 710f0f8..a5f4a0d onto 710f0f8## Commands:# p, pick = use commit# e, edit = use commit, but stop for amending# s, squash = use commit, but meld into previous commit## If you remove a line here THAT COMMIT WILL BE LOST.# However, if you remove everything, the rebase will be aborted.#很重要的一点是你得注意这些提交的顺序与你通常通过log命令看到的是相反的。如果你运行log,你会看到下面这样的结果:
$ git log --pretty=format:"%h %s" HEAD~3..HEADa5f4a0d added cat-file310154e updated README formatting and added blamef7f3f6d changed my name a bit请注意这里的倒序。交互式的rebase给了你一个即将运行的脚本。它会从你在命令行上指明的提交开始(HEAD~3)然后自上至下重播每次提交里引入的变更。它将最早的列在顶上而不是最近的,因为这是第一个需要重播的。
edit f7f3f6d changed my name a bitpick 310154e updated README formatting and added blamepick a5f4a0d added cat-file当你保存并退出编辑器,Git会倒回至列表中的最后一次提交,然后把你送到命令行中,同时显示以下信息:
$ git rebase -i HEAD~3Stopped at 7482e0d... updated the gemspec to hopefully work betterYou can amend the commit now, with git commit --amendOnce you’re satisfied with your changes, run git rebase --continue这些指示很明确地告诉了你该干什么。输入
$ git commit --amend修改提交说明,退出编辑器。然后,运行
$ git rebase --continue这个命令会自动应用其他两次提交,你就完成任务了。如果你将更多行的 pick 改为 edit ,你就能对你想修改的提交重复这些步骤。Git每次都会停下,让你修正提交,完成后继续运行。
pick f7f3f6d changed my name a bitpick 310154e updated README formatting and added blamepick a5f4a0d added cat-file改为这个:
pick 310154e updated README formatting and added blamepick f7f3f6d changed my name a bit当你保存并退出编辑器,Git 将分支倒回至这些提交的父提交,应用310154e,然后f7f3f6d,接着停止。你有效地修改了这些提交的顺序并且彻底删除了”added cat-file”这次提交。
压制(Squashing)提交
交互式的衍合工具还可以将一系列提交压制为单一提交。脚本在 rebase 的信息里放了一些有用的指示:
## Commands:# p, pick = use commit# e, edit = use commit, but stop for amending# s, squash = use commit, but meld into previous commit## If you remove a line here THAT COMMIT WILL BE LOST.# However, if you remove everything, the rebase will be aborted.#如果不用”pick”或者”edit”,而是指定”squash”,Git 会同时应用那个变更和它之前的变更并将提交说明归并。因此,如果你想将这三个提交合并为单一提交,你可以将脚本修改成这样:
pick f7f3f6d changed my name a bitsquash 310154e updated README formatting and added blamesquash a5f4a0d added cat-file当你保存并退出编辑器,Git 会应用全部三次变更然后将你送回编辑器来归并三次提交说明。
# This is a combination of 3 commits.# The first commit's message is:changed my name a bit# This is the 2nd commit message:updated README formatting and added blame# This is the 3rd commit message:added cat-file当你保存之后,你就拥有了一个包含前三次提交的全部变更的单一提交。
pick f7f3f6d changed my name a bitedit 310154e updated README formatting and added blamepick a5f4a0d added cat-file然后,这个脚本就将你带入命令行,你重置那次提交,提取被重置的变更,从中创建多次提交。当你保存并退出编辑器,Git 倒回到列表中第一次提交的父提交,应用第一次提交(f7f3f6d),应用第二次提交(310154e),然后将你带到控制台。那里你可以用git reset HEAD^对那次提交进行一次混合的重置,这将撤销那次提交并且将修改的文件撤回。此时你可以暂存并提交文件,直到你拥有多次提交,结束后,运行git rebase --continue。
例如你刚刚推送了一个代码发布版本到产品环境中,对代码为什么会表现成那样百思不得其解。你回到你的代码中,还好你可以重现那个问题,但是找不到在哪里。你可以对代码执行bisect来寻找。首先你运行git bisect start启动,然后你用git bisect bad来告诉系统当前的提交已经有问题了。然后你必须告诉bisect已知的最后一次正常状态是哪次提交,使用git bisect good [good_commit]:
$ git bisect start$ git bisect bad$ git bisect good v1.0Bisecting: 6 revisions left to test after this[ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] error handling on repoGit 发现在你标记为正常的提交(v1.0)和当前的错误版本之间有大约12次提交,于是它检出中间的一个。在这里,你可以运行测试来检查问题是否存在于这次提交。如果是,那么它是在这个中间提交之前的某一次引入的;如果否,那么问题是在中间提交之后引入的。假设这里是没有错误的,那么你就通过git bisect good来告诉 Git 然后继续你的旅程:
$ git bisect goodBisecting: 3 revisions left to test after this[b047b02ea83310a70fd603dc8cd7a6cd13d15c04] secure this thing现在你在另外一个提交上了,在你刚刚测试通过的和一个错误提交的中点处。你再次运行测试然后发现这次提交是错误的,因此你通过git bisect bad来告诉Git:
$ git bisect badBisecting: 1 revisions left to test after this[f71ce38690acf49c1f3c9bea38e09d82a5ce6014] drop exceptions table这次提交是好的,那么 Git 就获得了确定问题引入位置所需的所有信息。它告诉你第一个错误提交的 SHA-1 值并且显示一些提交说明以及哪些文件在那次提交里修改过,这样你可以找出缺陷被引入的根源:
$ git bisect goodb047b02ea83310a70fd603dc8cd7a6cd13d15c04 is first bad commitcommit b047b02ea83310a70fd603dc8cd7a6cd13d15c04Author: PJ Hyett <pjhyett@example.com>Date: Tue Jan 27 14:48:32 2009 -0800 secure this thing:040000 040000 40ee3e7821b895e52c1695092db9bdc4c61d1730f24d3c6ebcfc639b1a3814550e62d60b8e68a8e4 M config当你完成之后,你应该运行git bisect reset来重设你的HEAD到你开始前的地方,否则你会处于一个诡异的地方:
$ git status# On branch master# Changes to be committed:# (use "git reset HEAD <file>..." to unstage)## new file: .gitmodules# new file: rack#首先你注意到有一个.gitmodules文件。这是一个配置文件,保存了项目 URL 和你拉取到的本地子目录
$ git merge origin/masterUpdating 0550271..85a3eeeFast forward rack | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)[master*]$ git status# On branch master# Changed but not updated:# (use "git add <file>..." to update what will be committed)# (use "git checkout -- <file>..." to discard changes in working directory)## modified: rack#你归并来的仅仅上是一个指向你的子模块的指针;但是它并不更新你子模块目录里的代码,所以看起来你的工作目录处于一个临时状态:
$ git submodule updatefatal: reference isn’t a tree: 6c5e70b984a60b3cecd395edd5b48a7575bf58e0Unable to checkout '6c5e70b984a60b3cecd395edd5ba7575bf58e0' in submodule path 'rack'你不得不去查看谁最后变更了子模块
$ git log -1 rackcommit 85a3eee996800fcfa91e2119372dd4172bf76678Author: Scott Chacon <schacon@gmail.com>Date: Thu Apr 9 09:19:14 2009 -0700 added a submodule reference I will never make public. hahahahaha!然后,你给那个家伙发电子邮件说他一通。
subject linewhat happened[ticket: X]# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit.# On branch master# Changes to be committed:# (use "git reset HEAD <file>..." to unstage)## modified: lib/test.rb#~~".git/COMMIT_EDITMSG" 14L, 297C如果你有特定的策略要运用在提交信息上,在系统上创建一个模板文件,设置 Git 默认使用它,这样当提交时,你的策略每次都会被运用。
$ git comgit: 'com' is not a git-command. See 'git --help'.Did you mean this? commit如果你把help.autocorrect设置成1(译注:启动自动修正),那么在只有一个命令被模糊匹配到的情况下,Git 会自动运行该命令。
$ git diffdiff --git a/chapter1.doc b/chapter1.docindex c1c8a0a..b93c9e4 100644--- a/chapter1.doc+++ b/chapter1.doc@@ -8,7 +8,8 @@ re going to cover Version Control Systems (VCS) and Git basics re going to cover how to get it and set it up for the first time if you don t already have it on your system. In Chapter Two we will go over basic Git usage - how to use Git for the 80%-s going on, modify stuff and contribute changes. If the book spontaneously+s going on, modify stuff and contribute changes. If the book spontaneously+Let's see if this works.Git 成功且简洁地显示出我增加的文本”Let’s see if this works”。虽然有些瑕疵,在末尾显示了一些随机的内容,但确实可以比较了。如果你能找到或自己写个Word到纯文本的转换器的话,效果可能会更好。strings可以在大部分Mac和Linux系统上运行,所以它是处理二进制格式的第一选择。
# 仅允许特定用户修改项目中的特定子目录def check_directory_perms access = get_acl_access_data('acl') # 检查是否有人在向他没有权限的地方推送内容 new_commits = `git rev-list #{$oldrev}..#{$newrev}`.split("\n") new_commits.each do |rev| files_modified = `git log -1 --name-only --pretty=format:'' #{rev}`.split("\n") files_modified.each do |path| next if path.size == 0 has_file_access = false access[$user].each do |access_path| if !access_path # 用户拥有完全访问权限 || (path.index(access_path) == 0) # 或者对此位置有访问权限 has_file_access = true end end if !has_file_access puts "[POLICY] You do not have access to push to #{path}" exit 1 end end end endcheck_directory_perms以上的大部分内容应该都比较容易理解。通过 git rev-list 获取推送到服务器内容的提交列表。然后,针对其中每一项,找出它试图修改的文件然后确保执行推送的用户对这些文件具有权限。一个不太容易理解的 Ruby 技巧石path.index(access_path) ==0 这句,它的返回真值如果路径以 access_path 开头——这是为了确保access_path 并不是只在允许的路径之一,而是所有准许全选的目录都在该目录之下。
[POLICY] Cannot push a non fast-forward referenceerror: hooks/update exited with error code 1error: hook declined to update refs/heads/master第一行是我们的脚本输出的,在往下是 Git 在告诉我们 update 脚本退出时返回了非零值因而推送遭到了拒绝。最后一点:
To git@gitserver:project.git ! [remote rejected] master -> master (hook declined)error: failed to push some refs to 'git@gitserver:project.git'我们将为每一个被挂 钩拒之门外的索引受到一条远程信息,解释它被拒绝是因为一个挂 钩的原因。
而且,如果那个 ref 字符串没有包含在任何的提交里,我们将看到前面脚本里输出的错误信息:
[POLICY] Your message is not formatted correctly又或者某人想修改一个自己不具备权限的文件然后推送了一个包含它的提交,他将看到类似的提示。比如,一个文档作者尝试推送一个修改到 lib 目录的提交,他会看到
[POLICY] You do not have access to push to lib/test.rb全在这了。从这里开始,只要 update 脚本存在并且可执行,我们的仓库永远都不会遭到回转或者包含不符合要求信息的提交内容,并且用户都被锁在了沙箱里面。
#!/usr/bin/env ruby$user = ENV['USER']# [ insert acl_access_data method from above ]# 只允许特定用户修改项目重特定子目录的内容def check_directory_perms access = get_acl_access_data('.git/acl') files_modified = `git diff-index --cached --name-only HEAD`.split("\n") files_modified.each do |path| next if path.size == 0 has_file_access = false access[$user].each do |access_path| if !access_path || (path.index(access_path) == 0) has_file_access = true end if !has_file_access puts "[POLICY] You do not have access to push to #{path}" exit 1 end endendcheck_directory_perms这和服务端的脚本几乎一样,除了两个重要区别。第一,ACL 文件的位置不同,因为这个脚本在当前工作目录运行,而非 Git 目录。ACL 文件的目录必须从
$ git commit -am 'Adding git-svn instructions to the README'[master 97031e5] Adding git-svn instructions to the README 1 files changed, 1 insertions(+), 1 deletions(-)接下来,可以将作出的修改推送到上游。值得注意的是,Subversion 的使用流程也因此改变了——你可以在离线状态下进行多次提交然后一次性的推送到 Subversion 的服务器上。向 Subversion 服务器推送的命令是git svn dcommit:
$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ... M README.txtCommitted r79 M README.txtr79 = 938b1a547c2cc92033b74d32030e86468294a5c8 (trunk)No changes between current HEAD and refs/remotes/trunkResetting to the latest refs/remotes/trunk所有在原 Subversion 数据基础上提交的 commit 会一一提交到 Subversion,然后你本地 Git 的 commit 将被重写,加入一个特别标识。这一步很重要,因为它意味着所有 commit 的 SHA-1 指都会发生变化。这也是同时使用 Git 和 Subversion 两种服务作为远程服务不是个好主意的原因之一。检视以下最后一个 commit,你会找到新添加的git-svn-id (译注:即本段开头所说的特别标识):
$ git log -1commit 938b1a547c2cc92033b74d32030e86468294a5c8Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>Date: Sat May 2 22:06:44 2009 +0000 Adding git-svn instructions to the README git-svn-id: file:///tmp/test-svn/trunk@79 4c93b258-373f-11de-be05-5f7a86268029注意看,原本以 97031e5 开头的 SHA-1 校验值在提交完成以后变成了 938b1a5 。如果既要向 Git 远程服务器推送内容,又要推送到 Subversion 远程服务器,则必须先向 Subversion 推送(dcommit),因为该操作会改变所提交的数据内容。
$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ...Merge conflict during commit: Your file or directory 'README.txt' is probably \out-of-date: resource out of date; try updating at /Users/schacon/libexec/git-\core/git-svn line 482为了解决该问题,可以运行 git svn rebase ,它会拉取服务器上所有最新的改变,再次基础上衍合你的修改:
$ git svn rebase M README.txtr80 = ff829ab914e8775c7c025d741beb3d523ee30bc4 (trunk)First, rewinding head to replay your work on top of it...Applying: first user change现在,你做出的修改都发生在服务器内容之后,所以可以顺利的运行 dcommit :
$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ... M README.txtCommitted r81 M README.txtr81 = 456cbe6337abe49154db70106d1836bc1332deed (trunk)No changes between current HEAD and refs/remotes/trunkResetting to the latest refs/remotes/trunk需要牢记的一点是,Git 要求我们在推送之前先合并上游仓库中最新的内容,而 git svn 只要求存在冲突的时候才这样做。假如有人向一个文件推送了一些修改,这时你要向另一个文件推送一些修改,那么dcommit 将正常工作:
$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ... M configure.acCommitted r84 M autogen.shr83 = 8aa54a74d452f82eee10076ab2584c1fc424853b (trunk) M configure.acr84 = cdbac939211ccb18aa744e581e46563af5d962d0 (trunk)W: d2f23b80f67aaaa1f6f5aaef48fce3263ac71a92 and refs/remotes/trunk differ, \ using rebase::100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 \ 015e4c98c482f0fa71e4d5434338014530b37fa6 M autogen.shFirst, rewinding head to replay your work on top of it...Nothing to do.这一点需要牢记,因为它的结果是推送之后项目处于一个不完整存在与任何主机上的状态。如果做出的修改无法兼容但没有产生冲突,则可能造成一些很难确诊的难题。这和使用 Git 服务器是不同的——在 Git 世界里,发布之前,你可以在客户端系统里完整的测试项目的状态,而在 SVN 永远都没法确保提交前后项目的状态完全一样。
及时还没打算进行提交,你也应该用这个命令从 Subversion 服务器拉取最新修改。sit svn fetch 能获取最新的数据,不过git svn rebase 才会在获取之后在本地进行更新 。
$ git svn rebase M generate_descriptor_proto.shr82 = bd16df9173e424c6f52c337ab6efa7f7643282f1 (trunk)First, rewinding head to replay your work on top of it...Fast-forwarded master to refs/remotes/trunk.不时地运行一下 git svn rebase 可以确保你的代码没有过时。不过,运行该命令时需要确保工作目录的整洁。如果在本地做了修改,则必须在运行git svn rebase 之前或暂存工作,或暂时提交内容——否则,该命令会发现衍合的结果包含着冲突因而终止。 作者: compare2000 时间: 2014-05-22 17:56
Git 分支问题
习惯了 Git 的工作流程以后,你可能会创建一些特性分支,完成相关的开发工作,然后合并他们。如果要用 git svn 向 Subversion 推送内容,那么最好是每次用衍 合来并入一个单一分支,而不是直接合并。使用衍合的原因是 Subversion 只有一个线性的历史而不像 Git 那样处理合并,所以 Git svn 在把快照转换为 Subversion 的 commit 时只能包含第一个祖先。
$ git svn dcommitCommitting to file:///tmp/test-svn/trunk ... M CHANGES.txtCommitted r85 M CHANGES.txtr85 = 4bfebeec434d156c36f2bcd18f4e3d97dc3269a2 (trunk)No changes between current HEAD and refs/remotes/trunkResetting to the latest refs/remotes/trunkCOPYING.txt: locally modifiedINSTALL.txt: locally modified M COPYING.txt M INSTALL.txtCommitted r86 M INSTALL.txt M COPYING.txtr86 = 2647f6b86ccfcaad4ec58c520e369ec81f7c283c (trunk)No changes between current HEAD and refs/remotes/trunkResetting to the latest refs/remotes/trunk在一个包含了合并历史的分支上使用 dcommit 可以成功运行,不过在 Git 项目的历史中,它没有重写你在 experiment 分支中的两个 commit ——另一方面,这些改变却出现在了 SVN 版本中同一个合并 commit 中。
commit 37efa680e8473b615de980fa935944215428a35aAuthor: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>Date: Sun May 3 00:12:22 2009 +0000 fixed install - go to trunk git-svn-id: https://my-project.googlecode.com/svn/trunk@94 4c93b258-373f-11de- be05-5f7a86268029现在是这样:
commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2Author: Scott Chacon <schacon@geemail.com>Date: Sun May 3 00:12:22 2009 +0000 fixed install - go to trunk不仅作者一项干净了不少,git-svn-id 也就此消失了。
$ git log -2commit 10a16d60cffca14d454a15c6164378f4082bc5b0Author: Perforce staff <support@perforce.com>Date: Thu Aug 19 10:18:45 2004 -0800 Drop 'rc3' moniker of jam-2.5. Folded rc2 and rc3 RELNOTES into the main part of the document. Built new tar/zip balls. Only 16 months later.commit 2b6c6db311dd76c34c66ec1c40a49405e6b527b2Author: Richard Geiger <rmg@perforce.com>Date: Tue Apr 22 20:51:34 2003 -0800 Update derived jamgram.c至此导入已经完成,可以开始向新的 Git 服务器推送了。
last_mark = nil# 循环遍历所有目录Dir.chdir(ARGV[0]) do Dir.glob("*").each do |dir| next if File.file?(dir) # 进入目标目录 Dir.chdir(dir) do last_mark = print_export(dir, last_mark) end endend我们在每一个目录里运行 print_export ,它会取出上一个快照的索引和标记并返回本次快照的索引和标记;由此我们就可以正确的把二者连接起来。”标记(mark)” 是fast-import 中对 commit 标识符的叫法;在创建 commit 的同时,我们逐一赋予一个标记以便以后在把它连接到其他 commit 时使用。因此,在print_export 方法中要做的第一件事就是根据目录名生成一个标记:
mark = convert_dir_to_mark(dir)实现该函数的方法是建立一个目录的数组序列并使用数组的索引值作为标记,因为标记必须是一个整数。这个方法大致是这样的:
$marks = []def convert_dir_to_mark(dir) if !$marks.include?(dir) $marks << dir end ($marks.index(dir) + 1).to_send有了整数来代表每个 commit,我们现在需要提交附加信息中的日期。由于日期是用目录名表示的,我们就从中解析出来。print_export 文件的下一行将是:
date = convert_dir_to_date(dir)而 convert_dir_to_date 则定义为
def convert_dir_to_date(dir) if dir == 'current' return Time.now().to_i else dir = dir.gsub('back_', '') (year, month, day) = dir.split('_') return Time.local(year, month, day).to_i endend它为每个目录返回一个整型值。提交附加信息里最后一项所需的是提交者数据,我们在一个全局变量中直接定义之:
puts 'deleteall'Dir.glob("**/*").each do |file| next if !File.file?(file) inline_data(file)end注意:由于很多系统把每次修订看作一个 commit 到另一个 commit 的变化量,fast-import 也可以依据每次提交获取一个命令来指出哪些文件被添加,删除或者修改过,以及修改的内容。我们将需要计算快照之间的差别并且仅仅给出这项数据,不过该做法要复杂很多——还如不直接把所有数据丢给 Git 然它自己搞清楚。假如前面这个方法更适用于你的数据,参考fast-import 的 man 帮助页面来了解如何以这种方式提供数据。
$ git log -2commit 10bfe7d22ce15ee25b60a824c8982157ca593d41Author: Scott Chacon <schacon@example.com>Date: Sun May 3 12:57:39 2009 -0700 imported from currentcommit 7e519590de754d079dd73b44d695a42c9d2df452Author: Scott Chacon <schacon@example.com>Date: Tue Feb 3 01:00:00 2009 -0700 imported from back_2009_02_03就它了——一个干净整洁的 Git 仓库。需要注意的是此时没有任何内容被检出——刚开始当前目录里没有任何文件。要获取它们,你得转到 master 分支的所在:
$ ls$ git reset --hard masterHEAD is now at 10bfe7d imported from current$ lsfile.rb libfast-import 还可以做更多——处理不同的文件模式,二进制文件,多重分支与合并,标签,进展标识等等。一些更加复杂的实例可以在 Git 源码的contib/fast-import 目录里找到;其中较为出众的是前面提过的 git-p4 脚本。
$ git log --pretty=oneline master1a410efbd13591db07496601ebc7a059dd55cfe9 third commitcac0cab538b970a37ea1e769cbbde608743bc96d second commitfdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit当然,我们并不鼓励你直接修改这些引用文件。如果你确实需要更新一个引用,Git 提供了一个安全的命令 update-ref:
$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9基本上 Git 中的一个分支其实就是一个指向某个工作版本一条 HEAD 记录的指针或引用。你可以用这条命令创建一个指向第二次提交的分支:
你也可以手动编辑这个文件,但是同样有一个更安全的方法可以这样做:symbolic-ref。你可以用下面这条命令读取 HEAD 的值:
$ git symbolic-ref HEADrefs/heads/master你也可以设置 HEAD 的值:
$ git symbolic-ref HEAD refs/heads/test$ cat .git/HEADref: refs/heads/test但是你不能设置成 refs 以外的形式:
$ git symbolic-ref HEAD testfatal: Refusing to point HEAD outside of refs/Tags
你刚刚已经重温过了 Git 的三个主要对象类型,现在这是第四种。Tag 对象非常像一个 commit 对象——包含一个标签,一组数据,一个消息和一个指针。最主要的区别就是 Tag 对象指向一个 commit 而不是一个 tree。它就像是一个分支引用,但是不会变化——永远指向同一个 commit,仅仅是提供一个更加友好的名字。
=> GET objects/ca/82a6dff817ec66f44342007202690a93763949(179 bytes of binary data)然后你取回了这个对象 - 这在服务端是一个松散格式的对象,你使用的是静态的 HTTP GET 请求获取的。可以使用 zlib 解压缩它,去除其头部,查看它的 commmit 内容:
$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949 tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 author Scott Chacon 1205815931 -0700 committer Scott Chacon 1240030591 -0700 changed the version number 这样,就得到了两个需要进一步获取的对象 - cfda3b 是这个 commit 对象所对应的 tree 对象,和 085bb3 是它的父对象;
=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7(179 bytes of data)这样就取得了这它的下一步 commit 对象,再抓取 tree 对象:
=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf(404 - Not Found)Oops - 看起来这个 tree 对象在服务端并不以松散格式对象存在,所以得到了404响应,代表在HTTP服务端没有找到该对象。这有好几个原因 - 这个对象可能在替代仓库里面,或者在打包文件里面, Git 会首先检查任何列出的替代仓库:
=> GET objects/info/http-alternates(empty file)如果这返回了几个替代仓库列表,那么它会去那些地方检查松散格式对象和文件 - 这是一种在软件分叉之间共享对象以节省磁盘的好方法。然而,在这个例子中,没有替代仓库。所以你所需要的对象肯定在某个打包文件中。要检查服务端有哪些打包格式文件,你需要获取objects/info/packs 文件,这里面包含有打包文件列表(是的,它也是被 update-server-info 所生成的);
=> GET objects/info/packsP pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack这里服务端只有一个打包文件,所以你要的对象显然就在里面。但是你可以先检查它的索引文件以确认。这在服务端有多个打包文件时也很有用,因为这样就可以先检查你所需要的对象空间是在哪一个打包文件里面了:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx(4k of binary data)现在你有了这个打包文件的索引,你可以看看你要的对象是否在里面 - 因为索引文件列出了这个打包文件所包含的所有对象的SHA值,和该对象存在于打包文件中的偏移量,所以你只需要简单地获取整个打包文件:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack(13k of binary data)现在你也有了这个 tree 对象,你可以继续在 commit 对象上漫游。它们全部都在这个你已经下载到的打包文件里面,所以你不用继续向服务端请求更多下载了。 在这完成之后,由于下载开始时已探明HEAD引用是指向master 分支, Git 会将它检出到工作目录。
整个过程看起来就像这样:
$ git clone http://github.com/schacon/simplegit-progit.gitInitialized empty Git repository in /private/tmp/simplegit-progit/.git/got ca82a6dff817ec66f44342007202690a93763949walk ca82a6dff817ec66f44342007202690a93763949got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7Getting alternates list for http://github.com/schacon/simplegit-progit.gitGetting pack list for http://github.com/schacon/simplegit-progit.gitGetting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835 which contains cfda3bf379e4f8dba8717dee55aab78aef7f4dafwalk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7walk a11bef06a3f659402fe7563abf99ad00de2209e6智能协议
这个HTTP方法是很简单但效率不是很高。使用智能协议是传送数据的更常用的方法。这些协议在远端都有Git智能型进程在服务 - 它可以读出本地数据并计算出客户端所需要的,并生成合适的数据给它,这有两类传输数据的进程:一对用于上传数据和一对用于下载。
下面的示例演示了对 test 仓库主分支进行 hard-reset 到一个老版本的 commit 的操作,然后恢复丢失的 commit 。首先查看一下当前的仓库状态:
$ git log --pretty=onelineab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit484a59275031909e19aadb7c92262719cfcdf19a added repo.rb1a410efbd13591db07496601ebc7a059dd55cfe9 third commitcac0cab538b970a37ea1e769cbbde608743bc96d second commitfdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit接着将 master 分支移回至中间的一个 commit:
$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9HEAD is now at 1a410ef third commit$ git log --pretty=oneline1a410efbd13591db07496601ebc7a059dd55cfe9 third commitcac0cab538b970a37ea1e769cbbde608743bc96d second commitfdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit这样就丢弃了最新的两个 commit ── 包含这两个 commit 的分支不存在了。现在要做的是找出最新的那个 commit 的 SHA,然后添加一个指它它的分支。关键在于找出最新的 commit 的 SHA ── 你不大可能记住了这个 SHA,是吧?
命令:du 查看目录大小
du -sk . 显示单位为KB
du -sh . 显示单位会自动调整
du -sh * 显示当前目录下的所有文件和目录大小
命令:find 查找
find命令参数中的数字n含义: +n 表示 “> n”; -n 表示 “< n”; n 表示 “= n”
如, find . -mmin +2 表示查找当前目录下、修改时间大于2分钟的文件(即,2分钟之前的文件)
find . -mmin -2 表示查找当前目录下、修改时间小于2分钟的文件(即,2分钟之后的文件)
-amin n 文件访问时间在n 分钟前
-atime n 文件访问时间在n 天前
-mmin n 文件修改时间在n 分钟前
-mtime n 文件修改时间在n 天前
-size n[bckw] 文件大小,b 表示单位为block(块,512字节),默认单位;
c 表示单位为字节; k 表示单位为KB; w 表示单位为2个字节
-type c 指定文件类型,c取值: d 表示目录; f 表示普通文件; l 表示link文件; s 表示sock; p 表示pipe
命令:date 时间显示与设置
date MMDDhhmm[[cc]YY][.ss] 设置时间,如date 12171830.00 设置当前时间为12月17日18点30分0秒(年份不变)
date '+%Y-%m-%d %H:%M:%S.%N' 或者 date '+%F %X.%N'
年 月 日 时 分 秒 纳秒
date --date '2 days ago' +%F 获取2天前的日期
date --date '2 days' +%F 获取2天后的日期
指定特定日期的格式: date -d 'string'
其中,string由以下几个部分组成(各个部分以空格分隔):
1,calendar date time 表示日期,有多种格式,但用格式 yyyy-mm-dd 即可。
2,time of day 表示时间,格式:HH:MM[:SS[.XXXXXX]][am|pm][SHHMM],其中,S表示+或-,HH和MM分别表示小时,分钟
如,12:20+0210 表示10:10 ,12:20-0210表示 14:30
3,time zone 表示时区,如UTC,CST等
4,day of the week 表示星期几
5,relative 调整日期,格式: [+|-]整数 单位 [ago], 或者直接用如下字符串:yesterday,tomorrow,now,today,this
单位有:失真的单位(year,month)精确的单位(fortnight(2周),week,day,hour,minute(min),second(sec))
这些单位后可以加(s),也可以不加
6,pure numbers 精确的date信息,当“calendar date time”未指定时,则日期从此处读取,格式:YYYYMMDD
当“time of day”未指定时,则时间从此处读取,格式:HHMM
将指定日期转换为秒: date -d '2008-10-12' +%s # 结果为:2008-10-12与1970-01-01的时间差(单位:秒)
获取2009-11-12的后3天的日期: date -d '2009-11-12 UTC 3 days' +%F # days 可以用hours,seconds,minutes
获取2009-11-12的前3天的日期: date -d '2009-11-12 UTC -3 days' +%F # days 可以用hours,seconds,minutes
获取timestamp时间对应的当前日期:time=1331974800
diff=$(echo "$(date +%s) - $time"|bc)
date -d "$diff seconds ago" +%F.%X
命令: tethereal,tcpdump 抓包命令
tethereal -ni bond0 host 10.164.79.105 and port 554
tcpdump -i any host 10.164.79.105
tcpdump -X -s 0 'dst port 554 and ip[2:2] > 60' ip[2:2] 表示ip报文的第2个字节开始,取2个字节的值
tcpdump -i any dst host 125.88.104.10 and ! \( 125.88.104.6 or 125.88.104.24 \) and dst port 60000
类型的关键字: host,net,port
传输的方向: src,dst,dst or src, dst and src
协议: tcp,udp,ip,arp,rarp
逻辑运算符: and,&& ; or ,|| ; not ,!,
命令:snmpwalk snmp查看OID值
snmpwalk -v 3 -l authPriv -u p2eAy1H0MqSbGT6oDuzXC3Wv -a MD5 -A oRghaPqum1v7ZCHKxFey3sl5 -x DES -X oRghaPqum1v7ZCHKxFey3sl5 124.92.253.45 1.3.6.1.4.1.2011.20.3.12.1.6.0
snmpwalk -v 2 -c public 125.88.105.225:161 1.3.6.1.2.1.2.2.1.5
命令:tar 打包,解压
c 表示创建; x 表示解压; t 表示查看;
tar -zcvf mytar.tar.gz myfiles/* 打包并压缩
tar -zcvf mytar.tar.gz -X temp_file myfiles/* 打包(不包括temp_file文件中列出的文件)并压缩
tar -zcvf mytar.tar.gz --exclude=abc/* . 打包当前目录下所有文件(但不包括abc目录下的文件)
tar -zxvf mytar.tar.gz -T temp_file temp_file文件中包含的文件都会被解压
tar -zxvf mytar.tar.gz filename1 filename2 ... 解压mytar.tar.gz 包中指定的文件:filename1,filename2...
tar -C/home/zcj -zcvf mem_check.tar.gz mem_check/ 将当前目录下的mem_check打包,并将包保存到/home/zcj目录下
tar -C/home/zcj -zxvf mem_check.tar.gz 将当前目录下的mem_check.tar.gz包解压到/home/zcj目录下
/proc/sys/net/ipv4/conf/all/arp_ignore
arp_ignore - INTEGER Define different modes for sending replies in response to received ARP requests that resolve local target IP addresses: 0 - (default): reply for any local target IP address, configured on any interface
1 - reply only if the target IP address is local address configured on the incoming interface
2 - reply only if the target IP address is local address configured on the incoming interface and both with the sender's IP address are part from same subnet on this interface
3 - do not reply for local addresses configured with scope host, only resolutions for global and link addresses are replied
4-7 - reserved
8 - do not reply for all local addresses The max value from conf/{all,interface}/arp_ignore is used when ARP request is received on the {interface}
tc命令
在bond0上配置流量整形:
tc qdisc add dev bond0 root tbf rate 2000mbit burst 2000kb limit 50mb
删除bond0上的流量整形:
tc qdisc del dev bond0 root
查询:
tc qdisc
raw命令
raw -qa 查看所有裸设备的绑定号,如:
MEM2:/dev/mapper # raw -qa
/dev/raw/raw1: bound to major 253, minor 0
/dev/raw/raw2: bound to major 253, minor 1
/dev/raw/raw3: bound to major 253, minor 2
然后用ls -l 命令查看逻辑卷,就可以知道逻辑卷对应哪个裸设备,如:
MEM2:/dev/mapper # ls -l oramem_dg-lv_*
brw-rw---- 1 oracle oinstall 253, 0 Oct 13 16:22 oramem_dg-lv_systemtbs
brw-rw---- 1 oracle oinstall 253, 1 Oct 13 16:22 oramem_dg-lv_sysauxtbs
brw-rw---- 1 oracle oinstall 253, 2 Oct 13 16:22 oramem_dg-lv_usertbs
#watch可以同时运行多个命令,命令间用分号分隔。
#以下命令监控磁盘的使用状况,以及当前目录下文件的变化状况,包括文件的新增、删除和文件修改日期的更新等。
/> watch -d -n 1 'df -h; ls -l'
Every 1.0s: df -h; ls -l Sat Nov 12 12:55:00 2011
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 5.8G 3.3G 2.2G 61% /
tmpfs 504M 420K 504M 1% /dev/shm
total 20
-rw-r--r--. 1 root root 10530 Nov 11 23:08 test.tar.bz2
-rw-r--r--. 1 root root 183 Nov 11 08:02 users
-rw-r--r--. 1 root root 279 Nov 11 08:45 users2
此时通过另一个Linux控制台窗口,在watch监视的目录下,如/home/stephen/test,执行下面的命令
/> touch aa #在执行该命令之后,另一个执行watch命令的控制台将有如下变化
Every 1.0s: df -h; ls -l Sat Nov 12 12:57:08 2011
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 5.8G 3.3G 2.2G 61% /
tmpfs 504M 420K 504M 1% /dev/shm
total 20
-rw-r--r--. 1 root root 0 Nov 12 12:56 aa
-rw-r--r--. 1 root root 0 Nov 12 10:02 datafile3
-rw-r--r--. 1 root root 10530 Nov 11 23:08 test.tar.bz2
-rw-r--r--. 1 root root 183 Nov 11 08:02 users
-rw-r--r--. 1 root root 279 Nov 11 08:45 users2
其中黄色高亮的部分,为touch aa命令执行之后watch输出的高亮变化部分。
123456789 order.out.log 8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.99 8:23:45 112, 1, Joy of Clojure, Hardcover, 29.99 8:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.99 order.in.log 8:22:20 111, Order Complete 8:23:50 112, Order sent to fulfillment 8:24:20 113, Refund sent to processing
cat
cat – 连接文件,并输出结果
cat 命令非常的简单,你从下面的例子可以看到。
1234 jfields$ cat order.out.log 8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.998:23:45 112, 1, Joy of Clojure, Hardcover, 29.998:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.99
就像它的说明描述的,你可以用它来连接多个文件。
1234567 jfields$ cat order.* 8:22:20 111, Order Complete8:23:50 112, Order sent to fulfillment8:24:20 113, Refund sent to processing8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.998:23:45 112, 1, Joy of Clojure, Hardcover, 29.998:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.99
如果你想看这些log文件的内容,你可以把它们连接起来并输出到标准输出上,就是上面的例子展示的。这很有用,但输出的内容可以更有逻辑些。
sort
sort – 文件里的文字按行排序
此时sort命令显然是你最佳的选择。
1234567 jfields$ cat order.* | sort8:22:19 111, 1, Patterns of Enterprise Architecture, Kindle edition, 39.998:22:20 111, Order Complete8:23:45 112, 1, Joy of Clojure, Hardcover, 29.998:23:50 112, Order sent to fulfillment8:24:19 113, -1, Patterns of Enterprise Architecture, Kindle edition, 39.998:24:20 113, Refund sent to processing
就像上面例子显示的,文件里的数据已经经过排序。对于一些小文件,你可以读取整个文件来处理它们,然而,真正的log文件通常有大量的内容,你不能不考虑这个情况。此时你应该考虑过滤出某些内容,把cat、sort后的内容通过管道传递给过滤工具。
grep
grep, egrep, fgrep – 打印出匹配条件的文字行
假设我们只对Patterns of Enterprise Architecture这本书的订单感兴趣。使用grep,我们能限制只输出含有Patterns字符的订单。
如何在RED Hat Linux 防一用户两个人用登陆
===================================
直接在shell里面,就是你的命令行下面输入
echo "* hard maxlogins 2" >> /etc/security/limits.conf
然后回车,这个命令会将内容
* hard maxlogins 2
写到文件/etc/security/limits.conf中
//
//
// be sorted
// workspace array
// current number of elements in y
int i;
for (i = 0; i < num_inputs; i++)
}
int k;
void insert(int new_y)
if (num_y = 0) { // y empty so far, easy case
return;
// need to insert just before the first y
for (j = 0; j < num_y; j++) {
// shift y[j], y[j+1],... rightward
return;
}
void process_data()
for (num_y = 0; num_y < num_inputs; num_y++)
// among y[0],...,y[num_y-1]
}
int i;
%d\n",y[i]);
int main(int argc, char ** argv)
process_data();
}我们编译一下:
gcc -g -Wall -o insert_sort ins.c
注意我们要使用-g选项告诉编译器在可执行文件中保存符号表——我们程序中变量和代码对应的内存地址。
现在我们开始运行一下,我们使用“从小处开始准则”,首先使用两个数进行测试:
./insert_sort 12 5
我们发现该程序没有退出,貌似进入了一个死循环。我们开始使用ddd调试这个程序:
ddd insert_sort
运行程序,传入两个参数:
r 12 5
此时程序一直运行不退出,按Ctrl+C暂停程序的执行
(GDB) r 12 5
^C
Program received signal SIGINT, Interrupt.
0x080484ff in insert (new_y=3) at insert_sort.c:45
/home/gnuhpc/MyCode/Debug/Chapter_01/insert_sort/pg_019/insert_sort.c:45:939:beg:0x80484ff
(GDB)
#等待一组输入,每个单词之间使用空格隔开,直到回车结束,并分别将单词依次赋值给这三个读入变量。
/> read one two three
1 2 3 #在控制台输入1 2 3,它们之间用空格隔开。
/> echo "one = $one, two = $two, three = $three"
one = 1, two = 2, three = 3
/> read #等待控制台输入,并将结果赋值给特定内置变量REPLY。
This is REPLY #在控制台输入该行。
/> echo $REPLY #打印输出特定内置变量REPLY,以确认是否被正确赋值。
This is REPLY
/> read -p "Enter your name: " #输出"Enter your name: "文本提示,同时等待输入,并将结果赋值给REPLY。
Enter you name: stephen #在提示文本之后输入stephen
/> echo $REPLY
stephen
#等待控制台输入,并将输入信息视为数组,赋值给数组变量friends,输入信息用空格隔开数组的每个元素
/> read -a friends
Tim Tom Helen
/> echo "I have ${#friends} friends"
I have 3 friends
/> echo "They are ${friends[0]}, ${friends[1]} and ${friends[2]}."
They are Tim, Tom and Helen.
3. 流程控制语句:
if语句格式如下:
#if语句的后面是Shell命令,如果该命令执行成功返回0,则执行then后面的命令。
if command
then
command
command
fi
#用test命令测试其后面expression的结果,如果为真,则执行then后面的命令。
if test expression
then
command
fi
#下面的格式和test expression等同
if [ string/numeric expression ]
then
command
fi
#下面的两种格式也可以用于判断语句的条件表达式,而且它们也是目前比较常用的两种。
if [[ string expression ]]
then
command
fi
if (( numeric expression )) #let表达式
then
command
fi
见如下示例:
/> cat > test1.sh #从命令行直接编辑test1.sh文件。
echo -e "Are you OK(y/n)? \c"
read answer
#这里的$answer变量必须要用双引号扩住,否则判断将失败。当变量$answer等于y或Y时,支持下面的echo命令。
if [ "$answer" = y -o "$answer" = Y ]
then
echo "Glad to see it."
fi
CTRL+D
/> . ./test1.sh
Are you OK(y/n)? y
Glad to see it.
上面的判断还可以替换为:
/> cat > test2.sh
echo -e "Are you OK(y/n or Maybe)? \c"
read answer
# [[ ]]复合命令操作符允许其中的表达式包含元字符,这里输入以y或Y开头的任意单词,或Maybe都执行then后面的echo。
if [[ $answer == [yY]* || $answer = Maybe ]]
then
echo "Glad to hear it.
fi
CTRL+D
/> . ./test2.sh
Are you OK(y/n or Maybe)? yes
Glad to hear it.
下面的例子将使用Shell中的扩展通配模式。
/> shopt -s extglob #打开该扩展模式
/> answer="not really"
/> if [[ $answer = [Nn]o?( way |t really) ]]
> then
> echo "I am sorry."
> fi
I am sorry.
对于本示例中的扩展通配符,这里需要给出一个具体的解释。[Nn]o匹配No或no,?( way|t really)则表示0个或1个( way或t really),因此answer变量匹配的字符串为No、no、Not really、not really、No way、no way。
下面的示例使用了let命令操作符,如:
/> cat > test3.sh
if (( $# != 2 )) #等同于 [ $# -ne 2 ]
then
echo "Usage: $0 arg1 arg2" 1>&2
exit 1 #exit退出值为0-255之间,只有0表示成功。
fi
if (( $1 < 0 || $1 > 30 )) #等同于 [ $1 -lt 0 -o $1 -gt 30 ]
then
echo "arg1 is out of range."
exit 2
fi
if (( $2 <= 20 )) #等同于 [ $2 -le 20 ]
then
echo "arg2 is out of range."
fi
CTRL+D
/> sh ./test3.sh
Usage: ./test3.sh arg1 arg2
/> echo $? #Shell脚本的退出值为exit的参数值。
1
/> sh ./test3.sh 40 30
arg1 is out of range.
/> echo $?
2
下面的示例为如何在if的条件表达式中检验空变量:
/> cat > test4.sh
if [ "$name" = "" ] #双引号就表示空字符串。
then
echo "name is null."
fi
CTRL+D
/> . ./test4.sh
name is null.
if/elif/else语句的使用方式和if语句极为相似,相信有编程经验的人都不会陌生,这里就不再赘述了,其格式如下:
if command
then
command
elif command
then
command
else
command
fi
见如下示例脚本:
/> cat > test5.sh
echo -e "How old are you? \c"
read age
if [ $age -lt 0 -o $age -gt 120 ] #等同于 (( age < 0 || age > 120 ))
then
echo "You are so old."
elif [ $age -ge 0 -a $age -le 12 ] #等同于 (( age >= 0 && age <= 12 ))
then
echo "You are child."
elif [ $age -ge 13 -a $age -le 19 ] #等同于 (( age >= 13 && age <= 19 ))
then
echo "You are 13--19 years old."
elif [ $age -ge 20 -a $age -le 29 ] #等同于 (( age >= 20 && age <= 29 ))
then
echo "You are 20--29 years old."
elif [ $age -ge 30 -a $age -le 39 ] #等同于 (( age >= 30 && age <= 39 ))
then
echo "You are 30--39 years old."
else
echo "You are above 40."
fi
CTRL+D
/> . ./test5.sh
How old are you? 50
You are above 40.
case语句格式如下:
case variable in
value1)
command
;; #相同于C语言中case语句内的break。
value2)
command
;;
*) #相同于C语言中switch语句内的default
command
;;
esac
见如下示例脚本:
/> cat > test6.sh
#!/bin/sh
echo -n "Choose a color: "
read color
case "$color" in
[Bb]l??)
echo "you select blue color."
;;
[Gg]ree*)
echo "you select green color."
;;
red|orange)
echo "you select red or orange."
;;
*)
echo "you select other color."
;;
esac
echo "Out of case command."
/> . ./test6.sh
Choose a color: green
you select green color.
Out of case command.
4. 循环语句:
Bash Shell中主要提供了三种循环方式:for、while和until。
for循环声明格式:
for variable in word_list
do
command
done
见如下示例脚本:
/> cat > test7.sh
for score in math english physics chemist #for将循环读取in后面的单词列表,类似于Java的for-each。
do
echo "score = $score"
done
echo "out of for loop"
CTRL+D
/> . ./test7.sh
score = math
score = english
score = physics
score = chemist
out of for loop
/> cat > mylist #构造数据文件
tom
patty
ann
jake
CTRL+D
/> cat > test8.sh
#!/bin/sh
for person in $(cat mylist) #for将循环读取cat mylist命令的执行结果。
do
echo "person = $person"
done
echo "out of for loop."
CTRL+D
/> . ./test8.sh
person = tom
person = patty
person = ann
person = jake
out of for loop.
/> cat > test9.sh
for file in test[1-8].sh #for将读取test1-test8,后缀为.sh的文件
do
if [ -f $file ] #判断文件在当前目录是否存在。
then
echo "$file exists."
fi
done
CTRL+D
/> . ./test9.sh
test2.sh exists.
test3.sh exists.
test4.sh exists.
test5.sh exists.
test6.sh exists.
test7.sh exists.
test8.sh exists.
/> cat > test10.sh
for name in $* #读取脚本的命令行参数数组,还可以写成for name的简化形式。
do
echo "Hi, $name"
done
CTRL+D
/> . ./test10.sh stephen ann
Hi, stephen
Hi, ann
while循环声明格式:
while command #如果command命令的执行结果为0,或条件判断为真时,执行循环体内的命令。
do
command
done
见如下示例脚本:
/> cat > test1.sh
num=0
while (( num < 10 )) #等同于 [ $num -lt 10 ]
do
echo -n "$num "
let num+=1
done
echo -e "\nHere's out of loop."
CTRL+D
/> . ./test1.sh
0 1 2 3 4 5 6 7 8 9
Here's out of loop.
/> cat > test2.sh
go=start
echo Type q to quit.
while [[ -n $go ]] #等同于[ -n "$go" ],如使用该风格,$go需要被双引号括起。
do
echo -n How are you.
read word
if [[ $word == [Qq] ]] #等同于[ "$word" = Q -o "$word" = q ]
then
echo Bye.
go= #将go变量的值置空。
fi
done
CTRL+D
/> . ./test2.sh
How are you. Hi
How are you. q
Bye.
until循环声明格式:
until command #其判断条件和while正好相反,即command返回非0,或条件为假时执行循环体内的命令。
do
command
done
见如下示例脚本:
/> cat > test3.sh
until who | grep stephen #循环体内的命令将被执行,直到stephen登录,即grep命令的返回值为0时才退出循环。
do
sleep 1
echo "Stephen still doesn't login."
done
CTRL+D
shift命令声明格式:shift [n]
shift命令用来把脚本的位置参数列表向左移动指定的位数(n),如果shift没有参数,则将参数列表向左移动一位。一旦移位发生,被移出列表的参数就被永远删除了。通常在while循环中,shift用来读取列表中的参数变量。
见如下示例脚本:
/> set stephen ann sheryl mark #设置4个参数变量。
/> shift #向左移动参数列表一次,将stephen移出参数列表。
/> echo $*
ann sheryl mark
/> shift 2 #继续向左移动两位,将sheryl和ann移出参数列表
/> echo $*
mark
/> shift 2 #继续向左移动两位,由于参数列表中只有mark了,因此本次移动失败。
/> echo $*
mark
/> cat > test4.sh
while (( $# > 0 )) #等同于 [ $# -gt 0 ]
do
echo $*
shift
done
CTRL+D
/> . ./test4.sh a b c d e
a b c d e
b c d e
c d e
d e
e
break命令声明格式:break [n]
和C语言不同的是,Shell中break命令携带一个参数,即可以指定退出循环的层数。如果没有指定,其行为和C语言一样,即退出最内层循环。如果指定循环的层数,则退出指定层数的循环体。如果有3层嵌套循环,其中最外层的为1,中间的为2,最里面的是3。
见如下示例脚本:
/> cat > test5.sh
while true
do
echo -n "Are you ready to move on?"
read answer
if [[ $answer == [Yy] ]]
then
break
else
echo "Come on."
fi
done
echo "Here we are."
CTRL+D
/> . ./test5.sh
Are you ready to move on? y
Here we are
continue命令声明格式:continue [n]
和C语言不同的是,Shell中continue命令携带一个参数,即可以跳转到指定层级的循环顶部。如果没有指定,其行为和C语言一样,即跳转到最内层循环的顶部。如果指定循环的层数,则跳转到指定层级循环的顶部。如果有3层嵌套循环,其中最外层的为3,中间的为2,最里面的是1。
/> cat maillist #测试数据文件maillist的内容为以下信息。
stephen
ann
sheryl
mark
/> cat > test6.sh
for name in $(cat maillist)
do
if [[ $name == stephen ]]; then
continue
else
echo "Hello, $name."
fi
done
CTRL+D
/> . ./test6.sh
Hello, ann.
Hello, sheryl.
Hello, mark.
/> cat > test8.sh
for i in 9 7 2 3 5 4
do
echo $i
done | sort -n #直接将echo的输出通过管道重定向sort命令。
CTRL+D
/> . ./test8.sh
2
3
4
5
7
9
5. IFS和循环:
Shell的内部域分隔符可以是空格、制表符和换行符。它可以作为命令的分隔符用在例如read、set和for等命令中。如果在列表中使用不同的分隔符,用户可以自己定义这个符号。在修改之前将IFS原始符号的值保存在另外一个变量中,这样在需要的时候还可以还原。
见如下示例脚本:
/> cat > test9.sh
names=Stephen:Ann:Sheryl:John #names变量包含的值用冒号分隔。
oldifs=$IFS #保留原有IFS到oldifs变量,便于后面的还原。
IFS=":"
for friends in $names #这是遍历以冒号分隔的names变量值。
do
echo Hi $friends
done
IFS=$oldifs #将IFS还原为原有的值。
set Jerry Tom Angela
for classmates in $* #再以原有IFS的值变量参数列表。
do
echo Hello $classmates
done
CTRL+D
/> . ./test9.sh
Hi Stephen
Hi Ann
Hi Sheryl
Hi John
Hello Jerry
Hello Tom
Hello Angela
6. 函数:
Shell中函数的职能以及优势和C语言或其它开发语言基本相同,只是语法格式上的一些差异。下面是Shell中使用函数的一些基本规则:
1) 函数在使用前必须定义。
2) 函数在当前环境下运行,它和调用它的脚本共享变量,并通过位置参量传递参数。而该位置参量将仅限于该函数,不会影响到脚本的其它地方。
3) 通过local函数可以在函数内建立本地变量,该变量在出了函数的作用域之后将不在有效。
4) 函数中调用exit,也将退出整个脚本。
5) 函数中的return命令返回函数中最后一个命令的退出状态或给定的参数值,该参数值的范围是0-256之间。如果没有return命令,函数将返回最后一个Shell的退出值。
6) 如果函数保存在其它文件中,就必须通过source或dot命令把它们装入当前脚本。
7) 函数可以递归。 将函数从Shell中清空需要执行:unset -f function_name。
9) 将函数输出到子Shell需要执行:export -f function_name。
10) 可以像捕捉Shell命令的返回值一样获取函数的返回值,如$(function_name)。
Shell中函数的声明格式如下:
function function_name { command; command; }
见如下示例脚本:
/> cat > test1.sh
function increment() { #定义函数increment。
local sum #定义本地变量sum。
let "sum=$1+1"
return $sum #返回值是sum的值。
}
echo -n "The num is "
increment 5 #increment函数调用。
echo $? #输出increment函数的返回值。
CTRL+D
/> . ./test1.sh
The num is 6
7. 陷阱信号(trap):
在Shell程序运行的时候,可能收到各种信号,有的来自于操作系统,有的来自于键盘,而该Shell在收到信号后就立刻终止运行。但是在有些时候,你可能并不希望在信号到达时,程序就立刻停止运行并退出。而是他能希望忽略这个信号而一直在运行,或者在退出前作一些清除操作。trap命令就允许你控制你的程序在收到信号以后的行为。
其格式如下:
trap 'command; command' signal-number
trap 'command; command' signal-name
trap signal-number
trap signal-name
后面的两种形式主要用于信号复位,即恢复处理该信号的缺省行为。还需要说明的是,如果trap后面的命令是使用单引号括起来的,那么该命令只有在捕获到指定信号时才被执行。如果是双引号,则是在trap设置时就可以执行变量和命令替换了。
下面是系统给出的信号数字和信号名称的对照表:
1)SIGHUP 2)SIGINT 3)SIGQUIT 4)SIGILL 5)SIGTRAP 6)SIGABRT 7)SIGBUS SIGFPE
9)SIGKILL 10) SIGUSR1 11)SIGEGV 12)SIGUSR2 13)SIGPIPE 14)SIGALRM 15)SIGTERM 17)SIGCHLD
1SIGCONT 19)SIGSTOP ... ...
见如下示例脚本:
/> trap 'rm tmp*;exit 1' 1 2 15 #该命令表示在收到信号1、2和15时,该脚本将先执行rm tmp*,然后exit 1退出脚本。
/> trap 2 #当收到信号2时,将恢复为以前的动作,即退出。
/> trap " " 1 2 #当收到信号1和2时,将忽略这两个信号。
/> trap - #表示恢复所有信号处理的原始值。
/> trap 'trap 2' 2 #在第一次收到信号2时,执行trap 2,这时将信号2的处理恢复为缺省模式。在收到信号2时,Shell程序退出。
/> cat > test2.sh
trap 'echo "Control+C will not terminate $0."' 2 #捕获信号2,即在键盘上按CTRL+C。
trap 'echo "Control+\ will not terminate $0."' 3 #捕获信号3,即在键盘上按CTRL+\。
echo "Enter stop to quit shell."
while true #无限循环。
do
echo -n "Go Go...."
read
if [[ $REPLY == [Ss]top ]] #直到输入stop或Stop才退出循环和脚本。
then
break
fi
done
CTRL+D
/> . ./test2.sh
Enter stop to quit shell.
Go Go....^CControl+C will not terminate -bash.
^\Control+\ will not terminate -bash.
stop
8. 用getopts处理命令行选项:
这里的getopts命令和C语言中的getopt几乎是一致的,因为脚本的位置参量在有些时候是失效的,如ls -lrt等。这时候-ltr都会被保存在$1中,而我们实际需要的则是三个展开的选项,即-l、-r和-t。见如下带有getopts的示例脚本:
/> cat > test3.sh
#!/bin/sh
while getopts xy options #x和y是合法的选项,并且将-x读入到变量options中,读入时会将x前面的横线去掉。
do
case $options in
x) echo "you entered -x as an option" ;;
y) echo "you entered -y as an option" ;;
esac
done
/> ./test3.sh -xy
you entered -x as an option
you entered -y as an option
/> ./test3.sh -x
you entered -x as an option
/> ./test3.sh -b #如果输入非法选项,getopts会把错误信息输出到标准错误。
./test3.sh: illegal option -- b
/> ./test3.sh b #该命令不会有执行结果,因为b的前面有没横线,因此是非法选项,将会导致getopts停止处理并退出。
/> cat > test4.sh
#!/bin/sh
while getopts xy options 2>/dev/null #如果再出现选项错误的情况,该重定向会将错误输出到/dev/null。
do
case $options in
x) echo "you entered -x as an option" ;;
y) echo "you entered -y as an option" ;;
\?) echo "Only -x and -y are valid options" 1>&2 # ?表示所有错误的选项,即非-x和-y的选项。
esac
done
/> . ./test4.sh -g #遇到错误的选项将直接执行\?)内的代码。
Only -x and -y are valid options
/> . ./test4.sh -xg
you entered -x as an option
Only -x and -y are valid options
/> cat > test5.sh
#!/bin/sh
while getopts xyz: arguments 2>/dev/null #z选项后面的冒号用于提示getopts,z选项后面必须有一个参数。
do
case $arguments in
x) echo "you entered -x as an option." ;;
y) echo "you entered -y as an option." ;;
z) echo "you entered -z as an option." #z的后面会紧跟一个参数,该参数保存在内置变量OPTARG中。
echo "\$OPTARG is $OPTARG.";
;;
\?) echo "Usage opts4 [-xy] [-z argument]"
exit 1 ;;
esac
done
echo "The number of arguments passed was $(( $OPTIND - 1 ))" #OPTIND保存一下将被处理的选项的位置,他是永远比实际命令行参数多1的数。
/> ./test5.sh -xyz foo
you entered -x as an option.
you entered -y as an option.
you entered -z as an option.
$OPTARG is foo.
The number of arguments passed was 2
/> ./test5.sh -x -y -z boo
you entered -x as an option.
you entered -y as an option.
you entered -z as an option.
$OPTARG is boo.
The number of arguments passed was 4
9. eval命令与命令行解析:
eval命令可以对命令行求值,做Shell替换,并执行命令行,通常在普通命令行解析不能满足要求时使用。
/> set a b c d
/> echo The last argument is \$$#
The last argument is $4
/> eval echo The last argument is \$$# #eval命令先进行了变量替换,之后再执行echo命令。
The last argument is d 作者: compare2000 时间: 2014-05-24 00:17
Linux Shell常用技巧(九) 系统运行进程 1. 进程监控命令(ps):
要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而ps命令就是最基本同时也是非常强大的进程查看命令。使用该命令可以 确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。总之大部分信息都是可以通过执行该命令得到的。
ps命令存在很多的命令行选项和参数,然而我们最为常用只有两种形式,这里先给出与它们相关的选项和参数的含义:选项 说明
a 显示终端上的所有进程,包括其他用户的进程。
u 以用户为主的格式来显示程序状况。
x 显示所有程序,不以终端来区分。
-e 显示所有进程。
o 其后指定要输出的列,如user,pid等,多个列之间用逗号分隔。
-p 后面跟着一组pid的列表,用逗号分隔,该命令将只是输出这些pid的相关数据。
#切换到stephen用户下执行一个后台进程,这里sleep进程将在后台睡眠1000秒。
/> su stephen
/> sleep 1000&
[1] 4812
/> exit #退回到切换前的root用户
#查看已经启动的后台sleep进程,其ni值为0,宿主用户为stephen
/> ps -eo user,pid,ni,command | grep stephen
stephen 4812 0 sleep 1000
root 4821 0 grep stephen
#以指定用户的方式修改该用户下所有进程的nice值
/> renice -n 5 -u stephen
500: old priority 0, new priority 5
#从再次执行ps的输出结果可以看出,该sleep后台进程的nice值已经调成了5
/> ps -eo user,pid,ni,command | grep stephen
stephen 4812 5 sleep 1000
root 4826 0 grep stephen
#以指定进程pid的方式修改该进程的nice值
/> renice -n 10 -p 4812
4812: old priority 5, new priority 10
#再次执行ps,该sleep后台进程的nice值已经从5变成了10
/> ps -eo user,pid,ni,command | grep stephen
stephen 4812 10 sleep 1000
root 4829 0 grep stephen
3. 列出当前系统打开文件的工具(lsof):
lsof(list opened files),其重要功能为列举系统中已经被打开的文件,如果没有指定任何选项或参数,lsof则列出所有活动进程打开的所有文件。众所周知,linux环境中任何事物都是文件,如设备、目录、sockets等。所以,用好lsof命令,对日常的linux管理非常有帮助。下面先给出该命令的常用选项:
现在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。当然,不同产商的make各不相同,也有不同的语法,但其本质都是在“文件依赖性”上做文章,这里,我仅对GNU的make进行讲述,我的环境是RedHat Linux 8.0,make的版本是3.80。必竟,这个make是应用最为广泛的,也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的(POSIX.2)。
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$(bar)的定义用,调用了函数“subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号,所以$(bar)的值是“a,b,c”。
“—debug[=<options>]”
输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:
a —— 也就是all,输出所有的调试信息。(会非常的多)
b —— 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
v —— 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。
i —— 也就是implicit,输出所以的隐含规则。
j —— 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
m —— 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。
rem 工程名,任意英文名,不要有空格
SET BUILD_ID=my_build
rem 依赖的jar包
SET CLASSPATH=D:\software\apache-maven-3.0\Repo_eUPP\**\*.jar
rem 扫描的工程目录
SET SCAN_DIR=D:\code\eupp V01R01\eUPP-pkg\**\*.java
sourceanalyzer "-b" "%BUILD_ID%" –clean
rem 1.6是指java版本,1.7的话请写1.7
sourceanalyzer "-b" "%BUILD_ID%" "-machine-output" "-cp" "%CLASSPATH%" "-source" "1.6" "%SCAN_DIR%" "-Xmx1024m"
命令打可执行jar包:
1.将源文件拷贝到d:\jartest;
2.在cmd命令下进入jartest该目录;
配置:
set path=C盘下jdk1.5.0_05\bin所在的目录 --你自己的jdk安装目录
set classpath=. --当前路径
但是说话做事还是很直接,用部门一个本地同事的话说,我的口头禅是“Let’s make a summary”。每次组织会议,听了与会人发表意见后,我总是忍不住要打断漫无边际的讨论,要马上列出行动计划去分配执行。确实性格很难改变,也许永远做不到人情练达。只求以诚待人,简单快乐。用我老婆的话说:“丑也有丑的好处,那张脸一眼看上去至少很真诚;而且心思简单,躺下10分钟就能开始打呼噜”。
2012年6月参加了FLM培训(First Line Manager),自己看了些书,包括德鲁克的几本经典著作、心理学方面的入门知识等;期望自己能做一个好的管理者。仔细想,也实在总结不出来经验。2012年,和一个产品线领导吃饭,他就问了我这个问题。忘记怎么回答了,好像是脱口而出:“开放的心态,以诚待人;多看他人的优点,承担责任,帮助他人成功”。
1,中断分为上半部和下半部
上半部:中断处理程序,接收到一个中断,立即开始执行,但只做有严格时限的工作,这些工作要求所有中断被禁止。
下半部:可以稍后完成的工作在下半部完成。
2,中断处理程序注册函数
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
irq是分配的中断号,要么提前定死,要么动态获得。
handler指向处理这个中断的实际中断处理程序。
flags 可以为0,也可以是下面一个或者多个标志的位掩码
/*
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
*
*/
name是与中断相关的设备的ASCII文本表示法。
dev主要用于共享中断线,如果无需共享,则赋值NULL,否则必须传递唯一的信息。
3,request_irq返回0为成功,返回非0为失败。最常见的是返回-EBUSY,表示给定的中断线已经在使用。
4,request_irq可能会睡眠,因此不能再中断上下文或其他不允许阻塞的代码中调用该函数。之所有会睡眠,是因为在注册过程中会调用kmalloc()来请求分配内存,而该函数是可以睡眠的。
5,释放中断处理程序函数为free_irq(unsigned int irq, void *dev_id),如果指定的中断线是非共享的,则同时禁用该中断线。
6,典型的中断处理程序声明如下:
irqreturn_t amd_iommu_int_handler(int irq, void *data)
这里要注意该函数的返回值是一个特殊类型irqreturn_t。
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
7,中断处理程序无需重入。当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽,以防止在同一个中断线上接受另一个新的中断。
但是不同中断线上的其他中断都可以处理。
9,当执行一个中断处理程序或下半部时,内核处于中断上下文中。中断上下文和进程没有关系,所以中断上下文不可以睡眠,而且不能从中断上下文中调用某些可以睡眠函数。
10,中断栈:中断处理程序早期是没有自己的栈,而是共享所中断进程的内核栈。2.6后,中断处理程序有了自己的栈,每个处理器一个,大小为一页。
11,中断处理机制:硬件设备产生中断,通过总线把电信号发送给中断控制器,如果中断线此时没有被屏蔽,处于激活态,则会把中断发往处理器。
如果处理器上没有禁止该中断,则立即停止正在做的事,关闭中断系统,跳到中断处理程序的入口点。在内核中,中断处理程序都有一个入口点,这样内核就
可以知道中断的IRQ号,入口点会在栈中保存IRQ号,并存放当前寄存器的值(这些值属于被中断的任务),然后内核调用函数unsigned int __irq_entry do_IRQ(struct pt_regs *regs)。
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
int xfs;
int xgs;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
do_IRQ会调用handle_irq运行中断处理程序,然后调用irq_exit处理软中断(如果必须且能处理的话,这个软中断是softirqs,下半部实现的一种机制,不同于软件中断),最后将原来寄存器的值恢复。
然后从入口点跳到ret_from_intr(汇编写的/arch/x86/kernel/entry.s),它会检查重新调度是否正在挂起,如果正在挂起,则分别按照用户抢占和内核抢占处理(具体可参考进程调度http://hi3ms.huawei.com/group/2642/blog_48236.html?uid=9986)。
ret_from_intr:
DISABLE_INTERRUPTS(CLBR_NONE)
TRACE_IRQS_OFF
decl PER_CPU_VAR(irq_count)
leaveq
CFI_DEF_CFA_REGISTER rsp
CFI_ADJUST_CFA_OFFSET -8
12,/proc/interrupts文件中存放的是系统中与中断相关的统计信息。
第一列是中断线,第二列是接受中断数目的计数器,第三列是处理该中断的中断控制器,最后一列是与这个中断相关的设备名字。
root@localhost:/proc> vi interrupts
属性的读写操作应该操作同一个buf。
【???】为何写入writehere时,store()方法调用了两次? echo writehere>/sys/hwobj/write_node
其他需要注意的:
---【???】写操作会引起show()的位置回到文件开始。
---buffer的大小总是PAGE_SIZE。
---show()应该返回写入到buf中的长度,返回值和scnprintf()一样。
【译注】
scnprintf -- Format a string and place it in a buffer
int scnprintf(char* buf, size_t size, const char *fmt, ....);
buf: the buffer to place the result into
size: the buffer size, including the trailing null space
【end】
---store()应该返回buffer所用的大小。如果所有的buffer都用完了,只需要返回count参数?
---show()或者store()方法可以返回错误。如果一个错误的参数传入,那么可以返回一个错误。
---show()或者store()传入的对象,会在sysfs引用的object的内存中(?)。所以,物理的设备可能不会存在,所以,如果需要,要检查物理设备是否存在。
samp_sysfs.c.txt
/*
* sysfs.example.c - sample code for Documentation/filesystems/sysfs.txt
*
* Copyright (C) 2013 Bill Wang(Liang)
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*/
External refers to external open source libraries. That means libraries that the Android platform depend upon but that are not primarily developed and maintained by the Android open source project. Typical examples are webkit for the browser, FreeType for fonts, SqlLite for databases and so on. As more features are added to Android, more of these libraries are included in external.
Bionic libc是BSD standard C library发展而来,最早是Google为Android开发。作用主要有三个:
a,BSD license: 通过BSD license,Android程序就和GPL分隔开来。
b,Bionic比GNU C Library要小的多。
c,Bionic的速度更快。
比较 工作目录和index,以及HEAD的区别。
结果显示三列,第一列表示index和HEAD的区别,大写。
-表示没有区别。
A: added (not in HEAD, in index )
M: modified ( in HEAD, in index, different content )
D: deleted ( in HEAD, not in index )
R: renamed (not in HEAD, in index, path changed )
C: copied (not in HEAD, in index, copied from another)
T: mode changed ( in HEAD, in index, same content )
U: unmerged; conflict resolution required
第二列表示working tree和index的区别,小写。
-: new / unknown (not in index, in work tree )
m: modified ( in index, in work tree, modified )
d: deleted ( in index, not in work tree )