Git is distributed version control solution.
It doesn't rely on a centralized repository like SVN. Instead, every developer / host has it's own copy of the repository.
You can pull push between repositories to get them in sync. Everyone can just work on their own repository offline.
In theory you can put a main repository on a share drive and everybody makes a copy to work on locally.
In practice there are repository hosting services like GitHub and GitLab, where the repository is hosted on internet and there are extra features (outside of git).
Download and install git
Config username and email
$git config --global user.name 'hello ned'
$git config --global user.email 'ned@helloworld.com'
Setup a Git repository
Simply create a empty folder C:/GitRepo/helloworld/ and run a git bash from the folder.
$ git init
Initialized empty Git repository in C:/GitRepo/helloworld/.git/
This command creates a .git folder where all the git version control data is kept.
Now add a file readme.md into the repository and check the status.
$ git status
Untracked files:
readme.md
It tells that the readme.md has not been added to git yet.
Now add the file and check status again.
$ git add readme.md
$ git status
Changes to be committed:
new file: readme.md
It now recognizes the new file but yet to be committed.
$ git commit readme.md -m 'created a new file'
[master (root-commit) be0fa99] created a new file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 readme.md
Branch and merge
Initially when a repo is inited, there is only one branch "master". Usually you would fork a branch to work on sth while others can fork other branches to work on.
Eventually, you will need to merge your branch back to the master branch.
Firstly to create a branch dev,
$ git branch dev
You will still be sitting in the master branch so switch to dev by checkout
$ git checkout dev
Switched to branch 'dev'
The checkout here is simply for switching between branches. A quick way to branch and switch to the branch is with the -b
make some changes to the readme.md, e.g.
$ echo 'first line' > readme.md
Alternatively, just open the file in an editor. Then commit the change. You may specify the file name or simply $git commit which commits all.
The -m is the message with the commit.
$ git commit readme.md -m 'added a line'
Now the dev branch has got the file udpated. But the master branch has not.
Switch back to the master branch.
$ git checkout master
$ cat readme.md
The file is still empty in the master branch. On switching back to master, git swaps the file back to the master's version.
You may merge the changes from the dev branch to the master.
$ git merge dev
Updating be0fa99..a26b97e
Fast-forward
readme.md | 1 +
1 file changed, 1 insertion(+)
Yeah master is merged with the changes from dev.
A bit more about commit
Git doesn’t store data as a series of changesets or differences, but instead as a series of snapshots.
When you make a commit, Git stores a commit object that contains a pointer to the snapshot of the content you staged.
A commit object contains metadata like author cheksum etc, and a tree of changes.
|---file1
commit-->tree--|---file2
|---file3
Also a commit needs to point to the commits immediately before it.
Initial commit has no parent. A normal commit points to one parent. A merge commit points all the involving branches' commits.
/<---commit 3 (dev)
/
initial commit <--commit 1 <--commit 2(master)
After a few commits & branching the whole history becomes a commit tree. Each branch label is essentially a pointer to a commit in the tree.
So the branch labels move as you commit and merge.
/<---commit 3 -------------<--\
/ \
initial commit <--commit 1 <--commit 2 <------------------------------ commit 4 (master, dev)
To see the graph of the commit history:
$ git log --graph --all
* commit a26b97e43550580c5bb38638e85ee9d6f79dcc07 (HEAD -> master, dev)
| Author: zchen <zchen@allianz-assistance.com.au>
| Date: Fri Jun 14 16:05:53 2019 +1000
|
| added a line
|
* commit be0fa995c5788398a23dea6802bcc26e24353faf
Author: zchen <zchen@allianz-assistance.com.au>
Date: Fri Jun 14 15:54:09 2019 +1000
created a new file
How does Git know which branch you are working on? It simply use a HEAD pointer to the current branch.
$ git log --decorate --oneline
a26b97e (HEAD -> master, dev) added a line
be0fa99 created a new file
A merge of two branches looks for the last common parent commit and start from there.
Both branches deviate from the last common parent commit and start their own changes. A merge will combine the changes on top of the common parent commit.
To see only commits from a branch
$ git log -v branch_name
Merge conflicts
When more than one people are changing the same file, it can cause conflicts when merging the changes.
Checkout dev branch and append ' something' to the line.
$ git checkout dev
$ echo ' something' >> readme.md
$ git commit readme.md -m 'appended sth'
Also swtich back to master and append ' different thing' to the line.
$ git checkout master
$ echo ' different thing' >> readme.md
$ git commit readme.md -m ' appended different thing'
Now if check the branchs' graph. The master and dev branches deviates from each other.
$ git log --graph --all
* commit d0bc4ba94204948b9251c48c0703c42eb88e9948 (HEAD -> master)
| Author: zchen <zchen@allianz-assistance.com.au>
| Date: Mon Jun 17 09:20:57 2019 +1000
|
| appended different thing
|
| * commit 6fae9abbf69ced855d02b718507e74fd28700388 (dev)
|/ Author: zchen <zchen@allianz-assistance.com.au>
| Date: Mon Jun 17 09:19:44 2019 +1000
|
| appended sth
|
* commit a26b97e43550580c5bb38638e85ee9d6f79dcc07
| Author: zchen <zchen@allianz-assistance.com.au>
| Date: Fri Jun 14 16:05:53 2019 +1000
|
| added a line
|
* commit be0fa995c5788398a23dea6802bcc26e24353faf
Author: zchen <zchen@allianz-assistance.com.au>
Date: Fri Jun 14 15:54:09 2019 +1000
created a new file
If we want to merge the dev's change back to the master branch.
It will advise there is a conflict because the same line has been modified differently in the two branches.
$ git merge dev
Auto-merging readme.md
CONFLICT (content): Merge conflict in readme.md
Automatic merge failed; fix conflicts and then commit the result.
Open up the file readme.md. It marks the change from HEAD (which is master) and the change from dev.
<<<<<<< HEAD
first line
different thing
=======
first line something
>>>>>>> dev
To resolve the conflict, edit the readme.md file into what you want it to be.
If edit the file outside of git using e.g. some editor, on completion it need to add the file again and commit to complete the merge
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: readme.md
no changes added to commit (use "git add" and/or "git commit -a")
Simply add the file and commit all.
$ git add readme.md
$ git commit -m 'merged changes'
Git also provides a tool for basic merging.
$ git mergetool
Merging:
readme.md
Normal merge conflict for 'readme.md':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (vimdiff):
4 files to edit
This will open command line merge windows for comparing the changes after the last common parent commit.
The edit tool (vimdiff / other) helps to edit and save.
On completion, only need to commit again
$ git commit -m 'merged changes'
[master 0611306] merged changes
Sometimes it may need to run with the -i option to conclude a conflicted merge
$ git commit -i . -m 'merged changes'
To create and checkout a branch. This is equivalent to git branch dev3 and git checkout dev3.
$ git checkout -b dev3
Switched to a new branch 'dev3'
To delete a branch after it's merged. Note if it has changes that are not merged back yet, it will fail to delete.
$ git branch -d dev2
Deleted branch dev2 (was 0611306).
To delete a branch from the remote, e.g. origin
git push -d origin <branch_name>
Centralized workflow
If one prefers the old centralized way of versioning (like SVN), Git can do it perfectly.
Simply maintain a central hub of source codes.
Every developer clones a copy of the codes, and works on their own.
The first developer that pushes his changes back to the hub can do so without any problems. The second developer must
merge the first developer's changes before he can push changes to the hub.
In Git, if a developer pushes his changes without merging previous changes, Git will decline the push. He will have
to fetch and merge first.
Integration Manager Workflow
This is the way used by GitHub and GitLab.
The repository owner/manager manages the repository. Every developer clones the repository and work on their own repo.
When a developer wants to push some changes to the main repository, he sends an email to the integration manager to ask
for a pull from the developer's repo. This is called 'Pull Request' in GitHub.
The integration manager then adds the developer's repo as a remote and pull changes from it and merge it locally.
The integration manger tests all the changes Ok and then pushes the changes to the main repo.
Pull request In practice
Developers work on different branches of the same repository. When a developer is ready to roll out something, he updates the branch by pusing the latest changes from his local laptop to the repository host. But that is only updating his branch. To merge the changes to the master branch on the host, he sends a Pull Request and specify the changes, etc. The repo manager and other developer can review and approve the request. If needed, there can be follow up commits to the pull request. Say a new fix is needed, simply the developer can update the codes and push again the changes for that branch. When everything is Ok, the repo manager simply pulls (git pull) the changes from the developer's branch to the master branch.
Note the 'pull request' is not a git commnad, it's implemented as a web interface on GitHub / Git lab, etc. You need to fill in the source repo, source branch, target repo and target branch, and reviewers, description, etc. People can have discussions on the request web page. Usually people work on the same repo so source repo and taret repo are the same. Only different branches.
Clone/fetch/push/pull/remote
The original repository can be hosted in a shared drive or on github/gitlab.
From a local folder, one can clone the repository.
If it's from a file path:
$ git clone /c/GitRepo/helloworld/
Cloning into 'helloworld'...
done.
If it's from a http service. The login / proxy settings are in the .github config file.
$ git clone https://github.com/hellobenchen/hello-world.git
Cloning into 'hello-world'...
remote: Enumerating objects: 10, done.
remote: Total 10 (delta 0), reused 0 (delta 0), pack-reused 10
Unpacking objects: 100% (10/10), done.
And git status will check if it's clean. The clone command automatically names it origin for you, pulls down all its data,
creates a pointer to where its master branch is, and names it origin/master locally. If you run git clone -o booyah instead,
then you will have booyah/master as your default remote branch.
$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
Check the remote repository:
$ git ls-remote
From C:/GitRepo/helloworld/
f98c1128f54cd71ea19aad18993490756bf1cedc HEAD
6fae9abbf69ced855d02b718507e74fd28700388 refs/heads/dev
dbc5b10ea72eb2d92050c96a95735234e0739efa refs/heads/dev3
f98c1128f54cd71ea19aad18993490756bf1cedc refs/heads/master
$ git remote show
origin
$ git remote get-url origin
C:/GitRepo/helloworld/
To manually add a remote:
$ git remote add origin C:\GitRepo\helloworld\
Note the origin/master is also a pointer in your local commits chain, same as the master pointer.
origin/master master
| |
initial commit <--commit 1 <--commit 2 <--commit3 <-- commit 4
When you make changes to your local master branch, the pointer moves on, but the origin/master remains.
The remote origin/master could change as other people could push changes to it.
You will need to fetch the latest changes from the remote origin/master branch to be able to push your changes.
$ git fetch origin
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From C:/GitRepo/helloworld
a7601f7..f98c112 master -> origin/master
Then the origin/master could move on as well locally.
<--commit5 <-- commit6 (origin/master)
|
initial commit <--commit 1 <--commit 2 <--commit3 <-- commit 4 (master)
Obviously you would need to fetch and merge the origin/master with you local master before you can push your local changes to origin.
$ git fetch origin
or
$ git fetch origin master
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From C:/GitRepo/helloworld
a7601f7..f98c112 master -> origin/master
If you don't fetch from origin and there is a conflict, you wont able to push.
$ git push origin master
To C:/GitRepo/helloworld/
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'C:/GitRepo/helloworld/'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Then merge with origin. Alternatively, one can run 'git pull origin master' which runs both fetch and merge, ie. git fetch + git merge.
$ git merge origin/master
In a centralized workflow, then push the master branch to origin
$ git push origin master
One thing to note here. If the origin repository is initializes as bare repository, git init --bare, the the push will be Ok.
But if the origin repository is initializes as non-bare repository, which means itself contains a working tree (someone working on it).
Then the push to origin will fail and warn that the push would cause in consistent work trees, because your local work tree can be different to the origin's work tree.
And obviously you don't want to overwrite other people's work tree.
So a better way is actually request the origin repository's owner to pull from your local repository. In GitHub, this is called a Pull Request.
Then the origin repo's owner will add your local repository as a remote and pull your changes in.
Now, switch to the origin repo and add the local repository as a remote
$ git remote add nonorigin /C/GitRepo/helloworld-dev/helloworld
$ git remote show
nonorigin
Then pull the master branch from non-origin to origin's master branch. Alternative you can pull to a staging branch for testing before merging back to master.
$ git pull nonorigin master
From C:/GitRepo/helloworld-dev/helloworld
* branch master -> FETCH_HEAD
Updating f98c112..7ef1407
Fast-forward
readme.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
Back to the Push method for bare repository. Firstly it needs to init the repo as bare
$ git init --bare test.git
Initialized empty Git repository in C:/GitRepo/helloworld-dev/test.git/
Add the repo as a remote in origin, so switch to origin and:
$ git remote add nonorigin-bare C:/GitRepo/helloworld-dev/test.git
Then push the change
$ git push nonorigin-bare master
Counting objects: 36, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (20/20), done.
Writing objects: 100% (36/36), 2.74 KiB | 100.00 KiB/s, done.
Total 36 (delta 8), reused 0 (delta 0)
To C:/GitRepo/helloworld-dev/test.git
* [new branch] master -> master
Above are the basic stuff. Ready to work.
Proxy for git
On linux:
git config --global http.proxy http://yourproxy:8080
On windows
edit the .gitconfig file
"
[core]
gitproxy=default-proxy
...
"
Create a local repository and push to remote
1. manually create a new repository on github
2. open git command line and change the path to a local working directory
3. init the local directory as a git repo
$git init
4. add source codes etc to the local repo and include them into the repo
$git add .
5. commit the changes
$git commit -m 'first commit'
6. Get the git repository URL, e.g. https://github.com/abc/helloworld.git
7. Add the URL to remote repository
$git remote add origin https://github.com/abc/helloworld.git
8. verify the remote URL
$git remote -v
9.1 if the git remote requires a login, then run a fetch command first, the login prompts for username password
$git fetch origin master
10. Pull (fetch & merge) the remote repo with local master branch. Some git version doesn't allow merging with unrelated history so --allow-unrelated-histories may be needed.
$git pull origin master --allow-unrelated-histories
11. Push the local repository to remote
$git push origin master
For a new local branch and push it to remote for the first time
git init
git add README.md
git commit -m "first commit"
git remote add origin http://github/... /abc.git
git push -u origin master
Use the -u flag (upstream) when you make your first push
Create a local branch and push the new branch to remote
Use the -u flag (upstream) when you make your first push
$git push -u origin your_branch_name
To delete the branch from remote
$git push origin --delete your_branch_name
Compare remote branch and local branch, e.g. compare remote master and local master
$git diff origin/master master
Push a local repo to a new Azure Devops Repo
create a new DevOps repo without initialization, no readme file
git remote add origin https://xxx@dev.azure.com/xxx/_git/yyy
git push origin main
If the repo was initialized, push wont work because it would be an independent repo already. cant' overwrite it unless using the -u parameter?