These are the notes I update as I learn more about GIT source code revision control. Though not a beginner tutorial, I try to make these notes a useful resource for beginners as well as myself.
Don't conflate GIT with the GitLab or GitHub hosting services. There is
no such thing as a "pull request" or "forking" in GIT. These are
GitHub & GitLab constructs. GIT is design to clone repositories and
push & pull changes between those repos that share past history.
GIT is decentralized. Unlike version control tools like SCCS, CVS, or Subversion there is no central or blessed repository, unless you just consider one as such. Even then the blessed repo is still just a social construct, GIT does not care.
GIT branches are just light weight user controlled labels, not large monolithic directory structures in a centrally maintained software repository.
In the 1990's, as a contractor for AT&T Microelectronics, I was the SCCS administrator for AT&T Microelectronics Process Control Manufacturing Execution System which tracked and controlled products through multiple semiconductor manufacturing production lines. Process Control, a misnomer, Production Control would have been a better name, controlled production based on customer orders and inventory. If it had existed, GIT would have been much simpler to configure, maintain and use than SCCS.
You do not know GIT until you can use its CLI interface. GIT's CLI interface was designed to implement user version control workflows. Typically I find that most GIT GUI clients, web interfaces and IDEs are designed to adapt the user to some particular workflow, typically that of the tool designer. Without a knowledge of GIT's CLI interface, it is much more difficult to know what to look for in a particular GIT GUI client's menus, keyboard shortcuts, and help utility. GUI and web clients come and go, GIT's CLI interface will be with us for decades, if not centuries.
Most Linux distributions have a good GIT tutorial in the man pages.
$ man gittutorialTo get a comprehensive overview of git, use
$ man gitand to get information on individual commands
$ man git-add
$ man git-infoor using the --help option in git
$ git --help
$ git clone --helpThree "patterns" to best leverage the above help commands
$ git help verb
$ git verb --help
$ man git-verbAlso see GIT help files on the web.
- /etc/gitconfig
- /usr/local/gitconfig
- ~/.config/git/config
- ~/.gitconfig
- .git/config at root of repo
Each one overrides the ones above it.
The git config command (without --global)
- updates the repo's '.git/config' file,
- or complains if not in a GIT repository
The git config --global command will
- update
~/.gitconfigif it exists - otherwise create and update
~/.gitconfigif~/.config/git/configdoes not exist - otherwise update
~/.config/git/config
I usually keep ~/.config/git/config under git control and
~/.gitconfig not. That way git config --global configurations don't
get clobbered when I update my config files with my dotfile installation
scripts.
Aside: The git maintenance command will create ~/.gitconfig even
if ~/.config/git/config exists.
$ git config --global user.name 'John Doe'
$ git config --global user.email john.doe.2@us.af.milIf you want to override these settings for a specific GIT repo, run the
above commands without the --global option while in that repo's
directory structure.
$ git config --global core.editor nvim
$ git config --list $ git config user.name $ cd ~/devel/notes/git-notes
$ git config --list --show-origin --show-scope
system file:/etc/gitconfig filter.lfs.required=true
system file:/etc/gitconfig filter.lfs.clean=git-lfs clean -- %f
system file:/etc/gitconfig filter.lfs.smudge=git-lfs smudge -- %f
system file:/etc/gitconfig filter.lfs.process=git-lfs filter-process
global file:/home/grs/.config/git/config user.name=grscheller
global file:/home/grs/.config/git/config user.email=geoffrey@scheller.com
global file:/home/grs/.config/git/config user.signingkey=~/.ssh/id_ed25519_grscheller.pub
global file:/home/grs/.config/git/config gpg.format=ssh
global file:/home/grs/.config/git/config core.editor=nvim
global file:/home/grs/.config/git/config core.fsmonitor=false
global file:/home/grs/.config/git/config core.pager=nvim -R -c 'set filetype=git'
global file:/home/grs/.config/git/config color.pager=no
global file:/home/grs/.config/git/config pull.rebase=false
global file:/home/grs/.config/git/config init.defaultbranch=main
global file:/home/grs/.config/git/config submodules.recurse=true
global file:/home/grs/.config/git/config diff.submodule=log
global file:/home/grs/.config/git/config rerere.enabled=true
local file:.git/config core.repositoryformatversion=0
local file:.git/config core.filemode=true
local file:.git/config core.bare=false
local file:.git/config core.logallrefupdates=true
local file:.git/config remote.origin.url=git@github.com:grscheller/git-notes
local file:.git/config remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
local file:.git/config branch.main.remote=origin
local file:.git/config branch.main.merge=refs/heads/mainSetting up your own GIT repository to work with.
Initializing a repository in an existing directory
$ cd /path/to/an/existing/directory
$ git initThis creates a .git subdirectory (a GIT repository skeleton).
Add files to this repository:
$ git add *.c *.py
$ git add LICENSE README.txt
$ git commit -m 'initial project version'To clone an existing GIT repository
$ git clone git@github.com:grscheller/grok-typescript
$ git clone https://github.com/grscheller/grok-typescript learn-ts
$ git clone grs@us.navo.hpc.mil:proj/grsHome.git
$ git clone ~/devel/myRepo ~/junk/myRepoCopyThis is not just a snapshot from the repo of the project, it clones the
entire repository, complete with all change control files, containing
all past versions of the project. The second version changes the name of
the directory containing the GIT repo from grok-typescript to learn-ts.
GIT does not care about the name of the directory it gets cloned into.
For the last version, the "upstream repo" or origin was set to
"/home/grs/devel/myRepo" with no knowledge of the upstream origin,
if it even exists.
Go to github.com or gitlab.com and create yourself an empty GIT repo for your project, using their webtools. Then clone the empty repo as shown in the above system. Both services provide useful boiler plate for various types of projects and software licenses.
I found doing this useful when I had two contractors working the same code base but didn't have access to each other's GIT repos. The only thing they share is ssh access to the same out-of-date CentOS Linux system where I had no root access.
First I had to ask the system admin "nicely" to have the contractors and myself put in the same Linux secondary group with access to some common file system real estate. At least this admin knew what a "secondary group" was.
$ su -
$(root) groupadd repo --users grs dude1 dude2
$(root) mkdir /share/repos
$(root) chown grs:repo /share/repos
$(root) chmod 2770 /share/reposThen using my login
$ umask 0007
$ls -ld /share/repos
drwxrws---. 7 grs repos 4096 Dec 12 09:17 /share/repos
$ cd /share/repos
$ git init --bare OurProject.gitThis initializes an empty GIT repository, but without the outer working
directory. Shared repositories should always be created with the
--bare flag. Think --bare as marking the repository as a storage
facility as opposed to a development environment.
Now connect up our empty naked repository to somewhere where some prior GIT based development work has been done, but neither contractor has access:
$ cd OurProject.git
$ git remote add upstream /home/grs/devel/someProject
$ git fetch upstream
$ git fetch --tags upstreamThis will bring in all the branches and tags. Next from the someProject upstream repo
$ cd /home/grs/devel/someProject
$ git remote add downstream /share/repos/ourProject.gitMake sure your umask is set to 0007 so that all the files in
ourProject.git get created with the necessary group rwx permissions for
group access. If not, GIT will function, but with subtly bizarre
behavior. The SGID bit is set on the directory so that all files will be
in the repo group, not a user's default groups.
Both contractors can clone this shared repo and collaborate by pulling and pushing to it. I can pull from the shared repo and send upstream from my repo.
Changes I would change if I had to do this again:
- refuse to help unless I had administrator privileges
- run an actual git server either locally or somewhere else
- have the contractors digitally sign their commits
If you are starting a brand new project, setting up the shared repo is a bit easier. Create a shared, empty, bare repository as above, clone it, add the initial files to the clone, and push it back to the shared repo.
$ git clone /share/repos/ourProject.git
$ cp -R /path/to/some/initial/files/* ourProject
$ cd ourProject
$ git add *
$ git commit
$ git pushTo "rename" the directory that a GIT repository is in, first note that that git does not care about the name of the working directory. The client just needs to know where to point.
On the server:
$ mv PAT.git SDT.gitOn the client:
$ git remote rm origin
$ git remote add origin grs@us.mhpcc.hpc.mil:projects/SDT.gitNote: you may need to tell a branch what its upstream now is
$ git checkout master
$ git branch -u origin/master
$ git fetch originLet's say I have a working repo in ~/Devel/SDT
$ cd ~/Devel/SDTMake sure we are in a stable state
$ git status
On branch sdt_devel_branch
nothing to commit, working tree cleanSee what branches are here
$ git branch
sdt_production_branch
* sdt_devel_branch
masterLet's clone the repo
$ cd ../..
$ mkdir temp
$ cd temp
$ git clone ../Devel/SDT
Initialized empty GIT repository in /home/grs/temp/SDT/.git/Let's see what we got
$ cd SDT
$ git branch
* sdt_devel_branch
$ git remote -v
origin /home/grs/temp/../Devel/SDT (fetch)
origin /home/grs/temp/../Devel/SDT (push)We only picked up the currently active branch. I will sometimes do this to have a quick and dirty snapshot of a working copy of the software. Also, I can 'rm -rf .git' and either tar ball or burn to DVD what I want to give someone. Just make sure you do this in ~/temp/SDT and not in ~/Devel/SDT!!! I learned this the hard way.
Suppose you create an empty (no branches) repo on GitHub. Then clone it.
$ git clone git@github.com:grscheller/experimental.git
$ cd experimentalGit will complain that you cloned an empty repo.
Do some work ... and push to GitHub.
$ git add .
$ git commit -S
$ git branch --list|cat
* main
$ git push -u origin main
Now upstream has a branch called main and it is the origin of your local main branch.
The fetch command updates local copies of remote branches.
$ git fetch originwill update information of local copies of all remote branches.
$ git fetch --allwill do this with all remote repositories your local repo knows about.
Note: 'git fetch' commands will not create local branches to track remote branches. You must do a 'git checkout' for each new branch you want to track. The 'git pull --all' command will only pull from branches you currently track.
To overiding global configurations when cloning a repo
$ git clone --config user.email=me@myotheremail.com \
--config http.sslcainfo=/home/geoff/.ssh/Cert_dropbox_wont_have.crt \
--config http.verify=true \
https://aur.archlinux.org/dropbox.gitYou can use -c instead of --config.
To list the commit comments made for the current "checked out" version of the repo you are currently in, do
$ git logfor a given director in the repo, do
$ git log grok/HaskellFor a given file, do
$ git log adminLogs/gauss17ArchLinuxAdmin.logHere is the usual "state transitions" of files in a GIT workspace
Deleted Untracked Unmodified Modified Staged
∥ ∥ ∥ ∥ ∥
∥ ∥---git add------------∥---------------------∥-------------------->∥
∥ ∥ ∥---edit file-------->∥ ∥
∥ ∥ ∥ ∥---git add---------->∥
∥ ∥ ∥<--------------------∥--------git commit---∥
∥<-------∥-------------git rm---∥ ∥ ∥
∥ ∥ ∥ ∥ ∥
∥ ∥<---git rm --cached---∥ ∥ ∥
∥ ∥<---------------------∥---git rm --cached---∥ ∥
∥ ∥<---------------------∥---------------------∥---git rm --cached---∥
∥ ∥ ∥ ∥ ∥
∥<-------∥----------------------∥---------git rm -f---∥ ∥
∥<-------∥----------------------∥---------------------∥---------git rm -f---∥
And here is the "life cycle" of a GIT workflow
remote tracked local index workspace
∥ ∥ ∥ ∥ ∥
∥---pull-------∥------------∥-------------∥------------->∥
∥ |-----------∥----------->∥ ∥<-------add---∥
∥ |---------->∥ ∥ ∥ ∥
∥ ∥ ∥ ∥ ∥
∥---fetch----->∥ ∥<---commit---∥ ∥
∥<-------------∥-----push---∥ ∥ ∥
∥ ∥ | ∥ ∥ ∥
∥ ∥<-----| ∥ ∥ ∥
To checking status of the files in your repo
$ git status
$ git status --shortNote: Git only tracks files. not directories.
To begin tracking a file
$ git add myfile.c # myfile.c is now staged
$ git add directory/ # all files in directory/ staged recursivelyThink of add as meaning "add this content to the next commit" rather than "add this file to the project." GIT stages the file as it was when 'git add' command was issued. If you modify a file after it was staged, you have to add it again to pick up the latest changes.
To unstage a file
$ git restore --staged some_fileThis s a clear improvement over how this was done in older versions of GIT as shown below.
$ git reset HEAD some_fileHEAD is a special ref that points to the commit that currently is checked out. Using the git-restore command makes it more clear as to what is being done. The git-reset command seems to be taking advantage of GIT implementation details and is opaque to what is really being done.
To discard changes not already staged
$ git checkout some_fileUse a .gitignore file to make GIT ignore files.
Example .gitinore file:
# ignore all .a files
*.a
# but do track lib.a, dispite ignoring all .a files above
!lib.a
# only ignore the root TODO file, not subdir/TODO
/TODO
# ignore all files in the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .txt files in the doc/ directory tree
doc/**/*.txt- Blank lines and lines starting with # are ignored
- Extended shell globbing patterns work
- End patterns with a forward slash (/) to specify a directory
- Negate a pattern by starting it with an exclamation point (!)
- Two asterisks match nested directories
To remove files from a branch
$ git rm file1 file2 dir1/
$ git commitIf there are untracked changes to the files, the files will remain in the workspace as untracked files. GIT does not track directories so dir1/ will remain too if there are untracked files in it.
You may need to do
$ rm file1 file2
$ rm -r dir1/To actually get rid of them from the working directory. Sometimes one run into empty directories when files are removed upstream.
Lets say you want to remove lots of files. For example, lets get rid of all the subversion directories (since we are using GIT).
$ find . -depth -name '.svn' -exec rm -rf '{}' \;Best practice is to then use
$ git add --update
$ git commitThe --update or -u option only matches files in the index rather
than the working tree. This removes as well as modifies index entries to
match the working tree, but adds no new files.
This will save you from having to do a lot of tricky bash shell scripting like
$ git rm $(git status | grep delete | awk '{print $3}')
$ git commitWhich will also work.
To updating major changes, such as a vendor upgrade, with file additions and removals, in a brute force sort of way, use
$ git add --all
$ git commitThe --all or -A option is like --update except that it also will
match against files in the working tree.
For better fidelity, especially when dealing with directory structure
changes, one might want to use git mv commands.
To move files
$ git mv path/to/filename new/path/to/new_filename
$ git commitGIT will move and stage the original version. If you move a file with unstaged changes, The changes made to the file will be unstaged changes within the working directory.
Usual Unix mv command semantics apply. To move a bunch of files to a new directory
$ mkdir new_dir
$ git mv file1 file2 file3 new_dir
$ git commitTo remove an empty directory.
GIT does not track directories, only files. You can't use "git rm" to remove a directory and push this change. See Cleaning up your working tree in the Git Maintenance section below,
To add files recursively down from the current directory,
$ git add .This will also remove files that have been deleted.
To only update tracked files, use
$ git add --update .This will prevent "random clutter" from being tracked.
The git clean command takes the following options
| Option | Description |
|---|---|
-d |
remove untracked directories too |
-n |
dry run, just show what to remove |
-f |
force |
-i |
interactive mode |
-x |
don't use ignore rules from .gitignore files |
-X |
remove only files ignored by git |
-e pat |
add pattern pat to ignored patterns from .gitignore |
Where one of -n, -f, or -i must be selected.
To see what needs cleaning up,
$ git clean -dnThen either manually clean up or use
$ git clean -dfThis won't work if there are .gitignore files in these directories.
To fully clean things up, use
$ git clean -fdxn # so you don't shoot yourself in the foot
$ git clean -fdxRewriting your history, don't do this if you already have shared it!!!
The 'git rebase' command comes in handy when you need to reorder commits, change commit messages, squash commits together.
A reasonable reason for squashing commits might be to enforce a policy
of never pushing non-working history to a release branch. On the other
hand, squashing commits puts changes into chunks which may be too large.
This will make tools like git bisect much less effective, making it
more difficult for end user bug reports to pinpoint when and where the
code got broken.
Hiding the scaffolding and refactoring you used to get to the code to its current state of affairs is never a good reason to rebase.
$ git rebase -i HEAD~4Drops you into an editor session:
pick 8c4a6a5 Commit message four commits ago.
pick 4a3f436 Commit message three commits ago.
pick 949e05d Commit message two commits ago.
pick 8ae51b6 Commit message on last commit.
...
And gives you the following choices to edit into above:
p, pick = use commit
r, reword = use commit, but edit the commit message
e, edit = use commit, but stop for amending
s, squash = use commit, but meld into previous commit
f, fixup = like 'squash', but discard this commit's log message
x, exec = run command (the rest of the line) using shell
d, drop = remove commit
In the case of a merge conflict, GIT drops you back to a shell. Fix the conflict, and
$ git add ...
$ git rebase --continue # or use "git rebase --abort"If you have not pushed your changes upstream, you can use the
'git commit --amend' to update the last commit. GitHub will
refuse a push with such an amended commit if a previous version
was already pushed. You will need to do a git merge with the
upstream version to make your HEAD pushable.
You just pushed to GitHub changes you very deeply regret. GitHub rejects your amended changes.
You don't want to be a bad boy and cause grief to others by doing a
$ git push --force # DON'T Do THIS!!!The above could swallow work done by others!
There is a newer option to the git push command that may save you.
git push --force-with-leaseWhen this option is used, GIT will let you force commit this change as long as it will not overwrite any work on the remote branch when more commits were added to the remote branch.
You did not have to do a messy merge! But you may still be a bad boy. If someone had fetched your changes they will not be able to push back until they did a messy merge. Also, until they fully merge, they will still have access to your dirty little secret and could very well put it back!
There is a lot more to this command and could be useful in rebasing situations. This option can take a ref as an optional parameter. This option allows you to say that you expect the history you are updating is what you rebased and want to replace. If the remote ref still points at the commit you specified, you can be sure that no other people did anything to the ref. It is like taking a "lease" on the ref without explicitly locking it, and the remote ref is updated only if the "lease" is still valid.
--force-with-lease alone, without specifying the details, will protect all remote refs that are going to be updated by requiring their current value to be the same as the remote-tracking branch we have for them.
Note that all branches really are just pointers to commits.
To create a new branch (does not checkout the branch)
$ git branch myBranchThis branch is based on the current branch checked out.
To create a new local empty branch
$ git checkout --orphan newBranchThis will create a new branch without any commits. The first commit will
start a new history without any ancestry. The --orphan is a useful
option when you want to create a directory structure and files
resembling the ones from the current branch. Also when you want to
create something related to the effort, like documentation or a software
tool useful to the main project.
List all available local branches:
$ git branchTo also see the remote tracked branches:
$ git branch -a
Anansi
astrolog-java
bar-mph
silverdb
silver_grs_oneToolbar
* master
oneToolbar
remotes/origin/Anansi
remotes/origin/HEAD -> origin/master
remotes/origin/astrolog-java
remotes/origin/bar-mph
remotes/origin/silverdb
remotes/origin/silverdb_grs_oneToolbar
remotes/origin/masterTo view merged local GIT branches relative to current branch
$ git branch --mergedAnd unmerged local GIT branches
$ git branch --no-mergedTo delete a local branch safely, git will prevent you if you have unmerged changes.
$ git branch -d myBranchTo force delete a specified branch and permanently throw away all unmerged changes
$ git branch -D myBranchTo delete a remote branch or tag
$ git push origin --delete myBranchToDeletePrior to git version 7.0 but will still work
$ git push origin :myBranchToDeleteTo delete references to remote branches that no longer exist on their remote repos.
$ git fetch -p
$ git fetch -p some_remote_repo
$ git fetch -p originTo rename a local Branch
$ git branch -m oldname newnameTo rename the current branch to new name
$ git branch -m myNewNameTo delete an existing (local) remote tracking branch
$ git branch -d -r origin/macosTo figuring out what exactly your branches are actually tracking
$ git branch -vv
car-mhf b819996 ,origin/car-mhf, Fixed awkward language in a comment to make more clear.
goldsdb 41d7446 [origin/goldsdb] The initial View Table now contains 1 empty row.
master 28f7355 [origin/master] Up dates Resources/DTIC_GIT_Notes.txt with notes on:
* scheller-master 2c7336f [scheller/master: ahead 1] An updater to the root README.md file.
$ git remote -v
origin https://geoffrey.scheller@repos.vdl.afrl.af.mil/git/astrodynamics/astrodynamics.git (fetch)
origin https://geoffrey.scheller@repos.vdl.afrl.af.mil/git/astrodynamics/astrodynamics.git (push)
scheller ../../../scheller-linux-archive/ (fetch)
scheller ../../../scheller-linux-archive/ (push)The git checkout command lets you navigate between branches. Checking out a branch updates the files in the working directory to match the version stored in that branch, and it tells GIT to record all new commits on that branch. Think of it as a way to select which line of development you’re working on.
$ git checkout existingBranchCan also create a new branch at checkout
$ git checkout -b newBranchOr base it on another existing branch instead of the current one.
$ git checkout -b newBranch anotherBranchYou can work on multiple branches in a single repository by switching
between them with git checkout or git switch.
Check out a branch you don't have locally from origin (the repo you are tracking from, usually the one you first cloned from).
$ git fetch
$ git checkout some_new_remote_branchIf you are tracking several remote branches, you may need to be a bit more specific:
$ git checkout -b some_branch remote-name/some_branchLets say we have two remotes each with a branch with same name
$ git branch -r
origin/copperDB
origin/dmc_run
origin/master
origin/spaceRad
scheller/masterand we are already tracking origin/master.
$ git branch
copperDB
dmc_run
* masterHow do we checkout scheller/master? We give it another name
$ git checkout -b scheller-master --track scheller/masterNow we have
$ git branch
copperDB
dmc_run
master
* scheller-masterThe git merge command is used to merge another branch into your current branch. If merge successful without conflicts, you are done. If not, changes are merged into your working directory with GIT putting comments in the code. In this case, you still need to resolve the conflicts and do a git commit.
To merge a specific branch into the current branch
$ git merge someBranchNote: The branch you are merging into is the currently checked out branch. If you are in a "detached HEAD" state, you will need to create a branch to merge someBranch into.
Edit any conflicts, git add changes, commit changes if needed.
Here is an example of the workflow for a 3-way merge. Start a new feature on its own new branch
$ git checkout -b new-feature masteredit some files
$ git add file1 file2 file3
$ git commit -m 'Starting new feature X'edit some more files
$ git add file2 file4
$ git commit -m 'Finish new feature X'Parallel development on the master branch
$ git checkout masteredit some files, file3 changes somewhat orthogonal to feature X changes
$ git add file3 file5
$ git commit -m 'Make some super-stable changes to master'Merge in the new-feature branch
$ git merge new-feature
$ git commitDelete the new-feature branch once things are safely tested and merged, no sense keeping old cruft around. Best practices is to only push to origin only stable changes.
$ git branch -d new-featureStart a new feature
$ git checkout -b long-term-feature masterDo some development for a while ...
Switch to master and sync up with origin/master
$ git checkout master
$ git pull origin masterSwitch back to development branch and merge in changes from master
$ git checkout long-term-feature
$ git merge master
<edit any conflicts, 'git add' changes, commit>Continue long-term development ...
To list remote connections.
$ git remote
originUse -v for more info.
$ git remote -v
origin grs@us.navo.hpc.mil:proj/PAT (fetch)
origin grs@us.navo.hpc.mil:proj/PAT (push)
The name, this case origin, can be used as a short cut to the other repository in git commands. The name origin is the default name given to the reprository you clone from, otherwise there is nothing special about it.
To add a remote connection
$ git remote add ethel emurtz@devel.desilu.com:repo/iHateFredTo remove a connection
$ git remote rm fredTo rename connection
$ git remote rename ricky lucyDevelopers need to pull upstream commits to their local repository and push local commits to other repositories. Having connections to other individual developers makes it possible to collaborate outside of the main (or blessed) repository. This can be very useful for a small teams working on a large subproject.
To make an existing branch track a remote branch, as of GIT 1.8.0
$ git checkout bar
$ git branch -u upstream/fooNow your local branch bar is tracking the branch foo on the remote repo upstream.
If you are on a branch other than bar,
$ git branch -u upstream/foo barif you like longer options, in 1.8.0+ you can do
$ git branch --set-upstream-to=upstream/foo barTo keep things simple, best practices is to rename upstream to origin or origin-foo and rename your local branch bar to foo.
Use fetch to import commits into the tracking branch for the remote
repository. This gives you a chance to review changes before integrating
them into the local copy of the project.
$ git fetch origin
$ git diff originor with more fidelity
$ git switch someBranch
$ git fetch origin someBranch
$ git diff origin/someBranchUse git branch to view local branches and use git branch -r for
remote branches. Inspect these branches with the ususal git chechout and
git log commands. Be aware that checking out a tracking branch will
leave you in a "detached head" state.
Use git push to merge local changes into remote branches located on
remote repos.
Use git pull to merge in remote changes. This command syncs the local
tracking branch with the remote branch. Then it merges the local
tracking branch into the local branch. The git pull command is
shorthand for git fetch followed by git merge FETCH_HEAD
Remember, pushing and pulling are between different repositories.
Merging happend between branches on the same repository. Both git push
and get pull use git merge behind the scenes.
$ git pull origin someBranchTo pull from the branch you are tracking with the current branch, use
$ git pullWarning!!!
$ git pull origin someBranchwill pull in from the remote "someBranch" even if you are on the local "someOtherBranch".
Best practice is:
$ git fetch origin # So local repo knows of remote changes.
$ git status # So we are sure we are where we think.
# This will also give other potentially
# useful info to keep yourself from
# shooting yourself in foot.
$ git pullNow, git status may tell us we can't just do a fast foreword. In
that case, create a new local branch tracking the remote branch. That
way you have two places to morph before doing a git merge.
The command
$ git pullwhich actually is the same as
$ git fetch
$ git merge FETCH_HEADwhich accomplishes same thing as doing
$ git fetch origin # Sync local copy of master
# with master on origin.
$ git merge origin/master # Merge local copy from last
# fetch from origin into your
# local version of master.or, assuming master tracks origin/master
$ git fetch
$ git merge originWhat you are actually merging into your working directory is
a local copy of the remote repo. You are "tracking" the local
copy. If pull requires a password, then the fetch will too, but
not the merge. The git fetch is syncing your local copy with what
is on the remote.
More explicitly,
git checkout master # or whatever branch
git fetch origin # fetch all tracked upstream changes
git merge origin/master # local copy of upstream branchTo push local changes elsewhere
$ git push origin someBranch
$ git push origin --all
$ git push origin --tagsThe commands (with nothing else)
$ git push
$ git push originshould be avoided on older versions of GIT. On git 1.9.5, it will push all tracked branches to their remotes. In later versions of git, it will just push whatever branch you are currently on.
To remove a remote branch or tag, do
git push --delete Remote_Name Branch_or_Tag_NameOn older versions of GIT
$ git push Remote_Name :Branch_NameNote the space before the colon, similar to renaming a branch, you are pushing "nothing" into BranchName.
To push a tag,
$ git push <RemoteName> <TagName>To push all tags
$ git push <RemoteName> --tagCloning from GITHUB sets the URL to origin as
$ git config remote.origin.url
https://github.com/grscheller/scheller-linux-environmentOn Arch Linux 'git push' prompts for username and password and everything works fine. On CentOS 6.8 (git version 1.7.1) I get the error:
$ git push
error: The requested URL returned error: 403 Forbidden while accessing https://github.com/grscheller/scheller-linux-environment/info/refs
fatal: HTTP request failedTo fix this, set the URL to
$ git config remote.origin.url https://grscheller@github.com/grscheller/scheller-linux-environment.gitNot recommended, but setting it to https://grscheller:MYPASSWORD@github.com/grscheller/scheller-linux-environment.git would allow you to not have to type your password.
I think the issue is because of the really olde version of git that CentOS 6.8 uses, but it might also be related to firewall proxy issues.
When I changed the name of a repo from scheller-linux-environment to scheller-linux-archive, above value for remote.origin.url still worked, but changing the repo name part of it to something random like, like scheller-linux-foofoo, failed.
From the GITHUB website, go to Settings -> SSH and GPG keys
Paste contents of ~/.ssh/id_rsa to the SSH text box. Leave off system name at end. No newlines.
Next, from the repo, tell git to use ssh protocal
$ git remote set-url origin git@github.com:grscheller/scheller-linux-archive Now,
$ git remote -v
origin git@github.com:grscheller/scheller-linux-archive (fetch)
origin git@github.com:grscheller/scheller-linux-archive (push) Able to push to GITHUB without the password.
I created a shell alias to restart the ssh-keyserver if it should
ever get hung.
$ alias addkey
alias addkey='eval $(ssh-agent) && ssh-add'To list all current tags in alphabetical order
$ git tagTo refine the search
$ git tag -l 'v1.8.*'
$ git tag -l '*foo*'There are two types of tags, lightweight and annotated. A lightweight tag is like a branch that does not change, just a pointer to a specific commit. Annotated tags are stored as full objects in GIT database. They have the tagger's name, e-mail address, date, and can be signed with GNU Privacy Guard.
To create an annotated tag, use -a
$ git tag -a v1.0 -m 'Original DVS code gotten from David Steller'By default, git push does not push tags. To explicitly push a tag
$ git push origin v1.0To push all available tags, use the --tags option on git push
$ git push origin --tagsTo create a new branch at a specific tag, say v2.2.0
$ git checkout -b version2-2 v2.2.0To delete local tag:
$ git tag -d tag_to_deleteTo delete off of a remote repo:
$ git push origin :refs/tags/tag_to_deleteUse the show cmd
$ git show tag_nameUse 'git show' to view a previous version of file
$ git show REVISION:path/to/filefor example
$ git show HEAD~3:PAT_Files/mfiles/FilterValuesPanel.mwill send the third version back of the file to a pager.
$ git show HEAD~1:pat.m > junkwill dump it all to a file called junk.
$ git show HEAD@{2022-03-30}:./config/nvim/lua/grs/init.luawill show the version of the file as of that date.
$ git show HEAD@{2022-03-30}:./config/nvim/lua/grs|cat
tree HEAD@{2022-03-30}:./config/nvim/lua/grs
Colorscheme.lua
Completions.lua
DevEnv.lua
Options.lua
Packer.lua
Telescope.lua
TextEdit.lua
Treesitter.lua
WhichKey.lua
init.luawhich lists the files in that directory as of that date.
$ git show HEAD@{2022-06-4}will list the commit messages and diffs for that day.
Use 'git log' to view the revision history a of file
$ git log -p --follow config/nvim/init.luaThe -p tells git to show all patch information, the --follow to
follow the history even in the event that the file name was changed.
$ git log $ git log A B $ git log -p $ git log --onelineTo get the most out of git log, remember the seven rules of a great GIT commit message:
- Separate subject from body with a blank line
- Limit the subject line to 50 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line
- Wrap the body at 72 characters
- Use the body to explain what and why vs. how
Above taken from this blog post.
Let's say we have three branches, main, feature1 and feature2.
First, lets see how to compare two specific files in two specific branches against each other. Output will be similar to the Unix diff command.
$ git diff feature1:file1 feature2:file2The line between the files which are the same will be colored white, red
lines will be the items in file1 not in file2, green lines will be for
items in file2 not in file1. Left red, Right green. The red lines will
begin with a -, the green lines a +.
Compare two branches. If you leave off the :, git usually guesses
correctly what you mean. I like to be non-ambiguous.
$ git diff main: feature1:Comparing past version with what is in the working directory
$ git diff HEAD~2:DVS.m DVS.mNote that 2 revisions ago need not necessarily be on the same branch!
To comparing a file between branches
$ git diff new_feature:DVS.m master:DVS.mIf given only one argument, compare with the working directory.
The range A..B will be resolved to show each commit individually from
A to B. Files can be specified via syntax commit:path/to/file.
Pass it a directory, it will show info of the last commit changing in
that directory.
The range A...B means every commit reachable by A or B but not both.
- for
git logwill show commits it makes when used with divergent branches - for
git diffit is syntactic sugar forgit diff $(git merge-base A B) B - for
git showwill show commit info for each single commit in that range
Find the best common ancestor for a three way merge.
$ git rev-list --max-parents=0 HEAD
4a0c3cff3b6d192effeb44bc2d8429c5e9f85825
6e886a1b01a10f39b53bf8b90dba4c73625f4353 $ git log --oneline -- Actor.scala | tail -n 1
d71adde Implemented book's nonblocking fpinscala.parallelism package.
Blocking version now package fpinscala.parallelism.javaFutures.Then search for equivalent of "d71adde" in a git log.
You need to turn color off in GIT and let nvim do colorization.
$ git config --global color.pager no
$ git config --global core.pager "nvim -R -c 'set filetype=git'"The -R means read-only.
Store away current state of working directory and the index and goes back to a clean working directory:
$ git stash pushor just
$ git stashDrop the last stash entry from the list of stash entries:
$ git dropPop changes back into working directory, perhaps on a subsequent commit.
$ git stash popThis may fail due to conflicts due to changes being applied at the commit that was HEAD at the time of the stash. Resolve the conflict and use git drop.
Remove all stash entries:
$ git stash clearNote, entries will be subject to pruning, so there may be no way to recover these changes.
Typical workflow when following a remote repo:
$ git stash
$ git pull
$ git stash popTODO: Merge the following in the following
To create a temporary branch and push it upstream.
$ git branch tmpWork
$ git stash
$ git checkout tmpWork
$ git stash apply # stash apply ???
$ git add .
$ git commit
$ git push -u origin tmpWork
$ git status
On branch tmpwork
Your branch is up to date with 'origin/tmpWork'.
nothing to commit, working tree cleanTODO: Explain the difference between git checkout branch
and git switch branch. The first is a bit overloaded.
TODO: Add info on git blame
TODO: Add info on rerere
https://git-scm.com/docs/git-rerere
https://git-scm.com/book/en/v2/Git-Tools-Rerere
TODO: Add info on git maintenance
TODO: Add info on git diff --cache
TODO: Look into git-rev-list and git-show-branch
TODO: Give examples of Git Ranges in use
TODO: Update GitHub access
TODO Git reset and ~ vs ^ (time moves down)
G H I J
\ / \ /
D E F
\ | / \
\ | / |
\|/ |
B C
\ /
\ /
A
A = = A^0
B = A^ = A^1 = A~1
C = A^2
D = A^^ = A^1^1 = A~2
E = B^2 = A^^2
F = B^3 = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2 = B^^2 = A^^^2 = A~2^2
I = F^ = B^3^ = A^^3^
J = F^2 = B^3^2 = A^^3^2
Note that git-merge can join two or more development histories together.