如何使用Git

回档回档回档回档!

Posted by tianchen on January 1, 2024

如何正常使用git

小金鱼A_suozhang需要一些记录来保证自己不会忘记这些命令

WorkFlow

Simplest Example

  • 首先 git init 创建仓库
  • 工作流(3棵树)
    • 中间的是index(暂存区),是一个缓存区域
    • 最后的是Head(也就是指向最后一次提交(Commit)的结果),到了head还不代表到了remote repository远程仓库
  • git add .将当前目录的改动暂存(Stage)
    • 这个命令可能会将一些奇怪的东西添加进来,建议是用git add -a添加改动区(没staged的不加),然后git commit -a
  • git commit -m "XXX"将Index中的内容提交给head
  • git push推送改动

分支 (Branch)

其产生是为了特性开发绝缘,创建仓库的时候,默认分支为“Master”

  • 创建新的分支 git checkout -b feature_x
    • 删除新的分支(当然你首先要切回主分支) git branch -d feature_x
    • 推送到当前分支 git push origin feature_x
  • 切换回到主分支 git checkout master

更新与合并

  • 更新本地仓库(Local Repo)到最新的改动 git pull
    • 相当于在目前的目录中获取(Fetch)并且合并(Merge)了远端(Remote)的改动
    • 这里的merge是默认fast forward的
  • 如果你只想合并其他某个分支到你的当前分支 git merge branch
  • 执行以上两者的时候都自动合并冲突(Conflict),有时候可能不成功,就要手动合并,修改完之后利用git add <Filename>来表征合并完毕,在合并的时候利用git diff <Source_Branch> <Target Branch>

  • 替换本地文件,采用git checkout <filename>用head中的文件替换本地的文件(但是不会改变staged区域)
    • Tutorial Link
    • 需要区分和reset和revert的区别
      • revert 表示对public branch撤销commit,没有对文件操作的方式
      • reset对文件操作是做Unstage,而对commit做表示撤销所有未经commit的变化
          • git checkout $FILENAME是和直接和remote重置
      • checkout对文件是改变working directory中的内容(不会改变stage区域),对commit做表示切换branch
      • 对某个具体的commit可以specify commit,like git reset HEAD~2
    • 将Head复制到了Local Repo
  • git reset -- files撤销最后一次git add
    • 也就是撤销stage区域
  • 替换整个本地仓库为云端版本,先获取服务器的最新版本git fetch origin,将本地仓库指向它 git reset --hard origin/master

  • merge与rebase的区别
    • 当执行git push的时候默认进行了merge
    • 如果当只是本地的小改动的话可以使用git rebase -i origin/XXX
      • rebase完了还是要push的哦
  • git pull和git fetch
    • git pull相当于先fetch再merge
    • git fetch下来只是更新了.git内部的,压缩过的新版本代码
      • 这样就可以利用git diff来查看和远程的有什么区别了
    • Dont Just Pull, Fetch & Merge
    • 执行git merge之后如果出现冲突,git会直接在你的文件里修改,回去把冲突消除之后再add然后commit就好了
  • Git Merge的两种情况 (Fast-Forward or not) 参考几个动图
    • 默认是FF的,加上 –no-ff 不去ff
    • FF:When Current Branch have no Commits, Merging branch
      • DO-NOT Create a new commit
      • 只是单纯的把新的branch连到了原来的branch上并且把HEAD指向其
  • No-FF:如果当前的branch有新的commit
    • 一般出现在我本地改动了一个Commit,但是别人也往Remote上commit了,导致现在的remote已经不是之前我本地的版本了
    • Git creates a new active Node (Merging Commit)
    • 当发生Merge CONFILCT的时候,我们需要手动Fix并且进行一个Commit,我们就手动创建了一个Merge Commit
  • 对一个Branch的内容进行更新的办法除了Merge就是Rebase
    • It copies the commit of current branch, and put it on top of the merging branch
    • 但是,rebase会直接用现有的情况去覆盖merging branch的内容(因为首先要让merging branch会到Current branch commit之前的状态),因此不会有MERGE CONFLICT,也不会有Merge Commit节点,让整个开发树保持线性
    • 因为很危险,所以一般要用rebase -i来进行控制
  • 另外一种方法是Cherry-pick
    • 如果对另一个branch的多个commit中只想要引入一个进来,如果merge的话会全都考虑进来,可以用git cherry-pick COMMIT_ID
  • Merge-Conflict的example
    • 比如说我先在本地做了修改并且commit且push了,push的时候报错表示我remote有了我本地没有的更改,pull下来之后发现conflict了,我们需要手动修复conflict
    • 会出现这样的额外内容
      • «« HEAD
      • 我刚才修改的内容
      • =========
      • remote上别人修改的内容
      • ======> remote_commit ID

补充(Complement)与回退(reset/checkout/revert)

  • git commit --amend 可以快捷修改当前的commit
    • 然后在amend出来的文件里添加第二次commit做了啥,保存之后退出
    • 如果上一个commit没有push上去,那么会直接替代没有关系
    • 如果上一个已经push上去了,由于需要覆盖会冲突,所以需要用git push -f

回退 reset/checkout/revert

在Commit层面

  1. reset:将某个分支的末端指向某个提交
    • git reset HEAD~2 - 将当前branch的回退两个commit
    • 有Soft和Hard两种,可以用git reset --soft HEAD~2来指定,soft的话只会移动HEAD的指向的位置,后面的两个Commit依然是可以Access的,但是hard的话最后的两个commit将被删除(也包括本地的Stage以及Working Directory中的新文件) * 相对危险,请只用于private repo
  2. checkout - 一般用于切换分支
    • 除了换分支之外,也可以切换到别的提交git checkout HEAD~2
    • git会强制覆盖本地的所有更改,所以Git会要求你预先Stage所有的更改,某则都会丢失
  3. revert相对安全,在撤销一个提交的时候会创建一个新的Commit,它并不会重写commit历史,只是找出一个commit,并且创建一个新的commit加入 git revert HEAD~2
    • 保存了之前的所有,只是新加了一个

在File层面

  1. reset - 将缓存区(Stash)同步到你指定的Commit git reset HEAD~2 foo.py不会影响Working Directory中的文件
  2. checkout - 类似,就是改变Working Directory但是不改变Stash

查看信息

  • git status
  • git reflog
  • git log 查看以往的commit都做了什么 (如果需要比较精简的便于查找ID用git reflog,然后通过reset获得神秘的时光机效果)
  • git log --graph
  • git remote -V
    • 没有什么卵用的功能
  • ls .git/refs/remotes/origin/
    • git branch -r
  • git show` 查看当前commit的修改情况

Gitignore

Tempelate

Place

  • 就放在.git文件所在的目录

Usage

  • 保存.gitginore之后可以使用git status来检查是否正确(Vscode直接用git框子里面看)
  • 也可以用git check-ignore -v XXX
  • 主要用到的 (支持逻辑通配)
    • *.pth
    • folder1/folder2
  • 已经上传的,后修改了gitignore (清空缓存可以解决)
    git rm -r --cached .
    add .
    
  • 一些简单的模板

#注释           .gitignore的注释
*.txt           忽略所有 .txt 后缀的文件
!src.a          忽略除 src.a 外的其他文件
/todo           仅忽略项目根目录下的 todo 文件,不包括 src/todo
build/          忽略 build/目录下的所有文件,过滤整个build文件夹;
doc/*.txt       忽略doc目录下所有 .txt 后缀的文件,但不包括doc子目录的 .txt 的文件

bin/:           忽略当前路径下的 bin 文件夹,该文件夹下的所有内容都会被忽略,不忽略 bin 文件
/bin:           忽略根目录下的 bin 文件
/*.c:           忽略 cat.c,不忽略 build/cat.c
debug/*.obj:    忽略debug/io.obj,不忽略 debug/common/io.obj和tools/debug/io.obj
**/foo:         忽略/foo, a/foo, a/b/foo等
a/**/b:         忽略a/b, a/x/b, a/x/y/b等
!/bin/run.sh    不忽略bin目录下的run.sh文件
*.log:          忽略所有 .log 文件
config.js:      忽略当前路径的 config.js 文件

/mtk/           忽略整个文件夹
*.zip           忽略所有.zip文件
/mtk/do.c       忽略某个具体文件

  • 可以直接用git clone将仓库拷贝到本地的其他位置

具体操作CheatSheet

对某个repo免密码push

  • 打开该工程目录下的.git/config
    • 将url改成https://A-suozhang:$PASSWD@github.com/A-suozhang/$REPO_NAME
  • 设置全局的gitignore,首先执行 git config --global core.excludesfile ~/.gitignore
    • 此时的~/.gitconfig`会变成这样(当然你手动加入指令也是可以的)
  • 然后编辑根目录下的gitignore自动和本机的git绑定

  • 同理可以在gitconfig中建立账户信息
    • git config --global user.email

免密码Token

  • git config --global credential.helper store,密码会生成在~/.git-credential

暴力PUSH

  • 因为各种操作(比如回退到了之前的Commit)可能会出现Your Repo Is Behind master/branch by 2 commit, try git pull first现在理论上是不能commit也不能push的,但是如果我们真的需要push
  • git push --force (谨慎啊)

强行回退版本

  1. 首先 git log查看历史Commit,找到自己想要回到的那一次,记录下来COMMIT_ID
  2. 执行 git reset --hard COMMIT_ID
    • 相当于直接删除对应的commit
  3. 或者是 git revert COMMIT_ID
    • 相当于用一次新的commit来回滚到之前的commit

Commit 完了之后发现还需要改个什么东西

  1. git add $SOME_FILE
  2. git commit --amend --no-edit
  3. (如果已经push到remote上,那么再git push -f)

Git Add之后需要撤销

  1. git reset HEAD ./XXX
    • 这样做的话不会实际改变文件,而git checkout则会丢弃本地修改
    • This act means Unstage

测试更新与Branch

  • 首先我新建一个文件testGit.sh
    • git add .过去,查看git status显示新增了一个文件
    • git reset --file testGit.sh之后git status发现显示目前还有testGit.sh这个文件没有提交
  • Addh回来,Commit上去git commit -m "Adding testGit.sh",同步到了HEAD
  • 新建一个branch git checkout -b branch0
  • 把HEAD push到新的branch上 git push origin branch0
  • 切换回到master branch, git checkout master
  • 直接 git pull,完成之后发现新的文件testGit没有了(合理,因为master branch上并没有这个文件)
  • 切换到git checkout branch0,然后git pull但是失败了,尝试git pull origin branch0,文件又回来了
  • 执行git diff master origin对比几个branch
  • git branch -vv查看对每个branch做了什么
  • git branch查看当前的分支(带星号的是)
  • git checkout master; git merge branch0 合并分支
  • git branch -d branch0删除合并后的分支

当简单修改的时候使用rebase来精简graph

这里没写清楚,看下面

  • 这个一般般 - lxf教程
  • Git Book
    • 这个讲的很清楚 1. 当你修改了一个文件,add-commit完了想把它推到远程去,但是远程仓库已经被人修改了 2. 这个时候普遍操作是先pull下来,然后再merge,但是你下次commit的时候就会出现一个merge comit才能push上去 3. 与之替代,你可以git rebase origin/XXX
    • 这个操作会把你之前的commit给取消掉,并且在新的origin后面append新的修改
      1. rebase的过程中会出现conflict,解决之后用git add,然后不用commit,可用git rebase --continue
      2. 如果中途放弃,可以git rebase --abort

Git Stash

当开发了一半,但是想要从remote同步代码的时候

  • 先用git stash把内容暂存下来
  • 然后可以正常的进行push和pull,commit (stash之后是可以正常的fetch merge的)
  • git stash pop
  • git stash drop

Git Show

想要查看某个commit的修改的时候

  • git log 查看commit的哈希值
  • git show查看最新的commit
  • git show COMMIT_ID 查看某个commit的修改
  • git show COMMIT_ID FILENAME 查看某个commit的某个文件的修改

对于不是clone下来的新项目

  • 正常的git add以及commit
  • git remote add https://XXX.git
  • git push -u origin master

Add Deletd Files while Metting “it does not exist”

  • For single file, use git add -u $SOME_FILE
  • For the folder, use git rm -r --cached $SOME_FOLDER
  • Or git add `git ls-files --deleted`

对于在不同的本地服务器之间进行仓库的同步

  • 其实和正常flow基本一样,只是将原本的remote (可以用git remote -v来描述)
    • 将原本的git服务器上的(https://github.com/A-suozhang./aw_nas)切换成ztc.eva8.nics.cc:/home/zhaotianchen/code/aw_nas
      • 将上面的连接加入git clone即可
      • 两者同样需要commit与add

修改remote的地址

  • 首先可以git remote -v看现在的remote是哪个地址-总之是个url,可以是和github服务器,也可以是本地服务器
    • 或者可以通过指定哪个remote(一般来说默认是origin)来查看对应的remote地址git remote origin
    • 同理push的时候也可以指定push到哪个remote
      1. git remote set-url origin http://xxx.git
      2. 先删除再建立
    • git remote rm origin
    • git remote add origin https://
      1. 修改配置文件
    • 进入.git/config

当你绑定了多个remote的时候

  • 可以用git remote -v来查看remote的名字和url
  • 注意push的时候要指定push到哪一个remote
  • pull的时候同理
    • 注意当你的branch不在master的时候,如果你想要和某个remote同步,需要在pull时候指定remote和branch的名字
    • 比如git pull a-suozhang bnn

对于add以及commit不需要每个都add这件事

  • 说起来非常惭愧到现在我才明白
  • 在git status中
    • changes not staged for commit 是不需要手动通过add加入的
    • 只有下面的untracked file需要手动加入
  • 在commit中指定-a就可以上面第一类中的所有内容全部给commit上去

rebase的理解

又一次发现自己之前没有搞清楚,我真的是个傻逼

一次新的开发例子

  • 我现在需要回退几个commit,将它们合起来,同时这之间还会存在一个分支的split又merge的问题
    • 如图所示,我需要回到add layer2 ss这个地方,并且保留add final model,将其他的commit全部合并成一个
  • git reflog或者git log来获得commit-id
  • git rebase -i (COMMIT_ID) - 其中COMMIT-ID是add final model的COMMIT-ID
  • 进入交互界面,(默认貌似是nano编辑器,可以用git config --global core.editor "vim"修改配置用vim)
  • 如图所示修改
    • 它找出了在add final model前面(同时)的4个commit(Merge commit貌似被省略了)
    • 下面的注释会告诉你如何操作,比如f的意思是fixup,跳过不显示commit内容,pick表示就是保留
    • 在我们的case里:被pick下的是add controller这个commit,如果不修改名字的话上面的所有的commit就会被合成这个commit
  • 如果出现了conflict(大概率),也就是你rebase的这个commit如果与现在的分支出现了conflict(肯定有,因为final model这个commit被我merge过),那么就会需要手动fix
    • git add之后git rebase --continue就好了
  • 在任何时候发现错了,可以直接git rebase --abort

  • 总之,git rebase就是将在指向commit之后的所有commit,合成一个commit,append在它后面
    • 与之同等级的(不同的分支出去的),都认为在其之后,

  • 现在完全看不懂我下面记的是什么吊东西
  • 先checkout到某个branch,把其他branch的内容给rebase过来
  • image

强制回退版本

我感觉我好像在什么地方已经记过这个一次了

  • git log查看COMMIT_ID
  • git reset --hard COMMIT_ID
  • 如果使用 git checkout COMMIT_ID会出现HEAD Detach的现象,导致不在任何一个branch上面

2021git更新了不让用密码而是用token进行登录

  • since push needs token now, use this command to save the token after your push git config --global credential.helper store
  • 或者用ssh和git服务器进行登录,貌似可以避免科学上网(*)
    • 但是maybe会出现其他问题

不去track某些文件

  • 比如一些经常需要更新的train的配置文件,本来不应该每次git更新都更新,但是我又想保存这个文件
  • 方案1:使用git rm --cached(这样做会直接在remote删除这个文件,这个时候看git status会显示deleted file)如果只用git rm的话,甚至当前工作区中的也会被移除(并不满足我们的需求)
  • 使用git update-index --skip-worktree kitti-train-mp.sh
    • 但是要注意使用本命令的时候,working tree中本身不能有对该文件的修改,否则就会出现一些奇妙错误*,如果在其他分支出现了问题的话,就需要手动merge而且很复杂
  • 所以以上的方式只能作为一个.gitignore的比较好的替代,但是好像还是不能很好的达到想不去track某些文件的作用,要是真的想这样,大概率还是用方案1

push到一个通过服务器之间ssh clone来的repo报错

  • 本质原因是因为source repo不是一个bare-rep
    • 我现在的case是我在eva3(eva3上的repo是从eva1上复制过来的)上修改了文件,我给它push到eva1上的时候报错了

Git Diff不出东西,但是不能mergre,说对local file做了修改?

  • 本质原因是: 在远程的其他地方 delete了文件,但是本地仍然保留了
  • 查看被ignore的file git ls-files --ignored --exclude-standard -z

有时候从git上下载东西一直报错 curl Failed to connect to raw.githubusercontent.com port 443: Connection refuse

Refs