Как сбросить git bash на windows

Теперь, когда Git установлен в вашей системе, самое время настроить среду для работы с Git под себя. Это нужно сделать только один раз — при обновлении версии Git настройки сохранятся. Но, при необходимости, вы можете поменять их в любой момент, выполнив те же команды снова.

Первоначальная настройка Git

Теперь, когда Git установлен в вашей системе, самое время настроить среду для работы с Git под себя.
Это нужно сделать только один раз — при обновлении версии Git настройки сохранятся.
Но, при необходимости, вы можете поменять их в любой момент, выполнив те же команды снова.

В состав Git входит утилита git config, которая позволяет просматривать и настраивать параметры, контролирующие все аспекты работы Git, а также его внешний вид.

Эти параметры могут быть сохранены в трёх местах:

  1. Файл [path]/etc/gitconfig содержит значения, общие для всех пользователей системы и для всех их репозиториев.
    Если при запуске git config указать параметр --system, то параметры будут читаться и сохраняться именно в этот файл.
    Так как этот файл является системным, то вам потребуются права суперпользователя для внесения изменений в него.

  2. Файл ~/.gitconfig или ~/.config/git/config хранит настройки конкретного пользователя.
    Этот файл используется при указании параметра --global и применяется ко всем репозиториям, с которыми вы работаете в текущей системе.

  3. Файл config в каталоге Git (т. е. .git/config) репозитория, который вы используете в данный момент, хранит настройки конкретного репозитория.
    Вы можете заставить Git читать и писать в этот файл с помощью параметра --local, но на самом деле это значение по умолчанию.
    Неудивительно, что вам нужно находиться где-то в репозитории Git, чтобы эта опция работала правильно.

Настройки на каждом следующем уровне подменяют настройки из предыдущих уровней, то есть значения в .git/config перекрывают соответствующие значения в [path]/etc/gitconfig.

В системах семейства Windows Git ищет файл .gitconfig в каталоге $HOME (C:Users$USER для большинства пользователей).
Кроме того, Git ищет файл [path]/etc/gitconfig, но уже относительно корневого каталога MSys, который находится там, куда вы решили установить Git при запуске инсталлятора.

Если вы используете Git для Windows версии 2.х или новее, то так же обрабатывается файл конфигурации уровня системы, который имеет путь C:Documents and SettingsAll UsersApplication DataGitconfig в Windows XP или C:ProgramDataGitconfig в Windows Vista и новее.
Этот файл может быть изменён только командой git config -f <file>, запущенной с правами администратора.

Чтобы посмотреть все установленные настройки и узнать где именно они заданы, используйте команду:

$ git config --list --show-origin

Имя пользователя

Первое, что вам следует сделать после установки Git — указать ваше имя и адрес электронной почты.
Это важно, потому что каждый коммит в Git содержит эту информацию, и она включена в коммиты, передаваемые вами, и не может быть далее изменена:

$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

Опять же, если указана опция --global, то эти настройки достаточно сделать только один раз, поскольку в этом случае Git будет использовать эти данные для всего, что вы делаете в этой системе.
Если для каких-то отдельных проектов вы хотите указать другое имя или электронную почту, можно выполнить эту же команду без параметра --global в каталоге с нужным проектом.

Многие GUI-инструменты предлагают сделать это при первом запуске.

Выбор редактора

Теперь, когда вы указали своё имя, самое время выбрать текстовый редактор, который будет использоваться, если будет нужно набрать сообщение в Git.
По умолчанию Git использует стандартный редактор вашей системы, которым обычно является Vim.
Если вы хотите использовать другой текстовый редактор, например, Emacs, можно проделать следующее:

$ git config --global core.editor emacs

В системе Windows следует указывать полный путь к исполняемому файлу при установке другого текстового редактора по умолчанию.
Пути могут отличаться в зависимости от того, как работает инсталлятор.

В случае с Notepad++, популярным редактором, скорее всего вы захотите установить 32-битную версию, так как 64-битная версия ещё не поддерживает все плагины.
Если у вас 32-битная Windows или 64-битный редактор с 64-битной системой, то выполните следующее:

$ git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin"

Примечание

Vim, Emacs и Notepad++ — популярные текстовые редакторы, которые часто используются разработчиками как в Unix-подобных системах, таких как Linux и Mac, так и в Windows.
Если вы используете другой редактор или его 32-битную версию, то обратитесь к разделу Команды git config core.editor за дополнительными инструкциями как использовать его совместно с Git.

Предупреждение

В случае, если вы не установили свой редактор и не знакомы с Vim или Emacs, вы можете попасть в затруднительное положение, когда какой-либо из них будет запущен.
Например, в Windows может произойти преждевременное прерывание команды Git при попытке вызова редактора.

Настройка ветки по умолчанию

Когда вы инициализируете репозиторий командой git init, Git создаёт ветку с именем master по умолчанию.
Начиная с версии 2.28, вы можете задать другое имя для создания ветки по умолчанию.

Например, чтобы установить имя main для вашей ветки по умолчанию, выполните следующую команду:

$ git config --global init.defaultBranch main

Проверка настроек

Если вы хотите проверить используемую конфигурацию, можете использовать команду git config --list, чтобы показать все настройки, которые Git найдёт:

$ git config --list
user.name=John Doe
user.email=johndoe@example.com
color.status=auto
color.branch=auto
color.interactive=auto
color.diff=auto
...

Некоторые ключи (названия) настроек могут отображаться несколько раз, потому что Git читает настройки из разных файлов (например, из /etc/gitconfig и ~/.gitconfig).
В таком случае Git использует последнее значение для каждого ключа.

Также вы можете проверить значение конкретного ключа, выполнив git config <key>:

$ git config user.name
John Doe

Примечание

Так как Git читает значение настроек из нескольких файлов, возможна ситуация когда Git использует не то значение что вы ожидали.
В таком случае вы можете спросить Git об origin этого значения.
Git выведет имя файла, из которого значение для настройки было взято последним:

$ git config --show-origin rerere.autoUpdate
file:/home/johndoe/.gitconfig	false

Note that git config --list does display both

  • the system (<path/to/git/config>), meaning where Git has been installed.
    (By default, on Windows: C:Program FilesGit)
  • global ($HOME/.gitconfig).
    On Windows, $HOME would be %USERPROFILE%
  • and local (path/to/repo/.git/config) settings.

To see the ones you have added to your repo:

git config --local --list

To remove multiple values:

git config [<file-option>] --unset-all name [value_regex]
git config [<file-option>] --remove-section name

For instance: git config --local --remove-section alias would get rid of the aliases.

That is a bit more precise that just getting rid of the .git/config file.


why git config --local --list doesn’t show my proxy setting?

Typically because a proxy setting could have been added for all repository, globally.

 git config --global --list

That would be in ~/.gitconfig or %USERPROFILE%.gitconfig.


Note: if you want to remove a specific value (not a regex), use Git 2.30 (Q1 2021)

Various subcommands of «git config«(man) that takes value_regex learn the «--literal-value» option to take the value_regex option as a literal string.

See commit c902618 (25 Nov 2020) by Junio C Hamano (gitster).
See commit 3f1bae1, commit c90702a, commit fda4394, commit d156719, commit 2076dba, commit 247e2f8, commit 504ee12 (25 Nov 2020) by Derrick Stolee (derrickstolee).
(Merged by Junio C Hamano — gitster — in commit a10e784, 08 Dec 2020)

config: add --fixed-value option, un-implemented

Signed-off-by: Derrick Stolee

The ‘git config(man) builtin takes a ‘value-pattern‘ parameter for several actions.

This can cause confusion when expecting exact value matches instead of regex matches, especially when the input string contains metacharacters. While callers can escape the patterns themselves, it would be more friendly to allow an argument to disable the pattern matching in favor of an exact string match.

Add a new ‘--fixed-value‘ option that does not currently change the behavior.

The implementation will be filled in by later changes for each appropriate action. For now, check and test that --fixed-value will abort the command when included with an incompatible action or without a ‘value-pattern‘ argument.

The name ‘—fixed-value’ was chosen over something simpler like ‘—fixed’ because some commands allow regular expressions on the key in addition to the value.

git config now includes in its man page:

--fixed-value

When used with the value-pattern argument, treat value-pattern as
an exact string instead of a regular expression. This will restrict
the name/value pairs that are matched to only those where the value
is exactly equal to the value-pattern.

git

  • Git

Хочу вычистить все под ноль и установить заново с новыми настройками. Но простое удаление гита не помогает.


  • Вопрос задан

    более трёх лет назад

  • 3281 просмотр


Комментировать


Решения вопроса 1

impexler

@impexler

front-end манит меня к себе

в учетных записях удали строки prntscr.com/hoyglb и Панель управления-> система -> дополнительные параметры -> переменные среды Path почисти.


Комментировать

Пригласить эксперта


Похожие вопросы


  • Показать ещё
    Загружается…

07 февр. 2023, в 01:24

20000 руб./за проект

06 февр. 2023, в 23:59

150000 руб./за проект

06 февр. 2023, в 23:16

7000 руб./за проект

Минуточку внимания

If this problem comes on a Windows machine, do the following.

  • Go to Credential Manager

    • in German, it is called: Anmeldeinformationsverwaltung
    • in French, it is called: Gestionnaire d’identification
    • in Polish, it is called: Menedżer poświadczeń
    • in Portuguese, it is called: Gerenciador de Credenciais
    • in Russian, it is called: Диспетчер учётных данных
    • in Spanish, it is called: Administrador de credenciales
    • in Norwegian, it is called: Legitimasjonsbehandling
    • in Czech, it is called: Správce pověření
    • in Dutch, it is called: Referentiebeheer

    Go to Credential Manager

  • Go to Windows Credentials

  • Delete the entries under Generic Credentials

    Go to Windows Credentials and Delete the entries under Generic Credentials

  • Try connecting again. This time, it should prompt you for the correct username and password.

brz's user avatar

brz

1,78120 silver badges21 bronze badges

answered Sep 21, 2016 at 6:24

Venkataramana Madugula's user avatar

16

The Git credential cache runs a daemon process which caches your credentials in memory and hands them out on demand. So killing your git-credential-cache—daemon process throws all these away and results in re-prompting you for your password if you continue to use this as the cache.helper option.

You could also disable use of the Git credential cache using git config --global --unset credential.helper. Then reset this, and you would continue to have the cached credentials available for other repositories (if any). You may also need to do git config --system --unset credential.helper if this has been set in the system configuration file (for example, Git for Windows 2).

On Windows you might be better off using the manager helper (git config --global credential.helper manager). This stores your credentials in the Windows credential store which has a Control Panel interface where you can delete or edit your stored credentials. With this store, your details are secured by your Windows login and can persist over multiple sessions. The manager helper included in Git for Windows 2.x has replaced the earlier wincred helper that was added in Git for Windows 1.8.1.1. A similar helper called winstore is also available online and was used with GitExtensions as it offers a more GUI driven interface. The manager helper offers the same GUI interface as winstore.

Extract from the Windows 10 support page detailing the Windows credential manager:

To open Credential Manager, type «credential manager» in the search box on the taskbar and select Credential Manager Control panel.

And then select Windows Credentials to edit (=remove or modify) the stored git credentials for a given URL.

Community's user avatar

answered Mar 13, 2013 at 10:38

patthoyts's user avatar

14

Retype:

$ git config credential.helper store

And then you will be prompted to enter your credentials again.

WARNING

Using this helper will store your passwords unencrypted on disk

Source: https://git-scm.com/docs/git-credential-store

answered Nov 18, 2014 at 18:55

aaafly's user avatar

aaaflyaaafly

2,8181 gold badge14 silver badges11 bronze badges

5

I faced the same issue as the OP. It was taking my old Git credentials stored somewhere on the system and I wanted to use Git with my new credentials, so I ran the command

$ git config --system --list

It showed

credential.helper=manager

Whenever I performed git push it was taking my old username which I set long back, and I wanted to use new a GitHub account to push changes. I later found that my old GitHub account credentials was stored under
Control PanelUser AccountsCredential ManagerManage Windows Credentials.

Manage Windows Credentials

I just removed these credentials and when I performed git push it asked me for my GitHub credentials, and it worked like a charm.

Peter Mortensen's user avatar

answered Sep 20, 2016 at 11:09

Mourya's user avatar

MouryaMourya

2,2803 gold badges18 silver badges24 bronze badges

7

Try using the below command.

git credential-manager

Here you can get various options to manage your credentials (check the below screen).

Enter image description here

Or you can even directly try this command:

git credential-manager uninstall

This will start prompting for passwords again on each server interaction request.

Peter Mortensen's user avatar

answered Aug 11, 2016 at 5:57

Himanshu Aggarwal's user avatar

8

I found something that worked for me. When I wrote my comment to the OP I had failed to check the system config file:

git config --system -l

shows a

credential.helper=!github --credentials

line. I unset it with

git config --system --unset credential.helper

and now the credentials are forgotten.

Jaap's user avatar

Jaap

61913 silver badges19 bronze badges

answered Nov 18, 2015 at 11:21

damix911's user avatar

damix911damix911

4,1051 gold badge28 silver badges44 bronze badges

3

git config --list

will show credential.helper = manager (this is on a windows machine)

To disable this cached username/password for your current local git folder, simply enter

git config credential.helper ""

This way, git will prompt for password every time, ignoring what’s saved inside «manager».

answered Sep 25, 2017 at 14:51

Zhe Hu's user avatar

Zhe HuZhe Hu

3,6474 gold badges32 silver badges42 bronze badges

6

This error appears when you are using multiple Git accounts on the same machine.

If you are using macOS then you can remove the saved credentials of github.com.

Please follow below steps to remove the github.com credentials.

  1. Open Keychain Access
  2. Find github
  3. Select the github.com and Right click on it
  4. Delete «github.com»
  5. Try again to Push or Pull to git and it will ask for the credentials.
  6. Enter valid credentials for repository account.
  7. Done

    enter image description here

brian d foy's user avatar

brian d foy

127k31 gold badges204 silver badges581 bronze badges

answered Dec 3, 2018 at 11:11

Pratik Patel's user avatar

Pratik PatelPratik Patel

2,1093 gold badges22 silver badges29 bronze badges

2

In my case, Git is using Windows to store credentials.

All you have to do is remove the stored credentials stored in your Windows account:

Windows credentials menu

Peter Mortensen's user avatar

answered Mar 2, 2017 at 14:20

SoliQuiD's user avatar

SoliQuiDSoliQuiD

1,9931 gold badge23 silver badges29 bronze badges

2

You have to update it in your Credential Manager.

Go to Control Panel > User Accounts > Credential Manager > Windows Credentials. You will see Git credentials in the list (e.g. git:https://). Click on it, update the password, and execute git pull/push command from your Git bash and it won’t throw any more error messages.

Stephen Rauch's user avatar

Stephen Rauch

46.7k31 gold badges109 silver badges131 bronze badges

answered Jun 18, 2018 at 23:45

ChiragBhalgami's user avatar

2

In Windows 2003 Server with «wincred»*, none of the other answers helped me. I had to use cmdkey.

  • cmdkey /list lists all stored credentials.
  • cmdkey /delete:Target deletes the credential with «Target» name.

cmdkey /list; cmdkey /delete:Target

(* By «wincred» I mean git config --global credential.helper wincred)

answered Dec 12, 2016 at 23:55

ericbn's user avatar

ericbnericbn

9,7023 gold badges45 silver badges53 bronze badges

1

Using latest version of git for Windows on Windows 10 Professional and I had a similar issue whereby I have two different GitHub accounts and also a Bitbucket account so things got a bit confusing for VS2017, git extensions and git bash.

I first checked how git was handling my credentials with this command (run git bash with elevated commands or you get errors):

git config --list

I found the entry Credential Manager so I clicked on the START button > typed Credential Manager to and left-clicked on the credential manager yellow safe icon which launched the app. I then clicked on the Windows Credentials tabs and found the entry for my current git account which happened to be Bit-bucket so I deleted this account.

But this didn’t do the trick so the next step was to unset the credentials and I did this from the repository directory on my laptop that contains the GitHub project I am trying to push to the remote. I typed the following command:

git config --system --unset credential.helper

Then I did a git push and I was prompted for a GitHub username which I entered (the correct one I needed) and then the associated password and everything got pushed correctly.

I am not sure how much of an issue this is going forward most people probably work off the one repository but I have to work across several and using different providers so may encounter this issue again.

Community's user avatar

answered Sep 16, 2018 at 15:51

Trevor's user avatar

TrevorTrevor

1,5311 gold badge19 silver badges27 bronze badges

1

Got same error when doing a ‘git pull’ and this is how I fixed it.

  1. Change repo to HTTPS
  2. Run command git config --system --unset credential.helper
  3. Run command git config --system --add credential.helper manager
  4. Test command git pull
  5. Enter credentials in the login window that pops up.
  6. Git pull completed successfully.

Daniel Fisher  lennybacon's user avatar

answered Oct 1, 2018 at 3:25

Amer Bashoeb's user avatar

2

If you want git to forget old saved credentials and re-enter username and password, you can do that using below command:

git credential-cache exit

After running above command, if you try to push anything it will provide option to enter username and password.

answered Dec 13, 2019 at 8:25

amitshree's user avatar

amitshreeamitshree

1,9342 gold badges22 silver badges40 bronze badges

5

In case Git Credential Manager for Windows is used (which current versions usually do):

git credential-manager clear

This was added mid-2016. To check if credential manager is used:

git config --global credential.helper
→ manager

answered Jan 23, 2018 at 15:53

Simon A. Eugster's user avatar

Simon A. EugsterSimon A. Eugster

4,0544 gold badges35 silver badges31 bronze badges

  1. Go to C:Users<current-user>
  2. check for .git-credentials file
  3. Delete content or modify as per your requirement
  4. Restart your terminal

answered Jul 16, 2018 at 21:00

Dinesh Patil's user avatar

Dinesh PatilDinesh Patil

1,03210 silver badges13 bronze badges

1

If your credentials are stored in the credential helper (generally the case), the portable way to remove a password persisted for a specific host is to call git credential reject:

  • in one line:

    $ echo "url=https://appharbor.com" | git credential reject
    
  • or interactively:

    $ git credential reject
    protocol=https
    host=gitlab.com
    username=me@example.com
    ↵
    
    • ↵ is the Enter symbol, just hit Enter key twice at the end of input, don’t copy/paste it
    • The username doesn’t seem recognized by wincred, so avoid to filter by username on Windows

After that, to enter your new password, type git fetch.

https://git-scm.com/docs/git-credential

answered Jun 27, 2020 at 10:39

Marsu's user avatar

MarsuMarsu

7366 silver badges9 bronze badges

0

Need to login with respective github username and password

To Clear the username and password in windows

Control PanelUser AccountsCredential Manager

Edit the windows Credential

Remove the existing user and now go to command prompt write the push command it shows a github pop-up to enter the username/email and password .

Now we able to push the code after switching the user.

answered Feb 12, 2018 at 13:28

Amit K's user avatar

Amit KAmit K

1711 silver badge4 bronze badges

0

In my case, I couldn’t find the credentials saved in the Windows Credential Manager (Windows 7).

I was able to reset my credentials by executing

git config --global credential.helper wincred

It was honestly a hail Mary to see if it would wipe out my credentials and it actually worked.

answered Feb 22, 2017 at 17:53

Jeff LaFay's user avatar

Jeff LaFayJeff LaFay

12.7k13 gold badges72 silver badges100 bronze badges

1

Remove this line from your .gitconfig file located in the Windows’ currently logged-in user folder:

[credential]
helper = !"C:/Program Files (x86)/GitExtensions/GitCredentialWinStore/git-credential-winstore.exe"

This worked for me and now when I push to remote it asks for my password again.

Peter Mortensen's user avatar

answered Aug 22, 2013 at 9:23

Liviu Mandras's user avatar

Liviu MandrasLiviu Mandras

6,5041 gold badge41 silver badges63 bronze badges

0

On Windows, at least, git remote show [remote-name] will work, e.g.

git remote show origin

answered May 2, 2019 at 19:57

Jason S's user avatar

Jason SJason S

182k161 gold badges594 silver badges954 bronze badges

4

This approach worked for me and should be agnostic of OS. It’s a little heavy-handed, but was quick and allowed me to reenter credentials.

Simply find the remote alias for which you wish to reenter credentials.

$ git remote -v 
origin  https://bitbucket.org/org/~username/your-project.git (fetch)
origin  https://bitbucket.org/org/~username/your-project.git (push)

Copy the project path (https://bitbucket.org/org/~username/your-project.git)

Then remove the remote

$ git remote remove origin

Then add it back

$ git remote add origin https://bitbucket.org/org/~username/your-project.git

answered Jul 16, 2020 at 16:08

Josh's user avatar

JoshJosh

6547 silver badges9 bronze badges

2

You can remove the line credential.helper=!github --credentials from the following file C:Program FilesGitmingw64etcgitconfig in order to remove the credentials for git

Liam's user avatar

Liam

26.7k27 gold badges120 silver badges184 bronze badges

answered May 25, 2016 at 4:19

Yoan Pumar's user avatar

Yoan PumarYoan Pumar

941 silver badge3 bronze badges

0

For macOS users :

This error appears when you are using multiple Git accounts on the same machine.

Please follow below steps to remove the github.com credentials.

  1. Go to Finder
  2. Go to Applications
  3. Go to Utilities Folder
  4. Open Keychain Access
  5. Select the github.com and Right click on it

Delete «github.com»

Try again to Push or Pull to git and it will ask for the credentials.
Enter valid credentials for repository account.
Done, now upvote the answer.

answered Dec 4, 2018 at 4:18

Suraj Chandgude's user avatar

No answer given worked for me. But here is what worked for me in the end:

rm -rf ~/.git-credentials

That will remove any credentials! When you use a new git command, you will be asked for a password!

answered Jan 7, 2022 at 8:34

Alex's user avatar

AlexAlex

41.1k81 gold badges234 silver badges448 bronze badges

1

Building from @patthoyts’s high-voted answer:

His answer uses but doesn’t explain local vs. global vs. system configs. The official git documentation for them is here and worth reading.

For example, I’m on Linux, and don’t use a system config, so I never use a --system flag, but do commonly need to differentiate between --local and --global configs.

My use case is I’ve got two Github crendentials; one for work, and one for play.

Here’s how I would handle the problem:

$ cd work
# do and commit work
$ git push origin develop
# Possibly prompted for credentials if I haven't configured my remotes to automate that. 
# We're assuming that now I've stored my "work" credentials with git's credential helper.

$ cd ~/play 
# do and commit play
$ git push origin develop                                                                   
remote: Permission to whilei/specs.git denied to whilei.                
fatal: unable to access 'https://github.com/workname/specs.git/': The requested URL returned error: 403

# So here's where it goes down:
$ git config --list | grep cred
credential.helper=store # One of these is for _local_
credential.helper=store # And one is for _global_

$ git config --global --unset credential.helper
$ git config --list | grep cred
credential.helper=store # My _local_ config still specifies 'store'
$ git config --unset credential.helper
$ git push origin develop
Username for 'https://github.com': whilei
Password for 'https://whilei@github.com':
Counting objects: 3, done.
Delta compression using up to 12 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 1.10 KiB | 1.10 MiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/whilei/specs.git
   b2ca528..f64f065  master -> master

# Now let's turn credential-helping back on:
$ git config --global credential.helper "store"
$ git config credential.helper "store"
$ git config --list | grep cred
credential.helper=store # Put it back the way it was.
credential.helper=store

It’s also worth noting that there are ways to avoid this problem altogether, for example, you can use ~/.ssh/config‘s with associated SSH keys for Github (one for work, one for play) and correspondingly custom-named remote hosts to solve authentication contextualizing too.

Bazer Con's user avatar

answered Dec 21, 2018 at 18:42

irbanana's user avatar

irbananairbanana

82012 silver badges19 bronze badges

What finally fixed this for me was to use GitHub desktop, go to repository settings, and remove user:pass@ from the repository url. Then, I attempted a push from the command line and was prompted for login credentials. After I put those in everything went back to normal. Both Visual Studio and command line are working, and of course, GitHub desktop.

GitHub Desktop->Repository->Repository Settings->Remote tab

Change Primary Remote Repository (origin) from:

https://pork@muffins@github.com/MyProject/MyProject.git

To:

https://github.com/MyProject/MyProject.git

Click «Save»

Credentials will be cleared.

answered Aug 4, 2018 at 8:40

Nick Pirocanac's user avatar

2

Update Actually useHttpPath is a git configuration, which should work for all GCMs. Corrected.

Summary of The Original Question

  • working with git on Windows
  • working on multiple repositories on GitHub
  • wrong credentials used for another GitHub repository

Although the title says «Remove credentials», the description leads me to the assumption that you may have multiple accounts on GitHub, e.g. for job-related vs. private projects. (At least that issue made me find this topic.)
If so read on, otherwise, ignore the answer, but it may come in handy at some time.

Reason

Git Credential Managers (short GCM) like Microsoft’s GCM for Windows store credentials per host by default.
This can be verified by checking the Windows Credential Manager (see other answers on how to access it on English, French, and German Windows versions).
So working with multiple accounts on the same host (here github.com) is not possible by default.

In October 2020 GCM for Windows got deprecated and superseded by GCM Core. The information here still applies to the new GCM and it should even use the credentials stored by GCM for Windows.

Solution

Configure git to include the full path to the repository as additional information for each credential entry. Also documented on GCM for Windows.
I personally prefer to include the HTTP(S) [repository] path to be able to use a separate account for each and every repository.

For all possible hosts:

git config --global credential.useHttpPath true

For github.com only:

git config --global credential.github.com.useHttpPath true

Have a look at the GCM and git docs and maybe you want to specify something different.

Vlad L.'s user avatar

Vlad L.

1541 silver badge9 bronze badges

answered Jun 20, 2020 at 22:59

Maddes's user avatar

MaddesMaddes

1811 silver badge4 bronze badges

In our case, clearing the password in the user’s .git-credentials file worked.

c:users[username].git-credentials

Bazer Con's user avatar

answered May 4, 2016 at 18:28

Fares's user avatar

FaresFares

6595 silver badges11 bronze badges

2

For Windows 10, go to below path,

Control PanelUser AccountsCredential Manager

There will be 2 tabs at this location,

  1. Web credentials and 2. Windows credentials.

Click on Windows credentials tab and here you can see your stored github credentials,
under «Generic credentials» heading.

You can remove those from here and try and re-clone — it will ask for username/password now as we have just removed the stored credential from the Windows 10 systems

answered Mar 3, 2020 at 8:28

AADProgramming's user avatar

AADProgrammingAADProgramming

5,88211 gold badges37 silver badges58 bronze badges

If this problem comes on a Windows machine, do the following.

  • Go to Credential Manager

    • in German, it is called: Anmeldeinformationsverwaltung
    • in French, it is called: Gestionnaire d’identification
    • in Polish, it is called: Menedżer poświadczeń
    • in Portuguese, it is called: Gerenciador de Credenciais
    • in Russian, it is called: Диспетчер учётных данных
    • in Spanish, it is called: Administrador de credenciales
    • in Norwegian, it is called: Legitimasjonsbehandling
    • in Czech, it is called: Správce pověření
    • in Dutch, it is called: Referentiebeheer

    Go to Credential Manager

  • Go to Windows Credentials

  • Delete the entries under Generic Credentials

    Go to Windows Credentials and Delete the entries under Generic Credentials

  • Try connecting again. This time, it should prompt you for the correct username and password.

brz's user avatar

brz

1,78120 silver badges21 bronze badges

answered Sep 21, 2016 at 6:24

Venkataramana Madugula's user avatar

16

The Git credential cache runs a daemon process which caches your credentials in memory and hands them out on demand. So killing your git-credential-cache—daemon process throws all these away and results in re-prompting you for your password if you continue to use this as the cache.helper option.

You could also disable use of the Git credential cache using git config --global --unset credential.helper. Then reset this, and you would continue to have the cached credentials available for other repositories (if any). You may also need to do git config --system --unset credential.helper if this has been set in the system configuration file (for example, Git for Windows 2).

On Windows you might be better off using the manager helper (git config --global credential.helper manager). This stores your credentials in the Windows credential store which has a Control Panel interface where you can delete or edit your stored credentials. With this store, your details are secured by your Windows login and can persist over multiple sessions. The manager helper included in Git for Windows 2.x has replaced the earlier wincred helper that was added in Git for Windows 1.8.1.1. A similar helper called winstore is also available online and was used with GitExtensions as it offers a more GUI driven interface. The manager helper offers the same GUI interface as winstore.

Extract from the Windows 10 support page detailing the Windows credential manager:

To open Credential Manager, type «credential manager» in the search box on the taskbar and select Credential Manager Control panel.

And then select Windows Credentials to edit (=remove or modify) the stored git credentials for a given URL.

Community's user avatar

answered Mar 13, 2013 at 10:38

patthoyts's user avatar

14

Retype:

$ git config credential.helper store

And then you will be prompted to enter your credentials again.

WARNING

Using this helper will store your passwords unencrypted on disk

Source: https://git-scm.com/docs/git-credential-store

answered Nov 18, 2014 at 18:55

aaafly's user avatar

aaaflyaaafly

2,8181 gold badge14 silver badges11 bronze badges

5

I faced the same issue as the OP. It was taking my old Git credentials stored somewhere on the system and I wanted to use Git with my new credentials, so I ran the command

$ git config --system --list

It showed

credential.helper=manager

Whenever I performed git push it was taking my old username which I set long back, and I wanted to use new a GitHub account to push changes. I later found that my old GitHub account credentials was stored under
Control PanelUser AccountsCredential ManagerManage Windows Credentials.

Manage Windows Credentials

I just removed these credentials and when I performed git push it asked me for my GitHub credentials, and it worked like a charm.

Peter Mortensen's user avatar

answered Sep 20, 2016 at 11:09

Mourya's user avatar

MouryaMourya

2,2803 gold badges18 silver badges24 bronze badges

7

Try using the below command.

git credential-manager

Here you can get various options to manage your credentials (check the below screen).

Enter image description here

Or you can even directly try this command:

git credential-manager uninstall

This will start prompting for passwords again on each server interaction request.

Peter Mortensen's user avatar

answered Aug 11, 2016 at 5:57

Himanshu Aggarwal's user avatar

8

I found something that worked for me. When I wrote my comment to the OP I had failed to check the system config file:

git config --system -l

shows a

credential.helper=!github --credentials

line. I unset it with

git config --system --unset credential.helper

and now the credentials are forgotten.

Jaap's user avatar

Jaap

61913 silver badges19 bronze badges

answered Nov 18, 2015 at 11:21

damix911's user avatar

damix911damix911

4,1051 gold badge28 silver badges44 bronze badges

3

git config --list

will show credential.helper = manager (this is on a windows machine)

To disable this cached username/password for your current local git folder, simply enter

git config credential.helper ""

This way, git will prompt for password every time, ignoring what’s saved inside «manager».

answered Sep 25, 2017 at 14:51

Zhe Hu's user avatar

Zhe HuZhe Hu

3,6474 gold badges32 silver badges42 bronze badges

6

This error appears when you are using multiple Git accounts on the same machine.

If you are using macOS then you can remove the saved credentials of github.com.

Please follow below steps to remove the github.com credentials.

  1. Open Keychain Access
  2. Find github
  3. Select the github.com and Right click on it
  4. Delete «github.com»
  5. Try again to Push or Pull to git and it will ask for the credentials.
  6. Enter valid credentials for repository account.
  7. Done

    enter image description here

brian d foy's user avatar

brian d foy

127k31 gold badges204 silver badges581 bronze badges

answered Dec 3, 2018 at 11:11

Pratik Patel's user avatar

Pratik PatelPratik Patel

2,1093 gold badges22 silver badges29 bronze badges

2

In my case, Git is using Windows to store credentials.

All you have to do is remove the stored credentials stored in your Windows account:

Windows credentials menu

Peter Mortensen's user avatar

answered Mar 2, 2017 at 14:20

SoliQuiD's user avatar

SoliQuiDSoliQuiD

1,9931 gold badge23 silver badges29 bronze badges

2

You have to update it in your Credential Manager.

Go to Control Panel > User Accounts > Credential Manager > Windows Credentials. You will see Git credentials in the list (e.g. git:https://). Click on it, update the password, and execute git pull/push command from your Git bash and it won’t throw any more error messages.

Stephen Rauch's user avatar

Stephen Rauch

46.7k31 gold badges109 silver badges131 bronze badges

answered Jun 18, 2018 at 23:45

ChiragBhalgami's user avatar

2

In Windows 2003 Server with «wincred»*, none of the other answers helped me. I had to use cmdkey.

  • cmdkey /list lists all stored credentials.
  • cmdkey /delete:Target deletes the credential with «Target» name.

cmdkey /list; cmdkey /delete:Target

(* By «wincred» I mean git config --global credential.helper wincred)

answered Dec 12, 2016 at 23:55

ericbn's user avatar

ericbnericbn

9,7023 gold badges45 silver badges53 bronze badges

1

Using latest version of git for Windows on Windows 10 Professional and I had a similar issue whereby I have two different GitHub accounts and also a Bitbucket account so things got a bit confusing for VS2017, git extensions and git bash.

I first checked how git was handling my credentials with this command (run git bash with elevated commands or you get errors):

git config --list

I found the entry Credential Manager so I clicked on the START button > typed Credential Manager to and left-clicked on the credential manager yellow safe icon which launched the app. I then clicked on the Windows Credentials tabs and found the entry for my current git account which happened to be Bit-bucket so I deleted this account.

But this didn’t do the trick so the next step was to unset the credentials and I did this from the repository directory on my laptop that contains the GitHub project I am trying to push to the remote. I typed the following command:

git config --system --unset credential.helper

Then I did a git push and I was prompted for a GitHub username which I entered (the correct one I needed) and then the associated password and everything got pushed correctly.

I am not sure how much of an issue this is going forward most people probably work off the one repository but I have to work across several and using different providers so may encounter this issue again.

Community's user avatar

answered Sep 16, 2018 at 15:51

Trevor's user avatar

TrevorTrevor

1,5311 gold badge19 silver badges27 bronze badges

1

Got same error when doing a ‘git pull’ and this is how I fixed it.

  1. Change repo to HTTPS
  2. Run command git config --system --unset credential.helper
  3. Run command git config --system --add credential.helper manager
  4. Test command git pull
  5. Enter credentials in the login window that pops up.
  6. Git pull completed successfully.

Daniel Fisher  lennybacon's user avatar

answered Oct 1, 2018 at 3:25

Amer Bashoeb's user avatar

2

If you want git to forget old saved credentials and re-enter username and password, you can do that using below command:

git credential-cache exit

After running above command, if you try to push anything it will provide option to enter username and password.

answered Dec 13, 2019 at 8:25

amitshree's user avatar

amitshreeamitshree

1,9342 gold badges22 silver badges40 bronze badges

5

In case Git Credential Manager for Windows is used (which current versions usually do):

git credential-manager clear

This was added mid-2016. To check if credential manager is used:

git config --global credential.helper
→ manager

answered Jan 23, 2018 at 15:53

Simon A. Eugster's user avatar

Simon A. EugsterSimon A. Eugster

4,0544 gold badges35 silver badges31 bronze badges

  1. Go to C:Users<current-user>
  2. check for .git-credentials file
  3. Delete content or modify as per your requirement
  4. Restart your terminal

answered Jul 16, 2018 at 21:00

Dinesh Patil's user avatar

Dinesh PatilDinesh Patil

1,03210 silver badges13 bronze badges

1

If your credentials are stored in the credential helper (generally the case), the portable way to remove a password persisted for a specific host is to call git credential reject:

  • in one line:

    $ echo "url=https://appharbor.com" | git credential reject
    
  • or interactively:

    $ git credential reject
    protocol=https
    host=gitlab.com
    username=me@example.com
    ↵
    
    • ↵ is the Enter symbol, just hit Enter key twice at the end of input, don’t copy/paste it
    • The username doesn’t seem recognized by wincred, so avoid to filter by username on Windows

After that, to enter your new password, type git fetch.

https://git-scm.com/docs/git-credential

answered Jun 27, 2020 at 10:39

Marsu's user avatar

MarsuMarsu

7366 silver badges9 bronze badges

0

Need to login with respective github username and password

To Clear the username and password in windows

Control PanelUser AccountsCredential Manager

Edit the windows Credential

Remove the existing user and now go to command prompt write the push command it shows a github pop-up to enter the username/email and password .

Now we able to push the code after switching the user.

answered Feb 12, 2018 at 13:28

Amit K's user avatar

Amit KAmit K

1711 silver badge4 bronze badges

0

In my case, I couldn’t find the credentials saved in the Windows Credential Manager (Windows 7).

I was able to reset my credentials by executing

git config --global credential.helper wincred

It was honestly a hail Mary to see if it would wipe out my credentials and it actually worked.

answered Feb 22, 2017 at 17:53

Jeff LaFay's user avatar

Jeff LaFayJeff LaFay

12.7k13 gold badges72 silver badges100 bronze badges

1

Remove this line from your .gitconfig file located in the Windows’ currently logged-in user folder:

[credential]
helper = !"C:/Program Files (x86)/GitExtensions/GitCredentialWinStore/git-credential-winstore.exe"

This worked for me and now when I push to remote it asks for my password again.

Peter Mortensen's user avatar

answered Aug 22, 2013 at 9:23

Liviu Mandras's user avatar

Liviu MandrasLiviu Mandras

6,5041 gold badge41 silver badges63 bronze badges

0

On Windows, at least, git remote show [remote-name] will work, e.g.

git remote show origin

answered May 2, 2019 at 19:57

Jason S's user avatar

Jason SJason S

182k161 gold badges594 silver badges954 bronze badges

4

This approach worked for me and should be agnostic of OS. It’s a little heavy-handed, but was quick and allowed me to reenter credentials.

Simply find the remote alias for which you wish to reenter credentials.

$ git remote -v 
origin  https://bitbucket.org/org/~username/your-project.git (fetch)
origin  https://bitbucket.org/org/~username/your-project.git (push)

Copy the project path (https://bitbucket.org/org/~username/your-project.git)

Then remove the remote

$ git remote remove origin

Then add it back

$ git remote add origin https://bitbucket.org/org/~username/your-project.git

answered Jul 16, 2020 at 16:08

Josh's user avatar

JoshJosh

6547 silver badges9 bronze badges

2

You can remove the line credential.helper=!github --credentials from the following file C:Program FilesGitmingw64etcgitconfig in order to remove the credentials for git

Liam's user avatar

Liam

26.7k27 gold badges120 silver badges184 bronze badges

answered May 25, 2016 at 4:19

Yoan Pumar's user avatar

Yoan PumarYoan Pumar

941 silver badge3 bronze badges

0

For macOS users :

This error appears when you are using multiple Git accounts on the same machine.

Please follow below steps to remove the github.com credentials.

  1. Go to Finder
  2. Go to Applications
  3. Go to Utilities Folder
  4. Open Keychain Access
  5. Select the github.com and Right click on it

Delete «github.com»

Try again to Push or Pull to git and it will ask for the credentials.
Enter valid credentials for repository account.
Done, now upvote the answer.

answered Dec 4, 2018 at 4:18

Suraj Chandgude's user avatar

No answer given worked for me. But here is what worked for me in the end:

rm -rf ~/.git-credentials

That will remove any credentials! When you use a new git command, you will be asked for a password!

answered Jan 7, 2022 at 8:34

Alex's user avatar

AlexAlex

41.1k81 gold badges234 silver badges448 bronze badges

1

Building from @patthoyts’s high-voted answer:

His answer uses but doesn’t explain local vs. global vs. system configs. The official git documentation for them is here and worth reading.

For example, I’m on Linux, and don’t use a system config, so I never use a --system flag, but do commonly need to differentiate between --local and --global configs.

My use case is I’ve got two Github crendentials; one for work, and one for play.

Here’s how I would handle the problem:

$ cd work
# do and commit work
$ git push origin develop
# Possibly prompted for credentials if I haven't configured my remotes to automate that. 
# We're assuming that now I've stored my "work" credentials with git's credential helper.

$ cd ~/play 
# do and commit play
$ git push origin develop                                                                   
remote: Permission to whilei/specs.git denied to whilei.                
fatal: unable to access 'https://github.com/workname/specs.git/': The requested URL returned error: 403

# So here's where it goes down:
$ git config --list | grep cred
credential.helper=store # One of these is for _local_
credential.helper=store # And one is for _global_

$ git config --global --unset credential.helper
$ git config --list | grep cred
credential.helper=store # My _local_ config still specifies 'store'
$ git config --unset credential.helper
$ git push origin develop
Username for 'https://github.com': whilei
Password for 'https://whilei@github.com':
Counting objects: 3, done.
Delta compression using up to 12 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 1.10 KiB | 1.10 MiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/whilei/specs.git
   b2ca528..f64f065  master -> master

# Now let's turn credential-helping back on:
$ git config --global credential.helper "store"
$ git config credential.helper "store"
$ git config --list | grep cred
credential.helper=store # Put it back the way it was.
credential.helper=store

It’s also worth noting that there are ways to avoid this problem altogether, for example, you can use ~/.ssh/config‘s with associated SSH keys for Github (one for work, one for play) and correspondingly custom-named remote hosts to solve authentication contextualizing too.

Bazer Con's user avatar

answered Dec 21, 2018 at 18:42

irbanana's user avatar

irbananairbanana

82012 silver badges19 bronze badges

What finally fixed this for me was to use GitHub desktop, go to repository settings, and remove user:pass@ from the repository url. Then, I attempted a push from the command line and was prompted for login credentials. After I put those in everything went back to normal. Both Visual Studio and command line are working, and of course, GitHub desktop.

GitHub Desktop->Repository->Repository Settings->Remote tab

Change Primary Remote Repository (origin) from:

https://pork@muffins@github.com/MyProject/MyProject.git

To:

https://github.com/MyProject/MyProject.git

Click «Save»

Credentials will be cleared.

answered Aug 4, 2018 at 8:40

Nick Pirocanac's user avatar

2

Update Actually useHttpPath is a git configuration, which should work for all GCMs. Corrected.

Summary of The Original Question

  • working with git on Windows
  • working on multiple repositories on GitHub
  • wrong credentials used for another GitHub repository

Although the title says «Remove credentials», the description leads me to the assumption that you may have multiple accounts on GitHub, e.g. for job-related vs. private projects. (At least that issue made me find this topic.)
If so read on, otherwise, ignore the answer, but it may come in handy at some time.

Reason

Git Credential Managers (short GCM) like Microsoft’s GCM for Windows store credentials per host by default.
This can be verified by checking the Windows Credential Manager (see other answers on how to access it on English, French, and German Windows versions).
So working with multiple accounts on the same host (here github.com) is not possible by default.

In October 2020 GCM for Windows got deprecated and superseded by GCM Core. The information here still applies to the new GCM and it should even use the credentials stored by GCM for Windows.

Solution

Configure git to include the full path to the repository as additional information for each credential entry. Also documented on GCM for Windows.
I personally prefer to include the HTTP(S) [repository] path to be able to use a separate account for each and every repository.

For all possible hosts:

git config --global credential.useHttpPath true

For github.com only:

git config --global credential.github.com.useHttpPath true

Have a look at the GCM and git docs and maybe you want to specify something different.

Vlad L.'s user avatar

Vlad L.

1541 silver badge9 bronze badges

answered Jun 20, 2020 at 22:59

Maddes's user avatar

MaddesMaddes

1811 silver badge4 bronze badges

In our case, clearing the password in the user’s .git-credentials file worked.

c:users[username].git-credentials

Bazer Con's user avatar

answered May 4, 2016 at 18:28

Fares's user avatar

FaresFares

6595 silver badges11 bronze badges

2

For Windows 10, go to below path,

Control PanelUser AccountsCredential Manager

There will be 2 tabs at this location,

  1. Web credentials and 2. Windows credentials.

Click on Windows credentials tab and here you can see your stored github credentials,
under «Generic credentials» heading.

You can remove those from here and try and re-clone — it will ask for username/password now as we have just removed the stored credential from the Windows 10 systems

answered Mar 3, 2020 at 8:28

AADProgramming's user avatar

AADProgrammingAADProgramming

5,88211 gold badges37 silver badges58 bronze badges

Команда git reset — это сложный универсальный инструмент для отмены изменений. Она имеет три основные формы вызова, соответствующие аргументам командной строки --soft, --mixed, --hard. Каждый из этих трех аргументов соответствует трем внутренним механизмам управления состоянием Git: дереву коммитов (HEAD), разделу проиндексированных файлов и рабочему каталогу.

Git reset и три дерева Git

Чтобы понять, как используется команда git reset, необходимо разобраться с внутренними системами управления состоянием в Git. Иногда эти механизмы называют «тремя деревьями» Git. «Деревья» — возможно, не самое точное название, поскольку это не традиционные древовидные структуры данных в строгом смысле слова. Тем не менее это структуры данных на основе узлов и указателей, которые Git использует для отслеживания истории внесения правок. Лучший способ продемонстрировать эти механизмы — создать набор изменений в репозитории и проследить его по трем деревьям.

Начнем работу с создания нового репозитория с помощью приведенных ниже команд.

$ mkdir git_reset_test
$ cd git_reset_test/
$ git init .
Initialized empty Git repository in /git_reset_test/.git/
$ touch reset_lifecycle_file
$ git add reset_lifecycle_file
$ git commit -m"initial commit"
[main (root-commit) d386d86] initial commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 reset_lifecycle_file

В приведенном выше примере кода создается новый репозиторий Git с одним пустым файлом reset_lifecycle_file. На этом этапе репозиторий имеет один коммит (d386d86), в котором отражено добавление файла reset_lifecycle_file.

Рабочий каталог

Первое дерево, которое мы рассмотрим, — рабочий каталог. Это дерево синхронизировано с локальной файловой системой и отображает непосредственные изменения, внесенные в содержимое файлов и каталогов.


$ echo 'hello git reset' > reset_lifecycle_file
$ git status 
On branch main
Changes not staged for commit: 
(use "git add ..." to update what will be committed) 
(use "git checkout -- ..." to discard changes in working directory) 
modified: reset_lifecycle_file

В нашем демонстрационном репозитории изменим и добавим содержимое в файл reset_lifecycle_file. Вызов команды git status показывает, что Git знает об изменениях в этом файле. В данный момент эти изменения являются частью первого дерева — рабочего каталога. Для отображения изменений в рабочем каталоге можно использовать команду git status. Измененные файлы будут отображаться красным цветом с префиксом «modified»

Раздел проиндексированных файлов

Следующее дерево — раздел проиндексированных файлов. Это дерево отслеживает изменения рабочего каталога, которые были добавлены с помощью команды git add, для сохранения в следующем коммите. Это дерево представляет собой сложный внутренний механизм кэширования. В целом Git пытается скрыть от пользователя подробности реализации раздела проиндексированных файлов.

Для полного просмотра состояния раздела проиндексированных файлов необходимо использовать менее известную команду Git — git ls-files. Команда git ls-files по сути является утилитой отладки для проверки состояния дерева раздела проиндексированных файлов.

git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0   reset_lifecycle_file

Здесь мы выполнили команду git ls-files с параметром -s (или --stage). Без параметра -s вывод команды git ls-files представляет собой просто список путей и имен файлов, которые в данный момент являются частью индекса. Параметр -s отображает дополнительные метаданные файлов, находящихся в разделе проиндексированных файлов. Эти метаданные — биты режима проиндексированного контента, имя объекта и номер в индексе. Здесь нас интересует второе значение, имя объекта (d7d77c1b04b5edd5acfc85de0b592449e5303770). Это стандартный хеш SHA-1 объекта Git, представляющий собой хеш содержимого файлов. В истории коммитов хранятся собственные SHA объектов для идентификации указателей на коммиты и ссылки, а в разделе проиндексированных файлов есть свои SHA объектов для отслеживания версий файлов в индексе.

Далее мы добавим измененный файл reset_lifecycle_file в раздел проиндексированных файлов.


$ git add reset_lifecycle_file 

$ git status 

On branch main Changes to be committed: 

(use "git reset HEAD ..." to unstage) 

modified: reset_lifecycle_file

Здесь мы вызываем команду git add reset_lifecycle_file, которая добавляет файл в раздел проиндексированных файлов. Теперь при вызове команды git status файл reset_lifecycle_file отображается зеленым цветом, как изменение, подлежащее коммиту («Changes to be committed»). Важно отметить, что команда git status не отображает истинное представление раздела проиндексированных файлов. Вывод git status отображает различия между историей коммитов и разделом проиндексированных файлов. Давайте рассмотрим содержимое раздела проиндексированных файлов на данный момент.

 $ git ls-files -s 100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file

Видно, что SHA объекта для файла reset_lifecycle_file изменился с e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 на d7d77c1b04b5edd5acfc85de0b592449e5303770.

История коммитов

Последнее дерево — история коммитов. Команда git commit добавляет изменения в постоянный снимок, который находится в истории коммитов. Этот снимок также включает состояние раздела проиндексированных файлов на момент выполнения коммита.

$ git commit -am"update content of reset_lifecycle_file"
[main dc67808] update content of reset_lifecycle_file
1 file changed, 1 insertion(+)
$ git status
On branch main
nothing to commit, working tree clean

Здесь мы создали новый коммит с комментарием update content of resetlifecyclefile. В историю коммитов был добавлен набор изменений. Вызов команды git status в этой точке показывает, что ни в одном дереве нет ожидающих изменений. Выполнение команды git log отобразит дерево коммитов. Теперь, когда мы проследили за этим набором изменений во всех трех деревьях, можно приступать к использованию команды git reset.

Порядок действий

На первый взгляд, поведение команды git reset схоже с поведением команды git checkout. Но команда git checkout работает исключительно с указателем HEAD, а git reset перемещает указатель HEAD и указатель текущей ветки. Чтобы лучше продемонстрировать это поведение, рассмотрим следующий пример.

4 узла, из которых «главный узел» является последним

В этом примере показана последовательность коммитов в ветке main. Сейчас и указатель HEAD, и указатель на главную ветку main указывают на коммит d. Теперь давайте выполним обе команды, git checkout b и git reset b, и сравним результат.

git checkout b

4 узла: указатель Main указывает на последний узел, а указатель HEAD — на 2-й узел

После выполнения команды git checkout указатель main по-прежнему ссылается на коммит d. Указатель HEAD переместился и теперь ссылается на коммит b. В данный момент репозиторий находится в состоянии открепленного указателя HEAD.

git reset b

2 набора из 2 узлов: указатели HEAD и Main указывают на 2-й узел 1-го набора

Команда git reset перемещает и указатель HEAD, и указатель ветки на заданный коммит.

Помимо обновления указателей на коммит команда git reset изменяет состояние трех деревьев. Указатели меняются всегда, то есть происходит обновление третьего дерева, дерева коммитов. Аргументы командной строки --soft, --mixed и --hard определяют, каким образом необходимо изменить деревья раздела проиндексированных файлов и рабочего каталога.

Основные параметры

По умолчанию при вызове команды git reset используются неявные аргументы --mixed и HEAD. Таким образом, выполнение команды git reset эквивалентно выполнению команды git reset --mixed HEAD. В этом случае HEAD является указателем на конкретный коммит. Вместо HEAD можно использовать любой хеш SHA-1 коммита Git.

схема области действия команд git reset

—hard

Это самый прямой, ОПАСНЫЙ и часто используемый вариант. При использовании аргумента --hard указатели в истории коммитов обновляются на указанный коммит. Затем происходит сброс раздела проиндексированных файлов и рабочего каталога до указанного коммита. Все предыдущие ожидающие изменения в разделе проиндексированных файлов и рабочем каталоге сбрасываются в соответствии с состоянием дерева коммитов. Это значит, что любая работа, находившаяся в состоянии ожидания в разделе проиндексированных файлов и рабочем каталоге, будет потеряна.

Чтобы продемонстрировать это, продолжим работать в репозитории, созданном ранее для примера с тремя деревьями. Сначала внесем в репозиторий некоторые изменения. Выполните в нем следующие команды:

$ echo 'new file content' > new_file
$ git add new_file
$ echo 'changed content' >> reset_lifecycle_file
 

С помощью этих команд мы создали новый файл с именем new_file и добавили его в репозиторий. Кроме того, было изменено содержимое файла reset_lifecycle_file. А теперь давайте выполним команду git status и посмотрим, как эти изменения повлияли на состояние репозитория.

$ git status
On branch main
Changes to be committed:
   (use "git reset HEAD ..." to unstage)

new file: new_file

Changes not staged for commit:
   (use "git add ..." to update what will be committed)
   (use "git checkout -- ..." to discard changes in working directory)

modified: reset_lifecycle_file

Мы видим, что в репозитории появились ожидающие изменения: в дереве раздела проиндексированных файлов — добавление файла new_file, а в рабочем каталоге — изменение файла reset_lifecycle_file.

Прежде чем двигаться дальше, изучим состояние раздела проиндексированных файлов:

$ git ls-files -s
100644 8e66654a5477b1bf4765946147c49509a431f963 0 new_file
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file

Мы видим, что в индекс добавлен файл new_file. Мы внесли изменения в файл reset_lifecycle_file, но его SHA в разделе проиндексированных файлов (d7d77c1b04b5edd5acfc85de0b592449e5303770) остался прежним. Это ожидаемый результат, поскольку мы не использовали команду git add для добавления этих изменений в раздел проиндексированных файлов. Эти изменения присутствуют и в рабочем каталоге.

Теперь давайте выполним команду git reset --hard и изучим новое состояние репозитория.

$ git reset --hard
HEAD is now at dc67808 update content of reset_lifecycle_file
$ git status
On branch main
nothing to commit, working tree clean
$ git ls-files -s
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file

Здесь мы выполнили «жесткий сброс» с помощью параметра --hard. Вывод Git сообщает, что указатель HEAD показывает на последний коммит, dc67808. Далее проверяем состояние репозитория с помощью команды git status. Git сообщает, что ожидающих изменений нет. Проверяем также состояние раздела проиндексированных файлов и видим, что он был сброшен к состоянию, предшествовавшему добавлению файла new_file. Изменения, которые мы внесли в файл reset_lifecycle_file, а также добавление файла new_file уничтожены. Важно понимать: восстановить эти потерянные данные невозможно.

—mixed

Это режим работы по умолчанию. Указатели ссылок обновляются. Раздел проиндексированных файлов сбрасывается до состояния указанного коммита. Любые изменения, которые были отменены в разделе проиндексированных файлов, перемещаются в рабочий каталог. Давайте продолжим.

$ echo 'new file content' > new_file
$ git add new_file
$ echo 'append content' >> reset_lifecycle_file
$ git add reset_lifecycle_file
$ git status
On branch main
Changes to be committed:
    (use "git reset HEAD ..." to unstage)

new file: new_file
modified: reset_lifecycle_file

$ git ls-files -s
100644 8e66654a5477b1bf4765946147c49509a431f963 0 new_file
100644 7ab362db063f9e9426901092c00a3394b4bec53d 0 reset_lifecycle_file

В приведенном выше примере мы внесли в репозиторий некоторые изменения: добавили файл new_file и изменили содержимое файла reset_lifecycle_file. Затем эти изменения были добавлены в раздел проиндексированных файлов с помощью команды git add. Теперь выполним команду reset по отношению к репозиторию в данном состоянии.

$ git reset --mixed
$ git status
On branch main
Changes not staged for commit:
    (use "git add ..." to update what will be committed)
    (use "git checkout -- ..." to discard changes in working directory)

modified: reset_lifecycle_file

Untracked files:
    (use "git add ..." to include in what will be committed)

new_file

no changes added to commit (use "git add" and/or "git commit -a")
$ git ls-files -s
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file

В данном случае мы выполнили «смешанный сброс». Напоминаем, что --mixed является режимом по умолчанию и выполнение команды git reset приведет к тому же результату. Изучение вывода команд git status и git ls-files показывает, что раздел проиндексированных файлов был сброшен до состояния, когда в этом разделе находился только файл reset_lifecycle_file. SHA объекта для файла reset_lifecycle_file был сброшен к предыдущей версии.

Обратите внимание: команда git status показывает нам, что появились изменения в файле reset_lifecycle_file и существует неотслеживаемый файл new_file. Это явный результат действия параметра --mixed. Раздел проиндексированных файлов был сброшен, а ожидающие изменения перемещены в рабочий каталог. Для сравнения, при использовании параметра --hard были сброшены и раздел проиндексированных файлов, и рабочий каталог, что привело к потере этих обновлений.

—soft

При передаче аргумента --soft выполняется обновление указателей, и на этом операция сброса останавливается. Раздел проиндексированных файлов и рабочий каталог остаются неизменными. Четко продемонстрировать такое поведение довольно сложно. Давайте продолжим работать с нашим демонстрационным репозиторием и подготовим его к мягкому сбросу.


$ git add reset_lifecycle_file 

$ git ls-files -s 

100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file 

$ git status 

On branch main

Changes to be committed: 

(use "git reset HEAD ..." to unstage) 

modified: reset_lifecycle_file 

Untracked files: 

(use "git add ..." to include in what will be committed) 

new_file

Здесь мы снова воспользовались командой git add, чтобы добавить измененный файл reset_lifecycle_file в раздел проиндексированных файлов. Чтобы убедиться, что индекс обновлен, посмотрим на вывод команды git ls-files. Теперь в выводе команды git status строка «Changes to be committed» (Изменения, подлежащие коммиту) окрашена в зеленый цвет. Файл new_file из предыдущего примера находится в рабочем каталоге как неотслеживаемый. Удалим его с помощью простой команды rm new_file, поскольку в следующих примерах он нам больше не понадобится.

Теперь давайте мягко сбросим текущее состояние репозитория.

$ git reset --soft
$ git status
On branch main
Changes to be committed:
    (use "git reset HEAD ..." to unstage)

modified: reset_lifecycle_file
$ git ls-files -s
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file

Мы выполнили «мягкий сброс». Изучение состояния репозитория с помощью команд git status и git ls-files показывает, что ничего не изменилось. Это ожидаемый результат. Мягкий сброс влияет только на историю коммитов. По умолчанию при вызове команды git reset в качестве целевого коммита используется HEAD. Поскольку HEAD уже указывал на нашу историю коммитов и мы выполнили неявный сброс до HEAD, в реальности ничего не произошло.

Для получения более полного представления о параметре --soft и правильного его использования нам потребуется целевой коммит, отличный от HEAD. У нас уже есть файл reset_lifecycle_file, находящийся в разделе проиндексированных файлов. Давайте создадим новый коммит.

$ git commit -m"prepend content to reset_lifecycle_file"

На данный момент в нашем репозитории находится три коммита. Мы выполним возврат к первому коммиту. Для этого нам потребуется идентификатор первого коммита. Его можно найти, просмотрев вывод команды git log.

$ git log
commit 62e793f6941c7e0d4ad9a1345a175fe8f45cb9df
Author: bitbucket 
Date: Fri Dec 1 15:03:07 2017 -0800
prepend content to reset_lifecycle_file

commit dc67808a6da9f0dec51ed16d3d8823f28e1a72a
Author: bitbucket 
Date: Fri Dec 1 10:21:57 2017 -0800

update content of reset_lifecycle_file

commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4

Author: bitbucket 
Date: Thu Nov 30 16:50:39 2017 -0800

initial commit

Помните, что идентификаторы в истории коммитов уникальны для каждой системы. Это означает, что идентификатор коммита в этом примере будет отличаться от идентификатора, который вы увидите на своей машине. Идентификатор интересующего нас коммита для этого примера — 780411da3b47117270c0e3a8d5dcfd11d28d04a4. Это идентификатор, соответствующий первичному коммиту «initial commit». Теперь укажем его в качестве целевого для нашей операции мягкого сброса.

Прежде чем вернуться назад во времени, проверим текущее состояние репозитория.

$ git status && git ls-files -s
On branch main
nothing to commit, working tree clean
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file

Здесь мы выполнили составную команду, состоящую из команд git status и git ls-files -s. Она показывает, что в репозитории есть ожидающие изменения, а файл reset_lifecycle_file в разделе проиндексированных файлов находится в версии 67cc52710639e5da6b515416fd779d0741e3762e. Держа в уме эти сведения, выполним мягкий сброс до нашего первого коммита.

$git reset --soft 780411da3b47117270c0e3a8d5dcfd11d28d04a4
$ git status && git ls-files -s
On branch main
Changes to be committed:
    (use "git reset HEAD ..." to unstage)

modified: reset_lifecycle_file
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file

Приведенный выше код выполняет «мягкий сброс», а также вызывает составную команду, которая включает команды git status и git ls-files и выводит информацию о состоянии репозитория. Мы можем изучить эту информацию и сделать несколько интересных наблюдений. Во-первых, git status указывает на наличие изменений в файле reset_lifecycle_file и сообщает, что эти изменения проиндексированы для выполнения коммита. Во-вторых, входные данные git ls-files указывают, что раздел проиндексированных файлов не изменился и SHA остался прежним: 67cc52710639e5da6b515416fd779d0741e3762e.

Чтобы выяснить, что произошло при этом сбросе, выполним команду git log:

 $ git log commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4 Author: bitbucket  Date: Thu Nov 30 16:50:39 2017 -0800 initial commit

Теперь вывод команды log показывает, что в истории коммитов находится единственный коммит. Это четко иллюстрирует, что делает параметр --soft. Как и при всех вызовах команды git reset, сначала происходит сброс дерева коммитов. Наши предыдущие примеры с параметрами --hard и --mixed воздействовали на указатель HEAD и не возвращали дерево коммитов в предыдущее состояние. Во время мягкого сброса происходит только сброс дерева коммитов.

Почему же команда git status сообщает о наличии измененных файлов? Это может сбивать с толку. Параметр --soft не затрагивает раздел проиндексированных файлов, поэтому изменения в этом разделе сохраняются в истории коммитов. В этом можно убедиться, просмотрев вывод команды git ls-files -s, показывающий, что SHA для файла reset_lifecycle_file остается неизменным. Напомним, что команда git status показывает не состояние «трех деревьев», а различия между ними. В данном случае она показывает, что изменения в разделе проиндексированных файлов опережают изменения в истории коммитов, как если бы мы уже добавили их в индекс.

Разница между командами git reset и git revert

Если git revert — «безопасный» способ отмены изменений, то git reset — опасный. При выполнении команды git reset есть реальный риск потерять наработки. Команда git reset никогда не удаляет коммиты, однако может оставить их без родителя, т. е. указатель потеряет прямой путь для доступа к ним. Такие коммиты без родителя обычно можно найти и восстановить с помощью команды git reflog. После запуска внутреннего «сборщика мусора» Git навсегда удалит все коммиты без родителя. По умолчанию Git запускает «сборщик мусора» каждые 30 дней. История коммитов — одно из «трех деревьев Git»; два других — раздел проиндексированных файлов и рабочий каталог — не отличаются таким же постоянством, как коммиты. При использовании этого инструмента необходимо соблюдать осторожность, поскольку это одна из немногих команд Git, которые могут привести к потере ваших наработок.

Команда revert предназначена для безопасной отмены публичных коммитов, а git reset — для отмены локальных изменений в разделе проиндексированных файлов и рабочем каталоге. Поскольку они предназначены для разных целей, их реализация также различается: команда reset полностью удаляет набор изменений, тогда как команда revert оставляет исходный набор изменений и использует новый коммит для применения отмены.

Не используйте reset в публичной истории

Никогда не используйте команду git reset <коммит>, если после этого <коммита> в публичный репозиторий были отправлены какие-либо снимки состояния. После того как коммит опубликован, нужно учитывать, что на него могут полагаться другие разработчики.

При удалении коммита, после которого другие участники команды начали работу, могут возникнуть серьезные проблемы. Когда коллеги попытаются синхронизироваться с вашим репозиторием, часть истории проекта будет просто отсутствовать. На следующей схеме показано, что происходит при использовании команды reset для публичного коммита. Ветка origin/main является версией вашей локальной главной ветки main в центральном репозитории.

4 набора узлов: указатель Origin/Main указывает на последний

Как только вы добавите новые коммиты после выполнения команды reset, Git будет считать, что локальная история отклонилась от ветки origin/main, а коммит слияния, необходимый для синхронизации репозиториев, скорее всего, собьет с толку вашу команду и помешает ей.

Команду git reset <коммит> можно использовать только для локальных экспериментов, в которых что-то пошло не так, а не для публичных изменений. Если необходимо исправить публичный коммит, воспользуйтесь специальной командой git revert.

Примеры

 git reset <файл>

Удаляет указанный файл из раздела проиндексированных файлов, но оставляет рабочий каталог без изменений. Эта команда удаляет из индекса подготовленный файл, не перезаписывая все изменения.

 git reset

Сбрасывает раздел проиндексированных файлов до состояния последнего коммита, но оставляет рабочий каталог без изменений. Эта команда удаляет из индекса все подготовленные файлы, не перезаписывая все изменения, что позволяет повторно собрать снимок состояния с нуля.

 git reset --hard

Сбрасывает раздел проиндексированных файлов и рабочий каталог до состояния последнего коммита. Флаг --hard говорит Git, что нужно не только отменить изменения в разделе проиндексированных файлов, но и перезаписать все изменения в рабочем каталоге. Другими словами, эта команда уничтожит все неотправленные изменения, поэтому перед ее использованием убедитесь, что вы действительно хотите удалить ваши локальные наработки.

 git reset <коммит> 

Перемещает указатель текущей ветки на более ранний <коммит>, сбрасывает раздел проиндексированных файлов до состояния этого коммита, но не затрагивает рабочий каталог. Все изменения, внесенные после <коммита>, останутся в рабочем каталоге, чтобы вы могли повторно сделать коммит истории проекта с использованием более мелких и упорядоченных снимков состояния.

 git reset --hard <коммит> 

Перемещает указатель текущей ветки на более ранний <коммит> и сбрасывает как раздел проиндексированных файлов, так и рабочий каталог до состояния этого коммита. Эта команда уничтожит не только неотправленные изменения, но и все коммиты, которые были добавлены после указанного коммита.

Удаление файла из раздела проиндексированных файлов

Команда git reset часто встречается при подготовке проиндексированного снимка состояния. В следующем примере предполагается, что у вас есть два файла с именами hello.py и main.py, которые вы уже добавили в репозиторий.

# Edit both hello.py and main.py

# Stage everything in the current directory
git add .

# Realize that the changes in hello.py and main.py
# should be committed in different snapshots

# Unstage main.py
git reset main.py

# Commit only hello.py
git commit -m "Make some changes to hello.py"

# Commit main.py in a separate snapshot
git add main.py
git commit -m "Edit main.py"

Как видите, команда git reset помогает соблюдать согласованность коммитов, позволяя не вносить изменения, которые не связаны со следующим коммитом.

Удаление локальных коммитов

В следующем примере показан более продвинутый сценарий использования. Он демонстрирует, что происходит, когда вы некоторое время работали над новой экспериментальной функцией, но после добавления нескольких коммитов состояния решили полностью все удалить.

# Create a new file called `foo.py` and add some code to it

# Commit it to the project history
git add foo.py
git commit -m "Start developing a crazy feature"

# Edit `foo.py` again and change some other tracked files, too

# Commit another snapshot
git commit -a -m "Continue my crazy feature"

# Decide to scrap the feature and remove the associated commits
git reset --hard HEAD~2

Команда git reset HEAD~2 перемещает указатель текущей ветки на два коммита назад, по сути удаляя из истории проекта оба снимка состояния, которые мы только что создали. Помните, что этот вид команды reset можно использовать только для неопубликованных коммитов. Никогда не выполняйте эту операцию, если вы уже отправили свои коммиты в общий репозиторий.

Резюме

Итак, git reset — это мощная команда, используемая для отмены локальных изменений в репозитории Git. Git reset работает с «тремя деревьями Git»: историей коммитов (HEAD), разделом проиндексированных файлов и рабочим каталогом. Существует три параметра командной строки, соответствующие этим трем деревьям. Эти параметры — --soft, --mixed и --hard — можно передавать команде git reset.

В этой статье мы воспользовались несколькими другими командами Git для демонстрации работы команды reset. Подробнее об этих командах см. на соответствующих страницах: git status, git log, git add, git checkout, git reflog и git revert.

Шпаргалка по консольным командам Git
Шпаргалка по консольным командам Git

Общее

Git — система контроля версий (файлов). Что-то вроде возможности сохраняться в компьютерных играх (в Git эквивалент игрового сохранения — коммит). Важно: добавление файлов к «сохранению» двухступенчатое: сначала добавляем файл в индекс (git add), потом «сохраняем» (git commit).

Любой файл в директории существующего репозитория может находиться или не находиться под версионным контролем (отслеживаемые и неотслеживаемые).

Отслеживаемые файлы могут быть в 3-х состояниях: неизменённые, изменённые, проиндексированные (готовые к коммиту).

Ключ к пониманию

Ключ к пониманию концепции git — знание о «трех деревьях»:

  • Рабочая директория — файловая система проекта (те файлы, с которыми вы работаете).
  • Индекс — список отслеживаемых git-ом файлов и директорий, промежуточное хранилище изменений (редактирование, удаление отслеживаемых файлов).
  • Директория .git/ — все данные контроля версий этого проекта (вся история разработки: коммиты, ветки, теги и пр.).

Коммит — «сохранение» (хранит набор изменений, сделанный в рабочей директории с момента предыдущего коммита). Коммит неизменен, его нельзя отредактировать.

У всех коммитов (кроме самого первого) есть один или более родительских коммитов, поскольку коммиты хранят изменения от предыдущих состояний.

Простейший цикл работ

  • Редактирование, добавление, удаление файлов (собственно, работа).
  • Индексация/добавление файлов в индекс (указание для git какие изменения нужно будет закоммитить).
  • Коммит (фиксация изменений).
  • Возврат к шагу 1 или отход ко сну.

Указатели

  • HEAD — указатель на текущий коммит или на текущую ветку (то есть, в любом случае, на коммит). Указывает на родителя коммита, который будет создан следующим.
  • ORIG_HEAD — указатель на коммит, с которого вы только что переместили HEAD (командой git reset ..., например).
  • Ветка (masterdevelop etc.) — указатель на коммит. При добавлении коммита, указатель ветки перемещается с родительского коммита на новый.
  • Теги — простые указатели на коммиты. Не перемещаются.

Настройки

Перед началом работы нужно выполнить некоторые настройки:

git config --global user.name "Your Name" # указать имя, которым будут подписаны коммиты
git config --global user.email "[email protected]"  # указать электропочту, которая будет в описании коммитера

Если вы в Windows:

git config --global core.autocrlf true # включить преобразование окончаний строк из CRLF в LF

Указание неотслеживаемых файлов

Файлы и директории, которые не нужно включать в репозиторий, указываются в файле .gitignore. Обычно это устанавливаемые зависимости (node_modules/bower_components/), готовая сборка build/ или dist/ и подобные, создаваемые при установке или запуске. Каждый файл или директория указываются с новой строки, возможно использование шаблонов.

Консоль

Длинный вывод в консоли: Vim

Вызов некоторых консольных команд приводит к необходимости очень длинного вывода в консоль (пример: вывод истории всех изменений в файле командой git log -p fileName.txt). При этом прямо в консоли запускается редактор Vim. Он работает в нескольких режимах, из которых Вас заинтересуют режим вставки (редактирование текста) и нормальный (командный) режим. Чтобы попасть из Vim обратно в консоль, нужно в командном режиме ввести :q. Переход в командный режим из любого другого: Esc.

Если нужно что-то написать, нажмите i — это переход в режим вставки текста. Если нужно сохранить изменения, перейдите в командный режим и наберите :w.

Vim (некоторые команды)

# Нажатия кнопок
ESC     — переход в командный режим
i       — переход в режим редактирования текста
ZQ (зажат Shift, поочередное нажатие) — выход без сохранения
ZZ (зажат Shift, поочередное нажатие) — сохранить и выйти
```bash
# Нажатия кнопок
ESC     — переход в командный режим
i       — переход в режим редактирования текста
ZQ (зажат Shift, поочередное нажатие) — выход без сохранения
ZZ (зажат Shift, поочередное нажатие) — сохранить и выйти

# Ввод в командном режиме
:q!             — выйти без сохранения
:wq             — сохранить файл и выйти
:w filename.txt — сохранить файл как filename.txt

Консольные команды

Создать новый репозиторий

git init             # создать новый проект в текущей директории
git init folder-name # создать новый проект в указанной директории

Клонирование репозитория

# клонировать удаленный репозиторий в одноименную директорию
git clone https://github.com/cyberspacedk/Git-commands.git    

# клонировать удаленный репозиторий в директорию «FolderName»
git clone https://github.com/cyberspacedk/Git-commands.git FolderName 

# клонировать репозиторий в текущую директорию
git clone https://github.com:nicothin/web-design.git .

Просмотр изменений

git status              # показать состояние репозитория (отслеживаемые, изменённые, новые файлы и пр.)
git diff                # сравнить рабочую директорию и индекс (неотслеживаемые файлы ИГНОРИРУЮТСЯ)
git diff --color-words  # сравнить рабочую директорию и индекс, показать отличия в словах (неотслеживаемые файлы ИГНОРИРУЮТСЯ)
git diff index.html     # сравнить файл из рабочей директории и индекс
git diff HEAD           # сравнить рабочую директорию и коммит, на который указывает HEAD (неотслеживаемые файлы ИГНОРИРУЮТСЯ)
git diff --staged       # сравнить индекс и коммит с HEAD
git diff master feature # посмотреть что сделано в ветке feature по сравнению с веткой master
git diff --name-only master feature # посмотреть что сделано в ветке feature по сравнению с веткой master, показать только имена файлов
git diff master...feature # посмотреть что сделано в ветке feature с момента (коммита) расхождения с master

Добавление изменений в индекс

git add .        # добавить в индекс все новые, изменённые, удалённые файлы из текущей директории и её поддиректорий
git add text.txt # добавить в индекс указанный файл (был изменён, был удалён или это новый файл)
git add -i       # запустить интерактивную оболочку для добавления в индекс только выбранных файлов
git add -p       # показать новые/изменённые файлы по очереди с указанием их изменений и вопросом об отслеживании/индексировании

Удаление изменений из индекса

git reset            # убрать из индекса все добавленные в него изменения (в рабочей директории все изменения сохранятся), антипод git add
git reset readme.txt # убрать из индекса изменения указанного файла (в рабочей директории изменения сохранятся)

Отмена изменений

git checkout text.txt      # ОПАСНО: отменить изменения в файле, вернуть состояние файла, имеющееся в индексе
git reset --hard           # ОПАСНО: отменить изменения; вернуть то, что в коммите, на который указывает HEAD (незакомиченные изменения удалены из индекса и из рабочей директории, неотслеживаемые файлы останутся на месте)
git clean -df              # удалить неотслеживаемые файлы и директории

Коммиты

git commit -m "Name of commit"    # зафиксировать в коммите проиндексированные изменения (закоммитить), добавить сообщение
git commit -a -m "Name of commit" # проиндексировать отслеживаемые файлы (ТОЛЬКО отслеживаемые, но НЕ новые файлы) и закоммитить, добавить сообщение

Отмена коммитов и перемещение по истории

Все коммиты, которые уже были отправлены в удалённый репозиторий, должны отменяться новыми коммитами (git revert), дабы избежать проблем с историей разработки у других участников проекта.

git revert HEAD --no-edit    # создать новый коммит, отменяющий изменения последнего коммита без запуска редактора сообщения
git revert b9533bb --no-edit # то же, но отменяются изменения, внесённые коммитом с указанным хешем (b9533bb)

Все команды, приведённые ниже можно выполнять ТОЛЬКО если коммиты еще не были отправлены в удалённый репозиторий.

# ВНИМАНИЕ! Опасные команды, можно потерять незакоммиченные изменения
git commit --amend -m "Название"  # «перекоммитить» изменения последнего коммита, заменить его новым коммитом с другим сообщением (сдвинуть текущую ветку на один коммит назад, сохранив рабочую директорию и индекс «как есть», создать новый коммит с данными из «отменяемого» коммита, но новым сообщением)
git reset --hard @~      # передвинуть HEAD (и ветку) на предыдущий коммит, рабочую директорию и индекс сделать такими, какими они были в момент предыдущего коммита
git reset --hard 75e2d51 # передвинуть HEAD (и ветку) на коммит с указанным хешем, рабочую директорию и индекс сделать такими, какими они были в момент указанного коммита
git reset --soft @~      # передвинуть HEAD (и ветку) на предыдущий коммит, но в рабочей директории и индексе оставить все изменения
git reset --soft @~2     # то же, но передвинуть HEAD (и ветку) на 2 коммита назад
git reset @~             # передвинуть HEAD (и ветку) на предыдущий коммит, рабочую директорию оставить как есть, индекс сделать таким, каким он был в момент предыдущего коммита (удобнее, чем git reset --soft @~, если индекс нужно задать заново)
# Почти как git reset --hard, но безопаснее: не получится потерять изменения в рабочей директории
git reset --keep @~      # передвинуть HEAD (и ветку) на предыдущий коммит, сбросить индекс, но в рабочей директории оставить изменения, если возможно (если файл с изменениями между коммитами менялся, будет выдана ошибка и переключение не произойдёт)

Временно переключиться на другой коммит

git checkout b9533bb # переключиться на коммит с указанным хешем (переместить HEAD на указанный коммит, рабочую директорию вернуть к состоянию, на момент этого коммита)
git checkout master  # переключиться на коммит, на который указывает master (переместить HEAD на коммит, на который указывает master, рабочую директорию вернуть к состоянию на момент этого коммита)

Переключиться на другой коммит и продолжить работу с него

Потребуется создание новой ветки, начинающейся с указанного коммита.

git checkout -b new-branch 5589877   # создать ветку new-branch, начинающуюся с коммита c хешем 5589877 (переместить HEAD на указанный коммит, рабочую директорию вернуть к состоянию, на момент этого коммита, создать указатель на этот коммит (ветку) с указанным именем)

Восстановление изменений

git checkout 5589877 index.html  # восстановить в рабочей директории указанный файл на момент указанного коммита (и добавить это изменение в индекс) (git reset index.html для удаления из индекса, но сохранения изменений в файле)

Копирование коммита (перенос коммитов)

git cherry-pick 5589877          # скопировать на активную ветку изменения из указанного коммита, закоммитить эти изменения
git cherry-pick master~2..master # скопировать на активную ветку изменения из master (2 последних коммита)
git cherry-pick -n 5589877       # скопировать на активную ветку изменения из указанного коммита, но НЕ КОММИТИТЬ (подразумевается, что мы сами потом закоммитим)
git cherry-pick master..feature  # скопировать на активную ветку изменения из всех коммитов ветки feature с момента её расхождения с master (похоже на слияние веток, но это копирование изменений, а не слияние), закоммитить эти изменения; это может вызвать конфликт
git cherry-pick --abort    # прервать конфликтный перенос коммитов
git cherry-pick --continue # продолжить конфликтный перенос коммитов (сработает только после решения конфликта)

Удаление файла

git rm text.txt    # удалить отслеживаемый неизменённый файл и проиндексировать это изменение
git rm -f text.txt # удалить отслеживаемый изменённый файл и проиндексировать это изменение
git rm -r log/     # удалить всё содержимое отслеживаемой директории log/ и проиндексировать это изменение
git rm ind*        # удалить все отслеживаемые файлы с именем, начинающимся на «ind» в текущей директории и проиндексировать это изменение
git rm --cached readme.txt # удалить из отслеживаемых индексированный файл (ФАЙЛ ОСТАНЕТСЯ НА МЕСТЕ) (часто используется для нечаянно добавленных в отслеживаемые файлов)

Перемещение/переименование файлов

Для git не существует переименования. Переименование воспринимается как удаление старого файла и создание нового. Факт переименования может быть определен только после индексации изменения.

git mv text.txt test_new.txt # переименовать файл «text.txt» в «test_new.txt» и проиндексировать это изменение
git mv readme_new.md folder/ # переместить файл readme_new.md в директорию folder/ (должна существовать) и проиндексировать это изменение

История коммитов

Выход из длинного лога вывода: q.

git log master             # показать коммиты в указанной ветке
git log -2                 # показать последние 2 коммита в активной ветке
git log -2 --stat          # показать последние 2 коммита и статистику внесенных ими изменений
git log -p -22             # показать последние 22 коммита и внесенную ими разницу на уровне строк
git log --graph -10        # показать последние 10 коммитов с ASCII-представлением ветвления
git log --since=2.weeks    # показать коммиты за последние 2 недели
git log --after '2018-06-30' # показать коммиты, сделанные после указанной даты
git log index.html         # показать историю изменений файла index.html (только коммиты)
git log -5 index.html      # показать историю изменений файла index.html, последние 5 коммитов (только коммиты)
git log -p index.html      # показать историю изменений файла index.html (коммиты и изменения)
git log -G'myFunction' -p  # показать все коммиты, в которых менялись строки с myFunction (в кавычках регулярное выражение)
git log -L '/<head>/','/</head>/':index.html # показать изменения от указанного до указанного регулярных выражений в указанном файле
git log --grep fix         # показать коммиты, в описании которых есть буквосочетание fix (регистрозависимо, только коммиты текущей ветки)
git log --grep fix -i      # показать коммиты, в описании которых есть буквосочетание fix (регистроНЕзависимо, только коммиты текущей ветки)
git log --grep 'fix(ing|me)' -P # показать коммиты, в описании которых есть совпадения для регулярного выражения (только коммиты текущей ветки)
git log --pretty=format:"%h - %an, %ar : %s" -4 # показать последние 4 коммита с форматированием выводимых данных
git log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short # мой формат вывода, висящий на алиасе оболочки
git log master..branch_99  # показать коммиты из ветки branch_99, которые не влиты в master
git log branch_99..master  # показать коммиты из ветки master, которые не влиты в branch_99
git log master...branch_99 --boundary -- graph # показать коммиты из указанных веток, начиная с их расхождения (коммит расхождения будет показан)
git show 60d6582           # показать изменения из коммита с указанным хешем
git show HEAD~             # показать данные о предыдущем коммите в активной ветке
git show @~                # аналогично предыдущему
git show HEAD~3            # показать данные о коммите, который был 3 коммита назад
git show my_branch~2       # показать данные о коммите, который был 2 коммита назад в указанной ветке
git show @~:index.html     # показать контент указанного файла на момент предыдущего (от HEAD) коммита
git show :/"подвал"        # показать самый новый коммит, в описании которого есть указанное слово (из любой ветки)

Кто написал строку

git blame README.md --date=short -L 5,8 # показать строки 5-8 указанного файла и коммиты, в которых строки были добавлены

История изменений указателей (веток, HEAD)

git reflog -20             # показать последние 20 изменений положения указателя HEAD
git reflog --format='%C(auto)%h %<|(20)%gd %C(blue)%cr%C(reset) %gs (%s)' -20 # то же, но с указанием давности действий

Ветки

git branch                 # показать список веток
git branch -v              # показать список веток и последний коммит в каждой
git branch new_branch      # создать новую ветку с указанным именем на текущем коммите
git branch new_branch 5589877 # создать новую ветку с указанным именем на указанном коммите
git branch -f master 5589877  # переместить ветку master на указанный коммит
git branch -f master master~2 # переместить ветку master на 2 коммита назад
git checkout new_branch    # перейти в указанную ветку
git checkout -b new_branch # создать новую ветку с указанным именем и перейти в неё
git checkout -B master 5589877 # переместить ветку с указанным именем на указанный коммит и перейти в неё
git merge hotfix           # влить в ветку, в которой находимся, данные из ветки hotfix
git merge hotfix -m "Горячая правка" # влить в ветку, в которой находимся, данные из ветки hotfix (указано сообщение коммита слияния)
git merge hotfix --log     # влить в ветку, в которой находимся, данные из ветки hotfix, показать редактор описания коммита, добавить в него сообщения вливаемых коммитов
git merge hotfix --no-ff   # влить в ветку, в которой находимся, данные из ветки hotfix, запретить простой сдвиг указателя, изменения из hotfix «останутся» в ней, а в активной ветке появится только коммит слияния
git branch -d hotfix       # удалить ветку hotfix (используется, если её изменения уже влиты в главную ветку)
git branch --merged        # показать ветки, уже слитые с активной
git branch --no-merged     # показать ветки, не слитые с активной
git branch -a              # показать все имеющиеся ветки (в т.ч. на удаленных репозиториях)
git branch -m old_branch_name new_branch_name # переименовать локально ветку old_branch_name в new_branch_name
git branch -m new_branch_name # переименовать локально ТЕКУЩУЮ ветку в new_branch_name
git push origin :old_branch_name new_branch_name # применить переименование в удаленном репозитории
git branch --unset-upstream # завершить процесс переименования

Теги

git tag v1.0.0               # создать тег с указанным именем на коммите, на который указывает HEAD
git tag -a -m 'В продакшен!' v1.0.1 master # создать тег с описанием на том коммите, на который смотрит ветка master
git tag -d v1.0.0            # удалить тег с указанным именем(ами)
git tag -n                   # показать все теги, и по 1 строке сообщения коммитов, на которые они указывают
git tag -n -l 'v1.*'         # показать все теги, которые начинаются с 'v1.*'

Временное сохранение изменений без коммита

git stash     # временно сохранить незакоммиченные изменения и убрать их из рабочей директории
git stash pop # вернуть сохраненные командой git stash изменения в рабочую директорию

Удалённые репозитории

Есть два распространённых способа привязать удалённый репозиторий к локальному: по HTTPS и по SSH. Если SSH у вас не настроен (или вы не знаете что это), привязывайте удалённый репозиторий по HTTPS (адрес привязываемого репозитория должен начинаться с https://).

git remote -v              # показать список удалённых репозиториев, связанных с локальным
git remote remove origin   # убрать привязку удалённого репозитория с сокр. именем origin
git remote add origin https://github.com:nicothin/test.git # добавить удалённый репозиторий (с сокр. именем origin) с указанным URL
git remote rm origin       # удалить привязку удалённого репозитория
git remote show origin     # получить данные об удалённом репозитории с сокращенным именем origin
git fetch origin           # скачать все ветки с удаленного репозитория (с сокр. именем origin), но не сливать со своими ветками
git fetch origin master    # то же, но скачивается только указанная ветка
git checkout --track origin/github_branch # создать локальную ветку github_branch (данные взять из удалённого репозитория с сокр. именем origin, ветка github_branch) и переключиться на неё
git push origin master     # отправить в удалённый репозиторий (с сокр. именем origin) данные своей ветки master
git pull origin            # влить изменения с удалённого репозитория (все ветки)
git pull origin master     # влить изменения с удалённого репозитория (только указанная ветка)

Конфликт слияния

Предполагается ситуация: есть ветка master и есть ветка feature. В обеих ветках есть коммиты, сделанные после расхождения веток. В ветку master пытаемся влить ветку feature (git merge feature), получаем конфликт, т.к. в обеих ветках есть изменения одной и той же строки в файле index.html.

При возникновении конфликта, репозиторий находится в состоянии прерванного слияния. Нужно оставить в конфликтующих местах файлов только нужный код, проиндексировать изменения и закоммитить.

git merge feature                # влить в активную ветку изменения из ветки feature
git merge-base master feature    # показать хеш последнего общего коммита для двух указанных веток
git checkout --ours index.html   # оставить в конфликтном файле (index.html) состояние ветки, В КОТОРУЮ мы вливаем (в примере — из ветки master)
git checkout --theirs index.html # оставить в конфликтном файле (index.html) состояние ветки, ИЗ КОТОРОЙ мы вливаем (в примере — из ветки feature)
git checkout --merge index.html  # показать в конфликтном файле (index.html) сравнение содержимого сливаемых веток (для ручного редактирования)
git checkout --conflict=diff3  --merge index.html # показать в конфликтном файле (index.html) сравнение содержимого сливаемых веток плюс то, что было в месте конфликта в коммите, на котором разошлись сливаемые ветки
git reset --hard  # прекратить это прерванное слияние, вернуть рабочую директорию и индекс как было в момент коммита, на который указывает HEAD, а я пойду немного поплачу
git reset --merge # прекратить это прерванное слияние, но оставить изменения, не закоммиченные до слияния (для случая, когда слияние делается не на чистом статусе)
git reset --abort # то же, что и строкой выше

«Перенос» ветки

Можно «переместить» ответвление какой-либо ветки от основной на произвольный коммит. Это нужно для того, чтобы в «переносимой» ветке появились какие-либо изменения, внесённые в основной ветке (уже после ответвления переносимой).

Нельзя «переносить» ветку, если она уже отправлена на удалённый репозиторий.

git rebase master # перенести все коммиты (создать их копии) активной ветки так, будто активная ветка ответвилась от master на нынешней вершине master (часто вызывает конфликты)
git rebase --onto master feature # перенести коммиты активной ветки на master, начиная с того места, в котором активная ветка отделилась от ветки feature
git rebase --abort # прервать конфликтный rebase, вернуть рабочую директорию и индекс к состоянию до начала rebase
git rebase --continue # продолжить конфликтный rebase (сработает только после разрешения конфликта и индексации такого разрешения)

Как отменить rebase

git reflog feature -2        # смотрим лог перемещений ветки, которой делали rebase (в этом примере — feature), видим последний коммит ПЕРЕД rebase, на него и нужно перенести указатель ветки
git reset --hard [email protected]{1} # переместить указатель ветки feature на один коммит назад, обновить рабочую директорию и индекс

Разное

git archive -o ./project.zip HEAD # создать архив с файловой структурой проекта по указанному пути (состояние репозитория, соответствующее указателю HEAD)

Примеры

Собираем коллекцию простых и сложных примеров работы.

Начало работы

Создание нового репозитория, первый коммит, привязка удалённого репозитория с gthub.com, отправка изменений в удалённый репозиторий.

# указана последовательность действий:
# создана директория проекта, мы в ней
git init                      # создаём репозиторий в этой директории
touch readme.md               # создаем файл readme.md
git add readme.md             # добавляем файл в индекс
git commit -m "Старт"         # создаем коммит
git remote add origin https://github.com:nicothin/test.git # добавляем предварительно созданный пустой удаленный репозиторий
git push -u origin master     # отправляем данные из локального репозитория в удаленный (в ветку master)

«Внесение изменений» в коммит

Только если коммит ещё не был отправлен в удалённые репозиторий.

# указана последовательность действий:
subl inc/header.html          # редактируем и сохраняем разметку «шапки»
git add inc/header.html       # индексируем измененный файл
git commit -m "Убрал телефон из шапки" # делаем коммит
# ВНИМАНИЕ: коммит пока не был отправлен в удалённый репозиторий
# сознаём, что нужно было еще что-то сделать в этом коммите.
subl inc/header.html          # вносим изменения
git add inc/header.html       # индексируем измененный файл (можно git add .)
git commit --amend -m "«Шапка»: выполнена задача №34" # заново делаем коммит

Работа с ветками

Есть master (публичная версия сайта), выполняем масштабную задачу (переверстать «шапку»), но по ходу работ возникает необходимость подправить критичный баг (неправильно указан контакт в «подвале»).

# указана последовательность действий:
git checkout -b new-page-header # создадим новую ветку для задачи изменения «шапки» и перейдём в неё
subl inc/header.html            # редактируем разметку «шапки»
git commit -a -m "Новая шапка: смена логотипа" # делаем коммит (работа еще не завершена)
# тут выясняется, что есть баг с контактом в «подвале»
git checkout master             # возвращаемся к ветке master
subl inc/footer.html            # устраняем баг и сохраняем разметку «подвала»
git commit -a -m "Исправление контакта в подвале" # делаем коммит
git push                        # отправляем коммит с быстрым критическим изменением в master в удалённом репозитории
git checkout new-page-header    # переключаемся обратно в ветку new-page-header для продолжения работ над «шапкой»
subl inc/header.html            # редактируем и сохраняем разметку «шапки»
git commit -a -m "Новая шапка: смена навигации" # делаем коммит (работа над «шапкой» завершена)
git checkout master             # переключаемся в ветку master
git merge new-page-header       # вливаем в master изменения из ветки new-page-header
git branch -d new-page-header   # удаляем ветку new_page_header

Работа с ветками, слияние и откат к состоянию до слияния

Была ветка fix, в которой исправляли баг. Исправили, влили fix в master. но тут выяснилось, что это исправление ломает какую-то функциональность, Нужно откатить master к состоянию без слияния (наличие бага менее критично, чем порча функциональности).

# находимся в ветке fix, баг уже «исправлен»
git checkout master            # переключаемся на master
git merge fix                  # вливаем изменения из fix в master
# видим проблему: часть функциональности сломалась
git checkout fix               # переключаемся на fix (пока мы в master, git не даст ее двигать)
git branch -f master ORIG_HEAD # передвигаем ветку master на коммит, указанный в ORIG_HEAD (тот, на который указывала master до вливания fix)

Работа с ветками, конфликт слияния

Есть ветка master (публичная версия сайта), в двух параллельных ветках (branch-1 и branch-2) было отредактировано одно и то же место одного и того же файла, первую ветку (branch-1) влили в master, попытка влить вторую вызывает конфликт.

# указана последовательность действий:
git checkout master           # переключаемся на ветку master
git checkout -b branch-1      # создаём ветку branch-1, основанную на ветке master
subl .                        # редактируем и сохраняем файлы
git commit -a -m "Правка 1"   # коммитим
git checkout master           # возвращаемся к ветке master
git checkout -b branch-2      # создаём ветку branch-2, основанную на ветке master
subl .                        # редактируем и сохраняем файлы
git commit -a -m "Правка 2"   # коммитим
git checkout master           # возвращаемся к ветке master
git merge branch-1            # вливаем изменения из ветки branch-1 в текущую ветку (master), удача (автослияние)
git merge branch-2            # вливаем изменения из ветки branch-2 в текущую ветку (master), КОНФЛИКТ автослияния
# Automatic merge failed; fix conflicts and then commit the result.
subl .                        # выбираем в конфликтных файлах те участки, которые нужно оставить, сохраняем
git commit -a -m "Устранение конфликта" # коммитим результат устранения конфликта

Синхронизация репозитория-форка с мастер-репозиторием

Есть некий репозиторий на github.com, он него нами был сделан форк, добавлены какие-то изменения. Оригинальный (мастер-)репозиторий был как-то обновлён. Задача: стянуть с мастер-репозитория изменения (которые там внесены уже после того, как мы его форкнули).

# указана последовательность действий:
git remote add upstream https://github.com:address.git # добавляем удаленный репозиторий: сокр. имя — upstream, URL мастер-репозитория
git fetch upstream            # стягиваем все ветки мастер-репозитория, но пока не сливаем со своими
git checkout master           # переключаемся на ветку master своего репозитория
git merge upstream/master     # вливаем стянутую ветку master удалённого репозитория upstream в свою ветку master

Ошибка в работе: закоммитили в мастер, но поняли, что нужно было коммитить в новую ветку

ВАЖНО: это сработает только если коммит еще не отправлен в удалённый репозиторий.

# указана последовательность действий:
# сделали изменения, проиндексировали их, закоммитили в master, но ЕЩЁ НЕ ОТПРАВИЛИ (не делали git push)
git checkout -b new-branch    # создаём новую вертку из master
git checkout master           # переключаемся на master
git reset HEAD~ --hard        # сдвигаем указатель (ветку) master на 1 коммит назад
git checkout new-branch       # переключаемся обратно на новую ветку для продолжения работы

Нужно вернуть содержимое файла к состоянию, бывшему в каком-либо коммите (известен хеш коммита)

# указана последовательность действий:
git checkout f26ed88 -- index.html # восстановить в рабочей директории состояние указанного файла на момент указанного коммита, добавить это изменение в индекс
git commit -am "Navigation fixs"   # сделать коммит

При любом действии с github (или другим удалённым сервисом) запрашивается логин и пароль

Речь именно о запросе пары логин + пароль, а не ключевой фразы. Происходит это потому, что git по умолчанию не сохранит пароль для доступа к репозиторию по HTTPS.

Простое решение: указать git кешировать ваш пароль.

.gitattributes

* text=auto

*.html diff=html
*.css  diff=css
*.scss diff=css

В этом уроке мы с вами узнаем, как перемещать указатель HEAD. Это знание откроет перед нами много возможностей. Например, мы сможем откатиться к предыдущему коммиту, добавить в созданный ранее коммит файлы или исправить ошибку в уже сделанном коммите.

Git. Урок 4.
Перемещение курсора и отмена изменений. Команды git restore, git rm, git reset, git checkout, git commit, git revert.

В этом уроке мы с вами узнаем, как перемещать указатель HEAD. Это знание откроет перед нами много возможностей. Например, мы сможем откатиться к предыдущему коммиту, добавить в созданный ранее коммит файлы или исправить ошибку в уже сделанном коммите.

Smartiqa Git cover

Команды: git restore, git rm, git reset, git checkout, git commit, git revert.

Оглавление

Теоретический блок

1. Удаляем и восстанавливаем файлы правильно. Команды git rm и git restore.
2. Просмотр старых коммитов и перемещение указателя HEAD

  1. История выводится не полностью
  2. Как переключиться обратно
  3. Зачем может понадобиться переключать указатель на старые коммиты, и важные особенности состояния «detached head».

3. Откат коммитов. Команда git revert.
4. Удаление и объединение коммитов. Команда git reset.

  1. Редактирование или отмена последнего коммита
  2. Объединение нескольких коммитов в один.
  3. Удаление последних нескольких коммитов.
  4. Отмена изменений команды git reset.

5. Различие команд git reset и git checkout и git revert.

Перейти

Практический блок

1. Задание
2. Решение

Перейти

ТЕОРЕТИЧЕСКИЙ БЛОК

Сегодня мы познакомимся с командами, которые позволят удалять и восстанавливать файлы в рабочей копии и индексе, отменять изменения на уровне целых коммитов, возвращаться в прошлое нашего репозитория и манипулировать указателем HEAD.

Удаляем и восстанавливаем файлы правильно. Команды git rm и git restore.

Начнем с удаления файлов. У читателя может возникнуть вопрос: почему нельзя просто взять и переместить файл из репозитория в корзину, как мы привыкли это делать с обычными файлами? Давайте разбираться.

Как мы говорили во втором уроке, файл в репозитории может находится в нескольких состояниях:

1. Отслеживаемый.

  1. Измененный (неподготовленный к коммиту).
  2. Подготовленный к коммиту.

2. Неотслеживаемый.

Когда мы делаем файл отслеживаемым или подготавливаем его к коммиту, информация об этом файле записывается в индекс (файл .git/index). Эта информация никуда не денется, даже если мы удалили файл из рабочей копии. Рассмотрим пример. Допустим, мы выполним следующие действия:

1. Создадим файл sample.txt и внесем в него какие-то изменения;
2. Сделаем его отслеживаемым;
3. Удалим файл sample.txt;
4. Сделаем коммит;

В таком случае, просматривая содержимое нового коммита, мы обнаружим, что файл sample.txt в нем присутствует, несмотря на то, что мы вроде бы его удалили. Это произошло из-за того, что хоть мы и удалили файл из рабочей копии, но не удалили информацию о нем из индекса.

В Git существует специальная команда, чтобы удалять файлы «правильно».

Команда git rm

Формат

git rm <ключ> <имя файла>

Что делает

Удаляет файл из рабочей копии и индекса / только из индекса. Данная команда не может удалить файл только из рабочей копии.

Пример

# Удалим файл sample.txt из рабочей копии и индекса
$ git rm sample.txt

# Удалим файл sample.txt из индекса и перемеcтим его в категорию Untracked
$ git rm —cached sample.txt

Внимательный читатель спросит: но почему нельзя удалить файл из репозитория обычным образом, а затем добавить изменения в индекс командой git add и сделать коммит? На самом деле можно. Команда git rm является сокращением вышеописанных действий. Ее применение считается более правильным, поскольку она короче и красивее. Давайте рассмотрим небольшой пример:

# Удалим файл обычным образом
$ rm sample.txt
$ git status
On branch main
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    sample.txt
$ git add -A

# И вся последовательность команд выше эквивалентна всего одной:
$ git rm sample.txt
rm 'sample.txt'

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    sample.txt

# Осталось только сделать коммит, чтобы сохранить изменения:
$ git commit -m "L-04: Deleted sample.txt"
[main f34705a] L-04: Deleted sample.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 sample.txt

Обычное удаление файла, который отслеживается Git

Обычное удаление файла, который отслеживается Git

”Правильное” удаление файла. Команда git rm

«Правильное» удаление файла. Команда git rm.

Итак, мы разобрались с удалением файлов. Но что, если мы захотим восстановить файл после удаления или изменения в рабочей копии или индексе? Для этого существует команда git restore. Давайте разберем ее подробнее.

Команда git restore

Формат

git restore <ключ> <имя файла>

Ключи

-s, —source=<tree>
Этот ключ нужен, чтобы передать команде путь к коммиту (ветке, пользовательскому указателю), откуда мы будем восстанавливать файл. По умолчанию файл берется из области индекса.

worktree (англ. рабочая копия)
—staged (англ. область индекса)

Эти два ключа позволяют указать, где именно восстанавливать файл. По умолчанию, если ни один из этих двух ключей не передан, файл восстанавливается только в рабочей копии.

Если же передан ключ —staged, файл восстанавливается только в области индекса. В этом случае источником восстановления по умолчанию является коммит, на который указывает HEAD (поскольку мы не можем восстановить файл в области индекса из самой же области индекса).

Если же вы хотите восстановить файл и в рабочей копии, и в области индекса, вам нужно передать оба ключа.

Что делает

Восстанавливает указанный файл из переданного источника. По умолчанию источником является индекс. Если файла нет в указанном источнике, файл будет удален.

Пример

# Если вы случайно удалили файл sample.txt обычным способом, то можно восстановить его из индекса
$ git restore sample.txt

# Вернем файл sample.txt к определенному коммиту с хэшем 09c2240. При этом мы изменим только файл в рабочей копии, файл в области индекса не поменяется.
$ git restore —source 09c2240 sample.txt

# Вернем файл sample.txt в индексе к состоянию последнего коммита (отменим все внесенные изменения или удалим файл, если в предыдущем коммите его не было), при этом изменения коснутся только индекса файла, рабочая копия не поменяется.
$ git restore —staged sample.txt

# Сделаем то же, что и в предыдущем примере, но теперь изменения затронут и файл в рабочей копии.
$ git restore —staged —worktree sample.txt

С помощью команды git restore можно сделать неотслеживаемыми файлы, которые вы случайно добавили командой git add. Приведем пример:

# Допустим у нас есть некоторый репозиторий, просмотрим статус файлов:
$ git status
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
       file_to_commit.txt
       another_file_to_commit.txt
       file_not_for_commit.txt

# Для простоты добавим все файлы в индекс разом
$ git add -A

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   file_to_commit.txt
        new file:   another_file_to_commit.txt
        new file:   file_not_for_commit.txt

# А теперь восстановим файл “file_not_for_commit.txt” в области индекса. Тогда источником станет последний коммит, а в нем такого файла нет (файл же новый). Поэтому файл будет удален из области индекса. Кстати, можно заметить, что даже Git подсказывает нам: “use "git restore --staged <file>..." to unstage”. 

$ git restore --staged file_not_for_commit.txt

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   another_file_to_commit.txt
        new file:   file_to_commit.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        file_not_for_commit.txt

# Для справки: в данном случае эквивалентной командой будет:
$ git rm --cached file_not_for_commit.txt
rm 'file_not_for_commit.txt'

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   another_file_to_commit.txt
        new file:   file_to_commit.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        file_not_for_commit.txt

Подведем итогИтак, мы теперь умеем удалять и восстанавливать файлы из индекса и рабочей копии.
1. Чтобы удалить файл правильно, воспользуйтесь командой git rm. Она удалит файл из индекса и из рабочей копии.

  1. Вариант git rm —cached удалит файл из индекса и переместит его в категорию Untracked.
  2. По своей сути git rm <filename> представляет сокращение двух команд: rm <filename> и git add <filename>.

2.Чтобы восстановить файл в рабочей копии и/или индексе, воспользуйтесь командой git restore.

  1. Ключ —source=<tree> позволит вам указать место, откуда брать файл для восстановления. По умолчанию этим местом является область индекса.
  2. По умолчанию файл восстанавливается в рабочей копии, но вы можете восстановить файл в области индекса с ключом —staged или одновременно в области индекса и в рабочей копии, передав ключи —staged —worktree. В этом случае по умолчанию (если вы не передали ключ —source) файл будет взят из коммита, на который указывает HEAD.

Просмотр старых коммитов и перемещение указателя HEAD

Итак, пришло время разобраться, каким образом можно перемещать HEAD (и другие указатели тоже) и что это дает. Для начала нужно прояснить несколько важных аспектов, которые мы уже упоминали, но не обращали ваше внимание на них:

1. Весь репозиторий – это древовидный граф, ноды которого – наши коммиты, а ребра – родительские отношения между коммитами.
2. HEAD – это указатель (то есть ссылка на один из коммитов), главное назначение которого — определять, в каком состоянии находится рабочая копия. На какой коммит указывает HEAD – в таком состоянии файлы и находятся в рабочей области.
3. Обычно HEAD указывает не на определенный коммит, а на указатель ветки, который в свою очередь указывает на конкретный коммит.
4. HEAD можно перемещать: при перемещении указателя файлы в рабочей копии изменятся так, чтобы соответствовать коммиту, на который указывает HEAD.
5. Указывая путь до чего бы то ни было, вы можете использовать как абсолютные указатели, например, хэш коммита или имя ветки, так и относительные. Вспомним, как пользоваться относительными указателями:

  1. Знак ^ означает «предыдущий». Например путь HEAD^ означает «предыдущий коммит перед тем, на который указывает HEAD«
  2. Знак ~ позволяет вам указать число коммитов. Например, запись HEAD~7 означает «7 коммитов назад от коммита, на который указывает HEAD«.

Использование относительных путей особенно удобно, поскольку они позволяют не запоминать хэши коммитов и не обращаться к истории репозитория чаще, чем это действительно нужно.

Давайте подробнее разберемся с перемещением указателя, а затем поговорим, зачем это может быть нужно. Итак, чтобы перемещать указатель, нужно воспользоваться знакомой нам из прошлого урока командой git checkout.

Команда git checkout

Что делает

Переводит курсор HEAD на указанный коммит или другой указатель и копирует.

Пример

# Передвинем HEAD на два коммита назад
$ git checkout HEAD~2
HEAD is now at 7194f7e L-04: Fixing gradient bug

Можно заметить, что синтаксис совершенно такой же, как и в переключении ветки из предыдущего урока. Все верно, ведь переключение ветки – ни что иное, как передвижение указателя HEAD с указателя одной ветки на указатель другой. Чтобы подробно разобраться, что происходит при передвижении указателя, давайте рассмотрим пример. Изначально у нас есть такой репозиторий.

Исходный репозиторий

Серыми овалами обозначены коммиты, текст на них – часть хэша соответствующего коммита. Коричневым прямоугольником обозначен указатель ветки: она у нас одна и называется main. Белым прямоугольником обозначен указатель HEAD, иногда его называют курсором. Его-то мы и будем двигать. Давайте попробуем сдвинуть HEAD на два коммита назад (то есть на коммит с хэшем 90ab…) и посмотрим, что будет.

$ git checkout HEAD~2
Note: switching to 'HEAD~2'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 90abf7e L-04: Fixing gradient bug

Предупреждение Git мы разберем чуть ниже, а сейчас давайте посмотрим, в каком состоянии мы оказались.

Граф Git после перевода указателя HEAD на два коммита назад

Граф Git после перевода указателя HEAD на два коммита назад

Как видно из рисунка, указатель HEAD сейчас действительно передвинут на два коммита назад. Но указатель ветки main остался на месте и все еще указывает на последний коммит. Такое состояние, когда HEAD указывает не на указатель ветки, а непосредственно на сам коммит, называется detached head.

Detached head (англ. дословно – отрубленная голова, имеется в виду указатель HEAD, отключенный от графа) – состояние, при котором указатель HEAD не указывает ни на одну из веток репозитория, а ссылается непосредственно на сам коммит.

Если прочесть предупреждение Git выше, можно заметить, что он сообщает нам, что мы оказались в состоянии detached head. Давайте рассмотрим несколько особенностей этого состояния:

2.1. История выводится не полностью

Действительно, если мы из текущего состояния выполним команду git log:

$ git log --pretty=oneline
90abf7ef211229adfa4cb75e0f35a0561dd15467 (HEAD)  L-04: Fixing gradient bug
3a3bb706651a19013822c09e5c70c9fc425a66dc L-04: Adding gradient function
d469222a7a760daa3cd56747e216e3de2a3343ee L-04: Initial commit

Видно, что вывелись только те коммиты, которые были сделаны позже коммита, на котором сейчас стоит HEAD. Если же мы хотим просмотреть всю историю, нужно воспользоваться ключом —all:

$ git log --all --pretty=oneline
62aa1ffe0587c7ffa3d865a3233da04d65818030 (main) L-04: Adding autograd
33ff7207fed9cbb34c9f3334249ef0707477f278 L-04: Adding neuron class
90abf7ef211229adfa4cb75e0f35a0561dd15467 (HEAD)  L-04: Fixing gradient bug
3a3bb706651a19013822c09e5c70c9fc425a66dc L-04: Adding gradient function
d469222a7a760daa3cd56747e216e3de2a3343ee L-04: Initial commit

Тогда нам видна полная история репозитория. Если присмотреться, то можно увидеть, что напротив первого сверху коммита в скобках написано main. Так Git сообщает нам, что на данный коммит указывает ветка main. Аналогично с третьим сверху коммитом: напротив него написано HEAD. Это означает, что HEAD указывает на этот коммит.

2.2. Как переключиться обратно

Вам вовсе необязательно смотреть полную историю каждый раз, когда вы хотите вернуться обратно на ветку main. В предыдущем уроке мы рассматривали команду git checkout — и говорили, что с ее помощью можно вернуться на ветку, с которой мы переключились на текущую. Так вот, эта команда работает не только для веток, но и для любых перемещений HEAD. Если мы хотим перенести HEAD обратно, достаточно выполнить:

$ git checkout -
Previous HEAD position was 90abf7e L-04: Fixing gradient bug
Switched to branch 'main'

Как видно по выводу Git, мы успешно вернулись обратно на ветку main и вышли из состояния detached head.

У данного способа есть один минус: если вы «прыгали» по коммитам репозитория несколько раз, то git checkout — вернет вас на последний коммит, на котором вы были, а не на ветку main. В данном случае поможет простое и гениальное:

$ git checkout main
Previous HEAD position was 90abf7e L-04: Fixing gradient bug
Switched to branch 'main'

То есть мы напрямую указываем Git, что хотим переключиться на ветку main (или любую другую), таким образом выходя из состояния detached head.

2.3. Зачем может понадобиться переключать указатель на старые коммиты, и важные особенности состояния «detached head».

На самом деле, все зависит от конкретной задачи и проекта, над которым вы работаете. Вообще состояние detached head на практике используется довольно-таки редко. Но все же можно привести несколько примеров использования этого состояния:

Пример 1
Самый банальный пример – просмотр файлов в определенном коммите. Допустим, вы хотите просмотреть содержимое файла main.py в коммите с определенным хэшем.
Просто выполните:

# Команда cat выводит в консоль содержимое файла.
$ git checkout 5df3f7e
$ cat main.py
print(“Hello world”)

Таким образом, вы можете просмотреть содержимое любого, когда либо закоммиченного файла, что бывает довольно удобно.

Пример 2
Кроме того, когда вы работаете над большим проектом, может быть нужно вернуться на несколько коммитов назад и создать свою ветку оттуда: например, чтобы протестировать экспериментальную функцию в том состоянии проекта. Сделать это можно так:

# Переключим указатель HEAD на определенный коммит:
$ git checkout 9a4e88b

# Теперь создадим новую ветку
$ git checkout -b feature
Switched to a new branch 'feature'

# Добавим файл и сделаем коммит
$ echo "just docs we forgot to put" > docs.md
$ cat docs.md
just docs we forgot to put

$ git add -A
$ git commit -m "L-04: Adding docs"
[feature d5e3273] L-04: Adding docs
 1 file changed, 1 insertion(+)
 create mode 100644 docs.md

Теперь у нас есть целая ветка feature, которая берет свое начало с коммита 9a4e88b. На ней мы можем проводит эксперименты с тем состоянием репозитория, которое было несколько коммитов назад, не боясь навредить остальной части репозитория.

Заметьте, что когда мы создали и переключились на ветку, мы вышли из состояния detached head. Если бы мы сделали коммит, не создавая для этого ветки, этот коммит оказался бы «оторванным» от истории: на него не ссылалась бы ни одна ветка, а в выводе истории (даже полной) его не было бы видно. Иначе говоря, если бы мы сделали коммит без ветки, мы бы смогли получить к нему доступ только запомнив его хэш наизусть.

Поэтому с состоянием detached head следует быть очень осторожным: если забыть создать новую ветку и начать делать коммиты, их можно с легкостью потерять.

На самом деле, если вы сделали несколько коммитов из состояния detached head и забыли создать ветку, ничего страшного: ветку можно создать прямо сейчас. Рассмотрим пример с тем же репозиторием:

# Переключимся в состояние “detached head” на ветке main.
$ git checkout d5e3273

# Теперь мы создадим несколько коммитов, исключительно для примера:
$ echo 'print('hello world')' > hello_world.py
$ echo 'print('Hi')' > say_hi.py
$ echo 'Some text file' > about.txt
$ git add hello_world.py
$ git commit -m "L-04: Adding hello_world.py"
[detached HEAD d013c75] L-04: Adding hello_world.py
 1 file changed, 1 insertion(+)
 create mode 100644 hello_world.py

$ git add say_hi.py
$ git commit -m "L-04: Adding say_hi.py"
[detached HEAD 58f8c22] L-04: Adding say_hi.py
 1 file changed, 1 insertion(+)
 create mode 100644 say_hi.py

$ git add about.txt
$ git commit -m "adding about.txt"
[detached HEAD 7c10724] L-04: Adding about.txt
 1 file changed, 1 insertion(+)
 create mode 100644 about.txt

# А сейчас мы вспомнили, что сделали несколько коммитов в состоянии “detached head” и испугались, что потеряем их. Но не стоит бояться, можно просто создать ветку прямо сейчас:
$ git checkout -b feature
Switched to a new branch 'feature'

# И чтобы убедиться, что мы не потеряли ни одного коммита, давайте взглянем на историю ветки feature
$ git log --pretty=oneline main..feature
7c10724b12dba69ed1acf4c6fef804c251f7c290 (HEAD -> feature) L-04: Adding about.txt
58f8c226952d500df7a9c2f798011ab20165a286 L-04: Adding say_hi.py
d013c75418ac71ca8f4578583aa23d7567dab332 L-04: Adding hello_world.py
d5e327368d12d6e5ef5b04e16af1d96319069805 L-04: Adding docs

Даже в состоянии detached head коммиты сохраняют родительские отношения, то есть каждый коммит знает, кто его предшественник. Поэтому мы в любой момент можем восстановить их последовательность, если найдем крайний коммит в данной цепочке. То есть для нас главное не забыть создать ветку, находясь в состоянии detached head. А вот создать ее можно в любой момент: до того, как мы сделаем первый коммит из этого состояния, или в любой другой момент – не важно, главное сделать это до переключения на другую ветку или коммит. Тогда у нас будет ссылка на крайний коммит ветки, а по его родителям мы сможем определить всю последовательность.

Подведем итог

  1. Указатель HEAD можно перемещать на разные коммиты, точно так же, как мы перемещали его между ветками. Для этого нужно использовать команду git checkout.
  2. Когда мы перемещаем HEAD с указателя ветки на коммит, мы попадаем в состояние detached head.
  3. В состоянии detached head стоит быть осторожным: если планируете делать коммиты, лучше сразу создайте ветку командой git checkout -b <имя ветки>.
  4. Если вы сделали несколько коммитов из состояния detached head, забыв создать ветку – ничего страшного, ветку можно создать в любой момент.
  5. Обычно detached head используется редко, но вы можете использовать его, чтобы просматривать старые версии файлов или экспериментировать с предыдущими версиями проекта.

    Откат коммитов. Команда git revert.

    Пожалуй одна из самых важных частей в изучении Git – научиться откатываться к предыдущим коммитам. Смысл отката мы обсуждали в предыдущих уроках: ваш проект может перестать работать по непонятным вам причинам после внесения некоторых изменений в код, в таком случае важно быстро вернуть все к рабочему состоянию и только потом заниматься поиском ошибки. В этом-то случае нам и поможет откат коммитов и команда git revert.

    Суть работы данной команды в том, что она создает новый коммит, который отменяет изменения внесенные в переданном коммите (последовательности коммитов).

    Команда git revert

    Формат

    git revert <ключи> <адрес коммита>

    Что делает

    Отменяет изменения, внесенные в переданном коммите.

    Пример

    # Отменим изменения, внесенные 2 коммита назад
    $ git revert HEAD~2

    # Отменим все изменения в коммитах, начиная с пятого с конца и заканчивая вторым с конца.
    $ git revert HEAD~5..HEAD~2

    Команда git revert очень важна, поэтому давайте разберем, как с ней работать. Рассмотрим самый простой пример, в котором у нас не возникнет файловых конфликтов. Допустим, мы хотим отменить изменения предпоследнего коммита. Главное, что здесь нужно запомнить – это что у нас не должно быть незакоммиченых изменений в рабочей директории, ведь мы все-таки делаем реверт-коммит. Если у вас есть таковые, лучшим решением станет закоммитить (или удалить) все изменения, и только потом делать реверт.

    # Выполним реверт предпоследнего коммита. Кстати, такой реверт никогда не вызовет конфликтов.
    
    $ git revert HEAD~1
    
    # Как только мы выполним команду, будет открыт консольный редактор, чтобы вы могли отредактировать сообщение нового коммита. Можно что-то дописать, можно сразу закрыть редактор сочетанием Ctrl+X (здесь приведено сочетание для редактора “nano”). Решать вам. В нашем случае окно редактора будет выглядеть так.
    
    Revert "L-04: Addit docs.txt"
    
    This reverts commit aadfbc3a6756289727e56ac3de59004e66e40033.
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    #
    # On branch master
    # Your branch is up to date with 'origin/master'.
    #
    # Changes to be committed:
    #       modified:   docs.txt
    #
    
    
    
    ^G Get Help  ^O Write Out ^W Where Is  ^K Cut Text  ^J Justify   ^C Cur Pos
    ^X Exit      ^R Read File ^ Replace   ^U Paste Text^T To Spell  ^_ Go To Line
    
    # После закрытия редактора, в терминал будет выведена информация об успешном реверте коммита
    
    [main e933971] Revert "addit docs.txt"
     1 file changed, 1 insertion(+), 1 deletion(-)

    Итак, это был самый простой пример. В основном сложности git revert связаны с разрешением файловых конфликтов. Разрешать мы их научимся в следующем уроке, а пока давайте обсудим, из-за чего может возникнуть конфликт. Может, обладая этими знаниями и столкнувшись с конфликтом во время выполнения git revert,вы поймете, что вам совсем не нужен откат.

    Иногда при отмене изменений может возникнуть ситуация, когда в файл в рабочей копии были внесены изменения с момента коммита находится не в том же состоянии, как в коммите, который мы отменяем. Звучит сложно, поэтому давайте приведем пример. Допустим, у нас есть следующий репозиторий.

    Репозиторий: 4 коммита - 4 изменения в файле docs.md

    Репозиторий: 4 коммита — 4 изменения в файле docs.md

    В каждом из вышеуказанных коммитов всего один файл: docs.md. Над хэшем каждого коммита на рисунке указано содержимое docs.md, которое было внесено в данный коммит.

    Теперь, когда перед глазами у вас есть диаграмма, давайте разберемся, в каком случае при откате возникнет конфликт файлов. На самом деле, конфликт возникнет при откате любого, кроме самого последнего коммита. Как мы уже говорили, при реверте коммита, его изменения отменяются, то есть файлы возвращаются к состоянию предыдущего коммита. Например, если мы попробуем откатить коммит 33ff, то содержимое файла docs.md изменится с

    Содержимое docs.md, коммит 33ff

    Содержимое docs.md, коммит 90ab

    Но ведь в последнем коммите, т.е. в рабочей копии находится файл, содержимое которого

    Содержимое docs.md, коммит c732f69

    Some docs here
    and here
    also here
    

    что совсем не соответствует содержимому коммита, который мы откатываем. То есть с момента того коммита мы уже успели изменить файл docs.md, и теперь Git не очень понимает, как именно делать откат:

    1. оставить файл в рабочей копии нетронутым или,
    2. наоборот, заменить файл в рабочей копии на файл после отката.

    Поэтому и возникает конфликт. Git напрямую спрашивает у нас: «Какой файл мне оставить?». На практике это выглядит вот так:

    # Пробуем откатить коммит .
    $ git revert 33ff381
    Auto-merging docs.md
    CONFLICT (content): Merge conflict in docs.md
    error: could not revert 33ff381... L-04: Adding info to docs
    hint: after resolving the conflicts, mark the corrected paths
    hint: with 'git add <paths>' or 'git rm <paths>'
    hint: and commit the result with 'git commit'

    В следующем уроке, когда мы будем говорить о слиянии веток, мы научимся разрешать файловые конфликты.

    Кстати, если вы попали в ситуацию, когда Git сообщил вам о конфликте файлов, а вы такого не ожидали, или вы просто передумали делать откат из-за конфликта, можете в любой момент выполнить git revert —abort. Это остановит откат, и вы сможете спокойно разобраться, откуда возникает конфликт.

    Подведем итог

    1. git revert – команда, отменяющая изменения переданного коммита. Она заменяет файлы в рабочей копии на файлы предка переданного коммита, а затем делает коммит, чтобы сохранить изменения.
    2. Иногда во время отката возникают конфликты. Их не стоит бояться, но нужно быть внимательным и разобраться, откуда возникает конфликт.
    3. Если вы столкнулись с конфликтом во время отката и передумали продолжать git revert, выполните команду git revert —abort. Она вернет все как было и отменит откат.
    4. В сообщении реверт-коммита следует указывать полезную информацию: зачем вы сделали откат, каким образом вы сливали файлы, если были конфликты, и т.д.

    Удаление и объединение коммитов. Команда git reset.

    Помните, мы говорили про команду git checkout и перемещение указателя HEAD? Тогда мы перемещали только сам указатель HEAD и попадали в состояние detached head. Команда git reset позволяет нам перемещать указатель ветки вместе с указателем HEAD. Давайте разберем эту команду подробнее.

      Команда git reset

      Формат

      git reset <ключи> <адрес коммита>

      Что делает

      Переносит указатель ветки на переданный коммит.

      Пример

      # Сделаем reset последнего коммита, который мы сделали по ошибке. При этом оставим файлы в том же состоянии.
      $ git reset —soft HEAD^

      # Отменим последние три коммита и удалим все изменения в файлах.
      $ git reset —hard HEAD~3
      HEAD is now at 2f96b73 L-04: Create main.py

      Чтобы лучше понять смысл этой команды приведем граф репозитория. Изначально он выглядел так.

      Репозиторий до ресета

      Теперь выполним «мягкий» reset последних двух коммитов.

      $ git reset --soft HEAD~2
      

      Теперь наш граф выглядит так.

      Репозиторий после выполнения команды git reset

      Репозиторий после выполнения команды git reset

      Причем файлы в рабочей копии остались в том же состоянии, что и были в коммите 62aa.

      Как видно из рисунка, Git создал новый указатель – ORIG_HEAD. Этот указатель создается при выполнении команды git reset и ссылается на тот коммит, от которого мы делали reset. Как мы увидим ниже, это очень полезный в работе с git reset указатель.

      Кстати, если мы сейчас сделаем коммит:

      $ git add -A
      $ git commit -m “L-04: Reset commit”
      [main 03953f8] L-04: Reset commit
       1 file changed, 3 insertions(+), 1 deletion(-)

      То получим такую картину:

      Новый коммит после ресета

      Новый коммит после ресета

      То есть наш коммит пойдет по ответвлению от исходной ветки main. Поэтому будьте внимательны, создавая новые коммиты.

      Итак, теперь поговорим, как на практике используется git reset. В основном, все использование сводится к трем пунктам:

      1. Редактирование/отмена последнего коммита.
      2. Объединение нескольких коммитов в один.
      3. Удаление коммитов.
      4. Отмена изменений команды git reset.

      Давайте подробно разберем каждый пункт.

      4.1. Редактирование или отмена последнего коммита

      Если вы случайно опечатались в сообщении последнего коммита, или хотите добавить в него файлов, то git reset поможет вам. Но прежде всего нам нужно разобрать новые ключи уже знакомой команды git commit. Они потребуются нам в дальнейшем.

      Команда git commit

      Формат

      git commit <ключи> <адрес переданного коммита>

      Что делает

      Создает новый коммит.

      Пример

      # Сделаем новый коммит с таким же сообщением и информацией об авторе, как у коммита, на который указывает ветка feature.
      $ git commit -C feature

      # Сделаем то же самое, но отредактируем сообщение коммита.
      $ git commit -с feature

      Теперь, когда мы познакомились с новыми ключами, можем перейти к редактированию последнего коммита. Последовательность действий в данной ситуации такая:

      1. Откатиться к предпоследнему коммиту командой git reset —soft HEAD^
      2. Добавить в коммит новые файлы, если вам это нужно, использовав команду git add.
      3. Выполнить git commit -c ORIG_HEAD, если вы хотите отредактировать сообщение, или git commit -C ORIG_HEAD, если вы хотите оставить сообщение коммита без изменений.

      Полезно знать
      Кстати, данная последовательность команд идентична git commit —amend. Чтобы отредактировать последний коммит, следуйте инструкции:
      1. Если вы хотите добавить в коммит файлы, то для начала добавьте их в индекс командой git add <имя файла>. Если не хотите, то проверьте, что проиндексированных изменений нет, иначе они будут добавлены в последний коммит.
      2. Выполните

      1. git commit —amend. Тогда откроется консольный редактор, где вы сможете отредактировать сообщение последнего коммита.
      2. git commit —amend -m «<новое сообщение>». Тогда сообщение последнего коммита будет заменено на <новое сообщение>.

      В случае, если же вы хотите насовсем удалить последний коммит, просто выполните

      $ git reset --hard HEAD^
      HEAD is now at c732f69 L-04: Adding more info to docs

      Таким образом, последний коммит будет удален и не будет отображаться в логе. Тем не менее, вернуться к нему вы сможете по указателю ORIG_HEAD, который оставит Git.

      4.2. Объединение нескольких коммитов в один.

      Иногда на практике возникают ситуации, когда для удобства восприятия и красивой истории необходимо объединить несколько последних коммитов в один. Порядок действий тут почти такой же, как и в случае редактирования последнего коммита:

      1. Выполните git reset —soft HEAD~n, где n это число коммитов, которые вы хотите объединить. Эта команда вернет указатель ветки на n коммитов назад, оставив изменения в индексе и рабочей копии нетронутыми. То есть после выполнения этой команды вы откатитесь на n коммитов назад, но все изменения внесенные этими коммитами останутся у вас в рабочей копии и индексе.
      2. Выполните git commit -c ORIG_HEAD, а затем отредактируйте сообщение коммита должным образом. Эта команда сделает коммит всех изменений в индексе. То есть она сделает коммит, который по своему содержимому представляет объединение последних n коммитов.

      Собственно, это все. То есть на самом деле мы просто откатили последние несколько коммитов, а затем создали новый коммит, который собирает в себе все их изменения. При этом изначальные коммиты никуда не делись. Их не будет видно в истории, но если вы помните их хэш, то в любой момент сможете переключиться на любой из них с помощью команды git checkout.

      4.3. Удаление последних нескольких коммитов.

      Пожалуй, самая простая ситуация. Чтобы удалить последние n коммитов, выполните

      $ git reset --hard HEAD~n
      

      Где n – это, конечно, какое-то конкретное число. После этого удаленные коммиты не будут выводиться в истории (совсем, даже с флагом —all).

      4.4. Отмена изменений команды git reset.

      Если вы каким-то образом случайно выполнили git reset и решили все вернуть, просто переместите указатель ветки обратно, использовав команду

      Эта команда вернет указатель ветки на коммит, с которого вы делали git reset, и вы вернете все изменения, даже если использовали ключ —hard.

      Подведем итог
      Итак, мы выучили новую команду git reset. Она используется, чтобы перемещать указатель ветки по графу Git. На практике ее в основном применяют для

      1. Редактирования или удаления последнего сделанного коммита.
      2. Объединения последних нескольких коммитов в один.
      3. Удаления последних нескольких коммитов.

      После использования git reset, Git создаёт указатель ORIG_HEAD, который ссылается на коммит, с которого мы сделали reset.

      Не стоит забывать, что даже если вы использовали git reset —hard, вы всегда можете вернуть все к изначальному состоянию, выполнив git reset ORIG_HEAD. Это вернет указатель ветки на коммит, с которого вы делали reset.

      Различие команд git reset и git checkout и git revert

      Сегодня мы изучили несколько команд, которые на первый взгляд очень похожи друг на друга. Давайте разберемся, в чем разница между ними. Для начала приведем краткую справку, чтобы напомнить, что делает каждая из команд.

      Изученные команды

      Команда git revert

      Данная команда создает новый коммит, который отменяет действие одного из предыдущих коммитов. То есть новый коммит появится в истории, а предыдущие коммиты не изменятся.

      Команда git reset

      Работа этой команды зависит от вызова. Если вызвать git reset <ссылка>, то команда переместит ветку, на которую указывает HEAD, на переданную ссылку. Затем, в зависимости от опций —soft/—hard/—mixed, команда:

      1. либо останавливается,
      2. либо обновляет индекс и рабочую копию так, чтобы они соответствовали текущему коммиту,
      3. либо обновляет только индекс соответственно.

      То есть она изменит историю, исключив из нее все коммиты, оставшиеся после HEAD. Говоря проще, команда действует так:

      1. Перемещает ветку, на которую указывает HEAD, или только HEAD (если он находится в состоянии detached HEAD). Останавливается на этом шаге, если передан ключ —soft.
      2. Делает индекс таким же, как в коммите, на который указывает HEAD. Останавливается на этом шаге, если не передан ключ —hard.
      3. Делает рабочую копию такой же, как коммит, на который указывает HEAD

      Вы также можете вызвать основную команду (без ключей —hard и —soft), передав ей путь к файлу: git reset <ссылка> <имя файла>. В этом случае команда будет действовать также, но эффект будет отличаться, поэтому этот случай стоит рассмотреть отдельно.

      В случае, если вы не передали ссылку, вместо нее будет подставлен HEAD. То есть она обновит индекс так, чтобы он соответствовал коммиту, на который указывает HEAD. Иначе говоря, скопирует файл из HEAD в индекс. То есть ее действие противоположно git add <имя файла>: она отменит добавление файла в индекс.

      Если же вы указали ссылку, то в вашем индексе окажется файл из одного из предыдущих коммитов. То есть в рабочей копии файл не изменится, но если вы сразу же сделаете коммит, то в новом коммите будет файл из одного из предыдущих коммитов.

      Команда git checkout

      Действие этой команды тоже зависит от вызова.

      1. Если вызвать git checkout <ссылка>, то команда либо перемещает указатель HEAD на переданную ссылку (т.е. на другую ветку или коммит). Историю данная команда не меняет, если только не забыть использовать ключ —all: git log —all.
      2. Если выполнить git checkout <ссылка> <имя файла>, то команда скопирует содержимое файла из переданного коммита в рабочую копию и индекс.

      Команда git restore

      Копирует файл из переданной ссылки в рабочую копию, индекс или сразу и туда, и туда. Изначально эта команда появилась, как аналог git checkout <ссылка> <имя файла>, но ее функционал немного шире. Данная команда также не меняет историю.

      Между git checkout, git reset и git restore есть несколько различий:

      1. Команда git checkout <ссылка> похожа на git reset —hard <ссылка>: в обоих случаях перемещается HEAD и меняется рабочая копия. Однако есть и разница:.

      1. Первое отличие состоит в том, что git checkout перемещает только HEAD, в то время как git reset перемещает HEAD и ветку, на которую указывает HEAD.
      2. Еще одно отличие заключается в том, что git checkout проверяет, что у вас в рабочей копии нет измененных файлов, в то время как git reset просто заменяет все файлы без разбора.

      В случае, когда мы находимся в состоянии detached head и у нас нет измененных файлов, эти две команды идентичны.

      2. Команда git checkout <ссылка> <имя файла> также имеет общие черты с git reset <ссылка> <имя файла>. Отличие состоит в следующем:

      git checkout <ссылка> <имя файла> изменяет и рабочую копию, и индекс, в то время, как git reset <ссылка> <имя файла> меняет только индекс. Кстати, идентичной командой для git checkout <ссылка> <имя файла> была бы команда git reset —hard <ссылка> <имя файла>, если бы ее можно было так использовать: это очень небезопасно для рабочей копии.

      3. Команда git restore задумывалась как аналог git checkout <ссылка> <имя файла>. Тем не менее, между ними есть следующие отличия:

      1. Используя git checkout <ссылка> <имя файла>, в качестве ссылки вы можете передать только определенный коммит, в то время как git restore может скопировать файл в рабочую копию прямо из индекса
      2. Команда git checkout всегда копирует файлы одновременно и в рабочую копию, и в область индекса, что не всегда удобно. В свою очередь, git restore может принять ключи —staged и —worktree, определяющие, скопировать файл только в область индекса, только в рабочую копию, или сразу в оба места.

      Для закрепления, давайте приведем таблицу сравнения команд git revert, git reset, git checkout и git reset по их взаимодействию с указателем HEAD, историей, индексом и рабочей копией.

      ПРАКТИЧЕСКИЙ БЛОК

      Задание. Библиотека Geometric Lib.

      Сегодня было много теории, пора закрепить ее на практике. Мы снова воспользуемся командой git clone (как и в прошлом уроке). Напомним, что она копирует удаленный репозиторий к вам на компьютер.

      Также напомним, что наш удаленный репозиторий представляет собой библиотеку на Python, которая позволяет высчитывать площади и периметры некоторых геометрических фигур.
      Ссылка на библиотеку Geometric Lib на GitHub: https://github.com/smartiqaorg/geometric_lib

      В этот раз в нашем репозитории появились еще две ветки, теперь его структура выглядит следующим образом:

      Структура библиотеки geometric_lib

      # Ветка main (Основная стабильная ветка)
      geometric_lib
          ├── circle.py
          ├── square.py
          └── docs
               └── README.md
      
      # Ветка develop (Ветка разработки)
      geometric_lib
          ├── circle.py
          ├── square.py
          └── docs
               └── README.md
          └── calculate.py
      
      # Ветка feature (Ветка для новых функций)
      geometric_lib
          ├── circle.py
          ├── square.py
          └── docs
               └── README.md
          └── rectangle.py

      К файлам добавилось три новых: triangle.py, rectangle.py и calculate.py. Первый содержит функции для вычисления периметра и площади треугольника, второй – то же для квадрата. В свою очередь, calculate.py объединяет функционал этих файлов: он предназначен для расчета площади и периметра переданной пользователем фигуры.

      Задание. Условие.

      1. Клонирование репозитория и знакомство с его структурой

      1.1. Выполните git clone https://github.com/smartiqaorg/geometric_lib. Эта команда создаст директорию geometric_lib/ на вашем компьютере и скопирует наш удаленный репозиторий. Не забудьте перейти в эту директорию командой cd geometric_lib, когда клонирование будет завершено.

      Кстати, когда вы склонируете к себе наш репозиторий, у вас будет только одна локальная ветка: main. Чтобы создать остальные, нужно выполнить git checkout <имя ветки>. Эта команда переключит вас на коммит, на который указывает удаленная ветка и создаст там локальную ветку с таким же именем. Эту команду нужно запустить для каждой ветки отдельно. То есть у вас получится два запуска: для ветки feature и ветки develop.

      1.2. Постройте полный граф истории, чтобы познакомиться со структурой комитов.

      2. Работа с веткой feature

      В последнем коммите ветки feature допущена ошибка. Откатите этот неудачный коммит.

      3. Работа с веткой develop

      Теперь заметьте, что у нас есть два коммита в ветке develop одной и той же тематики: «L-04: Add calculate.py», «L-04: Update docs for calculate.py». Объедините их в один коммит и напишите к нему пояснение.

      4. Эксперименты. Работа с файлами calculate.py и rectangle.py в ветке experiments

      Ветку develop мы привели в порядок. Теперь давайте представим, что мы хотим протестировать совместную работу файлов calculate.py и rectangle.py. Чтобы не мешать работе других файлов, создадим отдельную ветку experiment, которая будет брать начало в конце ветки main. Новая ветка будет хранить коммиты с результатами наших экспериментов. Задания:

      4.1. Создайте новую ветку с именем experiment. Как было сказано выше, она пригодится нам, чтобы хранить наши экспериментальные коммиты.

      4.2. Мы хотим провести эксперименты с файлом calculate.py, но текущая документация (файл docs/README.md) устарела. Добавьте в нашу рабочую копию документацию, которая содержит информацию о файле calculate.py. Такая есть, например, в последнем коммите ветки develop. Для этого скопируйте файл docs/README.md из последнего коммита ветки develop в рабочую копию. Подсказка: указатель develop находится на последнем коммите ветки develop.

      4.3. Добавьте в индекс и рабочую копию файл calculate.py из последнего коммита ветки develop.

      4.4. Добавьте все нужные файлы в индекс и сделайте коммит.

      4.5. Мы поняли, что файлы circle.py и square.py могут помешать чистоте наших экспериментов. Удалите их и сделайте коммит.

      Задание. Решение.

      # п 1. Клонируем репозиторий и активируем локальные ветки
      $ git clone https://github.com/smartiqaorg/geometric_lib
      $ cd geometric_lib/
      $ git branch --all
      $ git checkout develop
      $ git checkout feature
      $ git branch --all
      
      # п 2. Работаем с веткой feature: откатываем коммит
      $ git checkout feature
      $ git log
      $ git revert HEAD
      $ git log
      
      # п 3. Работа с веткой develop: Объединение коммитов
      $ git checkout develop
      $ git log
      $ git reset --soft HEAD~2
      $ git log
      $ git status
      $ git commit -c ORIG_HEAD
      $ git log
      
      # п 4. Работа с веткой experiment: Перенос и удаление файлов
      $ git checkout main
      $ git checkout -b experiment
      $ git restore --worktree docs/README.md --source develop
      $ git status
      $ git restore --worktree --staged calculate.py --source develop
      $ git status
      $ git add docs/README.md
      $ git commit -m "L-04: Experimental commit"
      $ git rm circle.py square.py 
      $ git status
      $ git commit -m "L-04: Deleted circle and square"

      Более подробный разбор задания

      Также на странице Задания и Ответы по курсу Git мы даем более подробный разбор текущего задания.
      Приводим в нем не только команды Git, но и их консольный вывод, а также даем комментарии к каждой команде.

      Как вам материал?

      Читайте также

      По материалам сайта Calculate Linux: https://www.calculate-linux.org/main/ru/git

      • Введение
      • Основы работы с удаленным репозиторием
      • Работа с локальным репозиторием
        • Базовые команды
        • Ветвление
        • Подмодули
        • Прочие команды и необходимые возможности
      • Серверные команды репозитория
      • Рецепты

      Введение

      Git (произн. «гит») — распределённая система управления версиями файлов.
      Проект был создан Линусом Торвальдсом для управления разработкой ядра Linux.
      На сегодняшний день поддерживается Джунио Хамано.

      Система спроектирована как набор программ, специально разработанных с учётом их
      использования в скриптах. Это позволяет удобно создавать специализированные
      системы контроля версий на базе Git или пользовательские интерфейсы. Например,
      Cogito является именно таким примером фронтенда к репозиториям Git, а StGit
      использует Git для управления коллекцией патчей.

      Git поддерживает быстрое разделение и слияние версий, включает инструменты для
      визуализации и навигации по нелинейной истории разработки. Как и Darcs, BitKeeper,
      Mercurial, SVK, Bazaar и Monotone, Git предоставляет каждому разработчику локальную
      копию всей истории разработки; изменения копируются из одного репозитория в другой.

      Удалённый доступ к репозиториям Git обеспечивается git-daemon, gitosis, SSH или
      HTTP-сервером. TCP-сервис git-daemon входит в дистрибутив Git и является наряду
      с SSH наиболее распространённым и надёжным методом доступа. Метод доступа по HTTP,
      несмотря на ряд ограничений, очень популярен в контролируемых сетях, потому что
      позволяет использовать существующие конфигурации сетевых фильтров.

      Основы работы с удаленным репозиторием

      git clone — создание копии (удаленного) репозитория

      Для начала работы с центральным репозиторием, следует создать копию оригинального
      проекта со всей его историей локально.

      Клонирует репозиторий, используя протокол http:

      git clone http://user@somehost:port/~user/repository/project.git
      

      Клонирует репозиторий с той же машины в директорию myrepo:

      git clone /home/username/project myrepo
      

      Клонирует репозиторий, используя безопасный протокол ssh:

      git clone ssh://user@somehost:port/~user/repository
      

      У git имеется и собственный протокол:

      git clone git://user@somehost:port/~user/repository/project.git/
      

      Импортирует svn репозиторий, используя протокол http:

      git svn clone -s http://repo/location
      

      где -s – понимать стандартные папки SVN (trunk, branches, tags)

      git fetch и git pull — забираем изменения из центрального репозитория

      Для синхронизации текущей ветки с репозиторием используются команды git fetch
      и git pull.

      git fetch — забирает изменения удаленной ветки из репозитория по умолчания,
      основной ветки; той, которая была использована при клонировании репозитория.
      Изменения обновят удаленную ветку (remote tracking branch), после чего надо будет
      провести слияние с локальной ветку командой git merge.

      Получает изменений из определенного репозитория:

      git fetch /home/username/project
      

      Возможно также использовать синонимы для адресов, создаваемые командой git remote:

      git remote add username-project /home/username/project
      git fetch username-project
      

      Естественно, что после оценки изменений, например, командой git diff, надо
      создать коммит слияния с основной:

      git merge username-project/master
      

      Команда git pull сразу забирает изменения и проводит слияние с активной веткой.
      Забирает из репозитория, для которого были созданы удаленные ветки по умолчанию:

      git pull
      

      Забирает изменения и метки из определенного репозитория:

      git pull username-project --tags
      

      Как правило, используется сразу команда git pull.

      git push — вносим изменения в удаленный репозиторий

      После проведения работы в экспериментальной ветке, слияния с основной, необходимо
      обновить удаленный репозиторий (удаленную ветку). Для этого используется команда
      git push.

      Отправляет свои изменения в удаленную ветку, созданную при клонировании по
      умолчанию:

      git push
      

      Отправляет изменения из ветки master в ветку experimental удаленного репозитория:

      git push ssh://yourserver.com/~you/proj.git master:experimental
      

      В удаленном репозитории origin удаляет ветку experimental:

      git push origin :experimental
      

      Отправляет в удаленную ветку master репозитория origin (синоним репозитория по
      умолчанию) ветки локальной ветки master:

      git push origin master:master
      

      Отправляет метки в удаленную ветку master репозитория origin:

      git push origin master --tags
      

      Изменяет указатель для удаленной ветке master репозитория origin (master будет
      такой же как и develop):

      git push origin origin/develop:master
      

      Добавляет ветку test в удаленный репозиторий origin, указывающую на коммит ветки
      develop:

      git push origin origin/develop:refs/heads/test
      

      Работа с локальным репозиторием

      Базовые команды

      git init — создание репозитория

      Команда git init создает в директории пустой репозиторий в виде директории .git,
      где и будет в дальнейшем храниться вся информация об истории коммитов, тегах —
      о ходе разработки проекта:

      mkdir project-dir
      cd project-dir
      git init
      

      git add и git rm — индексация изменений

      Следующее, что нужно знать — команда git add. Она позволяет внести в индекс —
      временное хранилище — изменения, которые затем войдут в коммит.

      Индексирует измененный файл, либо оповещение о создании нового:

      git add EDITEDFILE
      

      Вносит в индекс все изменения, включая новые файлы:

      git add .
      

      Из индекса и дерева проекта одновременно файл можно удалить командой git rm.

      Удаляет из индекса и дерева проекта отдельные файлы:

      git rm FILE1 FILE2
      

      Хороший пример удаления из документации к git, удаляются сразу все файлы txt из
      папки:

      git rm Documentation/*.txt
      

      Вносит в индекс все удаленные файлы:

      git rm -r --cached .
      

      Сбросить весь индекс или удалить из него изменения определенного файла можно
      командой git reset:

      git reset
      

      Удаляет из индекса конкретный файл:

      git reset — EDITEDFILE
      

      Команда git reset используется не только для сбрасывания индекса, поэтому дальше
      ей будет уделено гораздо больше внимания.

      git status — состояние проекта, измененные и не добавленные файлы, индексированные файлы

      Команду git status, пожалуй, можно считать самой часто используемой наряду с
      командами коммита и индексации. Она выводит информацию обо всех изменениях,
      внесенных в дерево директорий проекта по сравнению с последним коммитом рабочей
      ветки; отдельно выводятся внесенные в индекс и неиндексированные
      файлы. Использовать ее крайне просто:

      git status
      

      Кроме того, git status указывает на файлы с неразрешенными конфликтами слияния и
      файлы, игнорируемые git.

      git commit — совершение коммита

      Коммит — базовое понятие во всех системах контроля версий, поэтому совершаться
      он должен легко и по возможности быстро. В простейшем случае достаточно
      после индексации набрать:

      git commit
      

      Если индекс не пустой, то на его основе будет совершен коммит, после чего
      пользователя попросят прокомментировать вносимые изменения вызовом команды
      edit. Сохраняемся, и вуаля! Коммит готов. Есть несколько ключей, упрощающих
      работу с git commit.

      Совершает коммит, автоматически индексируя изменения в файлах проекта. Новые
      файлы при этом индексироваться не будут! Удаление же файлов будет учтено:

      git commit -a
      

      Комментирует коммит прямо из командной строки вместо текстового редактора:

      git commit -m «commit comment»
      

      Вносит в индекс и создаёт коммит на основе изменений единственного файла:

      git commit FILENAME
      

      Пример написания хорошего сообщения коммита:

      Описывает изменение (до 50 символов)
      
      Более детальное объяснение, если необходимо. Перенос на 72 символе
      или около того. В некоторых контекстах первая строка рассматривается
      как тема письма, а остальное как тело. Пустая строка, отделяющая сводку
      от тела, важна (если вы не опустили тело целиком); если вы оставите их
      вместе, инструменты, такие как rebase, могут воспринять это неправильно.
      
      Дальнейшие параграфы идут после пустых строк
      
       - также можно применять маркеры списков
      
       - обычно в качестве маркера списка используется дефис или звёздочка
         с одним пробелом перед ним и пустыми строками между пунктами,
         хотя соглашения в этом аспекте могут разниться
      
      Если вы используете систему отслеживания задач, поставьте ссылки на нее:
      
      Resolves: #123
      See also: #456, #789
      

      git reset — возврат к определенному коммиту, откат изменений, «жесткий» или «мягкий»

      Помимо работы с индексом (см. выше), git reset позволяет сбросить состояние
      проекта до какого-либо коммита в истории. В git данное действие может быть двух
      видов: «мягкого»(soft reset) и «жесткого» (hard reset).

      «Мягкий» (с ключом —soft) резет оставит нетронутыми ваши индекс и все дерево
      файлов и директорий проекта, вернется к работе с указанным коммитом. Иными
      словами, если вы обнаруживаете ошибку в только что совершенном коммите или
      комментарии к нему, то легко можно исправить ситуацию:

      1. Некорректный коммит
        git commit
        
      2. Переход к работе над уже совершенным коммитом, сохраняя все состояние
        проекта и проиндексированные файлы

        git reset --soft HEAD^
        
      3. Редактирование файла или файлов
      4. Добавление файлов в индекс
        git add .
        
      5. Возврат к последнему коммиту, будет предложено отредактировать его сообщение
        git commit -c ORIG_HEAD
        

        Если сообщение оставить прежним, то достаточно изменить регистр ключа -с

        git commit -C ORIG_HEAD
        

      Обратите внимание на обозначение HEAD^, оно означает «обратиться к предку
      последнего коммита». Подробней описан синтаксис такой относительной адресации
      будет ниже, в разделе «Хэши, тэги, относительная адресация». Соответственно,
      HEAD — ссылка на последний коммит. Ссылка ORIG_HEAD после «мягкого» резета
      указывает на оригинальный коммит.

      Естественно, можно вернуться и на большую глубину коммитов,

      «Жесткий» резет (ключ —hard) — команда, которую следует использовать с
      осторожностью. git reset —hard вернет дерево проекта и индекс в состояние,
      соответствующее указанному коммиту, удалив изменения последующих коммитов:

      git add .
      git commit -m «destined to death»
      git reset --hard HEAD~1 — больше никто и никогда не увидит этот позорный коммит...
      git reset --hard HEAD~3 — ...вернее, три последних коммита. Никто. Никогда!
      

      Если команда достигнет точки ветвления, удаления коммита не произойдет.

      Для команд слияния или выкачивания последних изменений с удаленного репозитория
      примеры резета будут приведены в соответствующих разделах.

      git revert — отмена изменений, произведенных в прошлом отдельным коммитом

      Возможна ситуация, в которой требуется отменить изменения, внесенные отдельным
      коммитом. git revert создает новый коммит, накладывающий обратные изменения.

      Отменяет коммит, помеченный тегом:

      git revert config-modify-tag
      

      Отменяет коммит, используя его хэш:

      git revert cgsjd2h
      

      Для отмены коммита слияния (коммита у которого несколько родителей), необходимо
      указать хэш и номер одного из родителей коммита:

      git revert cgsjd2h -m 1
      

      Для использования команды необходимо, чтобы состояние проекта не отличалось от
      состояния, зафиксированного последним коммитом.

      git log — разнообразная информация о коммитах в целом

      Иногда требуется получить информацию об истории коммитов; коммитах, изменивших
      отдельный файл; коммитах за определенный отрезок времени и так далее. Для этих
      целей используется команда git log.

      Простейший пример использования, в котором приводится короткая справка по всем
      коммитам, коснувшимся активной в настоящий момент ветки (о ветках и ветвлении
      подробно узнать можно ниже, в разделе «Ветвления и слияния»):

      git log
      

      Получает подробную информацию о каждом в виде патчей по файлам из коммитов
      можно, добавив ключ -p (или -u):

      git log -p
      

      Статистика изменения файлов, вроде числа измененных файлов, внесенных в них
      строк, удаленных файлов вызывается ключом —stat:

      git log --stat
      

      За информацию по созданиям, переименованиям и правам доступа файлов отвечает ключ
      —summary:

      git log --summary
      

      Чтобы просмотреть историю отдельного файла, достаточно указать в виде параметра
      его имя (кстати, в моей старой версии git этот способ не срабатывает,
      обязательно добавлять » — » перед «README»):

      git log README
      

      или, если версия git не совсем свежая:

      git log — README
      

      Далее приводится только более современный вариант синтаксиса. Возможно
      указывать время, начиная в определенного момента («weeks», «days», «hours», «s»
      и так далее):

      git log --since=«1 day 2 hours» README
      git log --since=«2 hours» README
      

      изменения, касающиеся отдельной папки:

      git log --since=«2 hours» dir/
      

      Можно отталкиваться от тегов.

      Все коммиты, начиная с тега v1:

      git log v1...
      

      Все коммиты, включающие изменения файла README, начиная с тега v1:

      git log v1... README
      

      Все коммиты, включающие изменения файла README, начиная с тега v1 и заканчивая
      тегом v2:

      git log v1..v2 README
      

      Интересные возможности по формату вывода команды предоставляет ключ —pretty.

      Выводит на каждый из коммитов по строчке, состоящей из хэша (здесь — уникального
      идентификатора каждого коммита, подробней — дальше):

      git log --pretty=oneline
      

      Лаконичная информация о коммитах, приводятся только автор и комментарий:

      git log --pretty=short
      

      Более полная информация о коммитах, с именем автора, комментарием, датой создания
      и внесения коммита:

      git log --pretty=full/fuller
      

      В принципе, формат вывода можно определить самостоятельно:

      git log --pretty=format:'FORMAT'
      

      Определение формата можно поискать в разделе по git log из Git Community Book
      или справке. Красивый ASCII-граф коммитов выводится с использованием ключа
      —graph.

      git diff — отличия между деревьями проекта, коммитами и т.д.

      Своего рода подмножеством команды git log можно считать команду git diff,
      определяющую изменения между объектами в проекте — деревьями (файлов и
      директорий).

      Показывает изменения, не внесенные в индекс:

      git diff
      

      Изменения, внесенные в индекс:

      git diff --cached
      

      Изменения в проекте по сравнению с последним коммитом:

      git diff HEAD
      

      Предпоследним коммитом:

      git diff HEAD^
      

      Можно сравнивать «головы» веток:

      git diff master..experimental
      

      или активную ветку с какой-либо:

      git diff experimental
      

      git show — показать изменения, внесенные отдельным коммитом

      Посмотреть изменения, внесенные любым коммитом в истории, можно командой git show:

      git show COMMIT_TAG
      

      git blame и git annotate — команды, помогающие отслеживать изменения файлов

      При работе в команде часто требуется выяснить, кто именно написал конкретный
      код. Удобно использовать команду git blame, выводящую построчную информацию о
      последнем коммите, коснувшемся строки, имя автора и хэш коммита:

      git blame README
      

      Можно указать и конкретные строки для отображения:

      git blame -L 2,+3 README — выведет информацию по трем строкам, начиная со второй.
      

      Аналогично работает команда git annotate, выводящая и строки, и информацию о
      коммитах, их коснувшихся:

      git annotate README
      

      git grep — поиск слов по проекту, состоянию проекта в прошлом

      git grep, в целом, просто дублирует функционал знаменитой юниксовой
      команды. Однако он позволяет слова и их сочетания искать в прошлом проекта, что
      бывает очень полезно.

      Ищет слова tst в проекте:

      git grep tst
      

      Подсчитывает число упоминаний tst в проекте:

      git grep -с tst
      

      Ищет в старой версии проекта:

      git grep tst v1
      

      Команда позволяет использовать логическое И и ИЛИ.

      Ищет строки, где упоминаются и первое слово, и второе:

      git grep -e 'first' --and -e 'another'
      

      Ищет строки, где встречается хотя бы одно из слов:

      git grep --all-match -e 'first' -e 'second'
      

      Ветвление

      git branch — создание, перечисление и удаление веток

      Работа с ветками — очень легкая процедура в git, все необходимые механизмы
      сконцентрированы в одной команде.

      Просто перечисляет существующие ветки, отметив активную:

      git branch
      

      Создаёт новую ветку new-branch:

      git branch new-branch
      

      Удаляет ветку, если та была залита (merged) с разрешением возможных конфликтов в
      текущую:

      git branch -d new-branch
      

      Удаляет ветку в любом случае:

      git branch -D new-branch
      

      Переименовывает ветку:

      git branch -m new-name-branch
      

      Показывывает те ветки, среди предков которых есть определенный коммит:

      git branch --contains v1.2
      

      Показывает коммит ответвления ветки new-name-branch от ветки master:

      git merge-base master new-name-branch
      

      git checkout — переключение между ветками, извлечение файлов

      Команда git checkout позволяет переключаться между последними коммитами (если
      упрощенно) веток:

      checkout some-other-branch
      

      Создаёт ветку, в которую и произойдет переключение:

      git checkout -b some-other-new-branch
      

      Если в текущей ветке были какие-то изменения по сравнению с последним коммитом в
      ветке(HEAD), то команда откажется производить переключение, дабы не потерять
      произведенную работу. Проигнорировать этот факт позволяет ключ -f:

      git checkout -f some-other-branch
      

      В случае, когда изменения надо все же сохранить, следует использовать ключ -m.
      Тогда команда перед переключением попробует залить изменения в текущую ветку и,
      после разрешения возможных конфликтов переключиться в новую:

      git checkout -m some-other-branch
      

      Вернуть файл (или просто вытащить из прошлого коммита) позволяет команда вида:

      git checkout somefile
      

      Возвращает somefile к состоянию последнего коммита:

      git checkout somefile
      

      Возвращает somefile к состоянию на два коммита назад по ветке:

      git checkout HEAD~2 somefile
      

      git merge — слияние веток, разрешение возможных конфликтов

      Слияние веток, в отличие от обычной практики централизованных систем, в git
      происходит практически каждый день. Естественно, что имеется удобный интерфейс
      к популярной операции.

      Пытается объединить текующую ветку и ветку new-feature:

      git merge new-feature
      

      В случае возникновения конфликтов коммита не происходит, а по проблемным файлам
      расставляются специальные метки а-ля svn; сами же файлы отмечаются в индексе как
      «не соединенные» (unmerged). До тех пор пока проблемы не будут решены, коммит
      совершить будет нельзя.

      Например, конфликт возник в файле TROUBLE, что можно увидеть в git status.

      Произошла неудачная попытка слияния:

      git merge experiment
      

      Смотрим на проблемные места:

      git status
      

      Разрешаем проблемы:

      edit TROUBLE
      

      Индексируем наши изменения, тем самым снимая метки:

      git add .
      

      Совершаем коммит слияния:

      git commit
      

      Вот и все, ничего сложного. Если в процессе разрешения вы передумали разрешать
      конфликт, достаточно набрать (это вернёт обе ветки в исходные состояния):

      git reset --hard HEAD
      

      Если же коммит слияния был совершен, используем команду:

      git reset --hard ORIG_HEAD
      

      git rebase — построение ровной линии коммитов

      Предположим, разработчик завел дополнительную ветку для разработки отдельной
      возможности и совершил в ней несколько коммитов. Одновременно по какой-либо
      причине в основной ветке также были совершены коммиты: например, в нее были
      залиты изменения с удаленного сервера, либо сам разработчик совершал в ней
      коммиты.

      В принципе, можно обойтись обычным git merge. Но тогда усложняется сама линия
      разработки, что бывает нежелательно в слишком больших проектах, где участвует
      множество разработчиков.

      Предположим, имеется две ветки, master и топик, в каждой из которых было
      совершенно несколько коммитов начиная с момента ветвления. Команда git rebase
      берет коммиты из ветки topic и накладывает их на последний коммит ветки master.

      Вариант, в котором явно указывается, что и куда накладывается:

      git-rebase master topic
      

      на master накладывается активная в настоящий момент ветка:

      git-rebase master
      

      После использования команды история становится линейной. При возникновении
      конфликтов при поочередном накладывании коммитов работа команды будет
      останавливаться, а в проблемные местах файлов появятся соответствующие метки.
      После редактирования, разрешения конфликтов, файлы следует внести в индекс
      командой:

      git add
      

      и продолжить наложение следующих коммитов командой:

      git rebase --continue
      

      Альтернативными выходами будут команды пропустить наложение коммита и перейти к
      следующему:

      git rebase --skip 
      

      и отмена работы команды и всех внесенных изменений:

      git rebase --abort 
      

      С ключом -i (—interactive) команда будет работать в интерактивном режиме.
      Пользователю будет предоставлена возможность определить порядок внесения
      изменений, автоматически будет вызывать редактор для разрешения конфликтов и
      так далее.

      git cherry-pick — применение к дереву проекта изменений, внесенных отдельным коммитом

      Если ведется сложная история разработки, с несколькими длинными ветками
      разработками, может возникнуть необходимость в применении изменений, внесенных
      отдельным коммитом одной ветки, к дереву другой (активной в настоящий момент).

      Изменения, внесенные указанным коммитом будут применены к дереву, автоматически
      проиндексированы и станут коммитом в активной ветке:

      git cherry-pick BUG_FIX_TAG
      

      Ключ -n показывает, что изменения надо просто применить к дереву проекта без
      индексации и создания коммита:

      git cherry-pick BUG_FIX_TAG -n
      

      git worktree — одновременная работа с несколькими ветками

      Git позволяет работать одновременно с несколькими ветками одного репозитория.
      Для добавления ветки в отдельную директорию необходимо выполнить команду:

      git worktree add path/ remote/master
      

      Для просмотра всех директориев с ветками можно воспользоваться командой:

      git worktree list
      

      Директорию с веткой можно перести в другое место с помощью команды:

      git worktree move old-path/ new-path/
      

      После окончания работы с веткой в директории, её можно удалить командой:

      git worktree remove path/ 
      

      Подмодули

      Клонирование репозитория с подмодулями

      При клонировании репозитория вам необходимо инициализировать и обновить подмодули:

      $ git clone --recursive https://github.com/username/repo.git
      

      Запуск данной команды эквивалентен запуску команды:

      git submodule update --init --recursive
      

      после обычного клонирования репозитория

      Обновление подмодулей

      Подмодуль ссылается на конкретную коммит в другом репозитории. Чтобы получить
      точное состояние всех подмодулей необходимо запустить:

      git submodule update --recursive
      

      Для получения состояния последнего коммита всех подмодулей необходимо выполнить
      следующую команду:

      git submodule foreach git pull origin master
      

      или использовать аргументы по умолчанию команды git pull:

      git submodule foreach git pull
      

      Эта команда просто обновляет локальную рабочую копию. При запуске команды git status
      каталоги подмодулей будут показаны изменёнными. Чтобы обновить репозиторий необходимо
      зафиксировать изменения:

      git add submodule_directory
      git commit
      

      Для получения состояние последнего коммита конкретного подмодуля необходимо использовать команду:

      git submodule update --remote submodule_directory
      

      Добавление подмодулей

      В текущий проект можно включить другой репозиторий Git в качестве папки, отслеживаемый Git:

      git submodule add https://github.com/jquery/jquery.git
      

      После этого необходимо добавить и зафиксировать новый файл .gitmodules. В нём описано
      какие подмодули следует клонировать при запуске команды git submodule update

      Перемещение подмодулей

      Для перемещение подмодуля из одного места репозитория в другое место репозитория
      необходимо выполнить команду:

      git mv /path/to/module new/path/to/module
      

      Удаление подмодулей

      Для удаления подмодуля из репозитория необходимо удалить описание подмодуля
      и каталог с файлами подмодуля:

      git submodule deinit the_submodule
      git rm the_submodule
      

      Прочие команды и необходимые возможности

      Хэш — уникальная идентификация объектов

      В git для идентификации любых объектов используется уникальный (то есть с
      огромной вероятностью уникальный) хэш из 40 символов, который определяется
      хэшируюшей функцией на основе содержимого объекта. Объекты — это все: коммиты,
      файлы, тэги, деревья. Поскольку хэш уникален для содержимого, например, файла,
      то и сравнивать такие файлы очень легко — достаточно просто сравнить две строки
      в сорок символов.

      Больше всего нас интересует тот факт, что хэши идентифицируют коммиты. В этом
      смысле хэш — продвинутый аналог ревизий Subversion. Несколько примеров
      использования хэшей в качестве способа адресации.

      Ищет разницу текущего состояния проекта и коммита за номером… сами видите,
      каким:

      git diff f292ef5d2b2f6312bc45ae49c2dc14588eef8da2
      

      То же самое, но оставляем только шесть первых символов. Git поймет, о каком
      коммите идет речь, если не существует другого коммита с таким началом хэша:

      git diff f292ef5
      

      Иногда хватает и четырех символов:

      git diff f292
      

      Читает лог с коммита по коммит:

      git log febc32...f292
      

      Разумеется, человеку пользоваться хэшами не так удобно, как машине, именно
      поэтому были введены другие объекты — тэги.

      git tag — тэги как способ пометить уникальный коммит

      Тэг (tag) — это объект, связанный с коммитом; хранящий ссылку на сам коммит,
      имя автора, собственное имя и некоторый комментарий. Кроме того, разработчик
      может оставлять на таких тегах собственную цифровую подпись.

      Кроме этого в git представленные так называемые «легковесные тэги» (lightweight
      tags), состоящие только из имени и ссылки на коммит. Такие тэги, как правило,
      используются для упрощения навигации по дереву истории; создать их очень легко.

      Создаёт «легковесный» тэг, связанный с последним коммитом; если тэг уже есть,
      то еще один создан не будет:

      git tag stable-1
      

      Помечает определенный коммит:

      git tag stable-2 f292ef5
      

      Удаляет тег:

      git tag -d stable-2
      

      Перечисляет тэги:

      git tag -l
      

      Создаёт тэг для последнего коммита, заменяет существующий, если таковой уже был:

      git tag -f stable-1.1
      

      После создания тэга его имя можно использовать вместо хэша в любых командах
      вроде git diff, git log и так далее:

      git diff stable-1.1...stable-1
      

      Обычные тэги имеет смысл использовать для приложения к коммиту какой-либо
      информации, вроде номера версии и комментария к нему. Иными словами, если в
      комментарии к коммиту пишешь «исправил такой-то баг», то в комментарии к тэгу по
      имени «v1.0» будет что-то вроде «стабильная версия, готовая к использованию».

      Создаёт обычный тэг для последнего коммита; будет вызван текстовый редактор для
      составления комментария:

      git tag -a stable
      

      Создаёт обычный тэг, сразу указав в качестве аргумента комментарий:

      git tag -a stable -m "production version"
      

      Команды перечисления, удаления, перезаписи для обычных тэгов не отличаются от
      команд для «легковесных» тэгов.

      Относительная адресация

      Вместо ревизий и тэгов в качестве имени коммита можно опираться на еще один
      механизм — относительную адресацию. Например, можно обратиться прямо к предку
      последнего коммита ветки master:

      git diff master^
      

      Если после «птички» поставить цифру, то можно адресоваться по нескольким предкам
      коммитов слияния:

      git diff HEAD^2
      

      Ищет изменения по сравнению со вторым предком последнего коммита в master; HEAD
      здесь — указатель на последний коммит активной ветки.

      Аналогично, тильдой можно просто указывать, насколько глубоко в историю ветки
      нужно погрузиться.

      Что привнес «дедушка» нынешнего коммита:

      git diff master^^
      

      То же самое:

      git diff master~2
      

      Обозначения можно объединять, чтобы добраться до нужного коммита:

      git diff master~3^~2
      git diff master~6
      

      Файл .gitignore — объясняем git, какие файлы следует игнорировать

      Иногда по директориям проекта встречаются файлы, которые не хочется постоянно
      видеть в сводке git status. Например, вспомогательные файлы текстовых редакторов,
      временные файлы и прочий мусор.

      Заставить git status игнорировать определенные файлы можно, создав в корне или
      глубже по дереву (если ограничения должны быть только в определенных директория)
      файл .gitignore. В этих файлах можно описывать шаблоны игнорируемых файлов
      определенного формата.

      Пример содержимого такого файла:

      #комментарий к файлу .gitignore
      #игнорируем сам .gitignore
      .gitignore
      #все html-файлы...
      *.html
      #...кроме определенного
      !special.html
      #не нужны объектники и архивы
      *.[ao]
      

      Существуют и другие способы указания игнорируемых файлов, о которых можно узнать
      из справки git help gitignore.

      Серверные команды репозитория

      Команда создания вспомогательных файлов для dumb-сервера в $GIT_DIR/info и
      $GIT_OBJECT_DIRECTORY/info каталогах, чтобы помочь клиентам узнать, какие ссылки
      и пакеты есть на сервере:

      git update-server-info
      

      Проверяет сколько объектов будет потеряно и объём освобождаемого места при
      перепаковке репозитория:

      git count-objects
      

      Переупаковывает локальный репозиторий:

      git gc
      

      Рецепты

      Создание пустого репозитория на сервере

      repo="repo.git" 
      mkdir $repo
      cd $repo
      git init --bare
      chown git. -R ./
      cd ../
      

      Импорт svn репозитория на Git-сервер

      repo="repo.svn" 
      svnserver="http://svn.calculate.ru" 
      git svn clone -s $svnserver/$repo $repo
      mv $repo/.git/refs/remotes/tags $repo/.git/refs/tags
      rm -rf $repo/.git/refs/remotes
      rm -rf $repo/.git/svn
      mv $repo/.git $repo.git
      rm -rf $repo
      cd $repo.git
      chown git. -R ./
      cd ../
      

      Связка локальной и удаленной ветки

      Отправление ветки local в удаленный репозиторий и установка локальной ветки
      local отслеживаемой с origin/local:

      git push -u origin local
      

      Пометка локальной ветки как отслеживаемой с origin/local:

      git branch --set-upstream-to=origin/local
      

      Создание новой пустой ветки

      Создание пустой ветки с именем newbranch:

      git checkout --orphan newbranch
      

      Удаление файлов:

      git rm -rf .
      

      Использование Git для получения версии

      Установка начала новой версии на текущий коммит:

      git tag v1.0-dev
      

      Получение номера версии для последующие коммитов:

      git describe --tags
      v1.0-dev-3-g387f83f
      

      где

      • v1.0-dev — имя тега
      • 3 — количество коммитов после этого тега (0 если тэг поставлен на текущий коммит)
      • 387f83f — короткий хэш текущего коммита.

      Файл настройки .gitconfig

      [core]
          quotepath = false
          pager = less -r
          abbrev = 8
          editor = nano -ixO -r72
      [format]
           pretty = Commit: %h%d%nAuthor: %an <%ae> %nDate:   %ad%C(reset)%n%n%w(72,2,2)%s%n%n%w(0,2,2)%-b%n
      [user]
          name = <Firstname> <Surname>
          email = <username@email.com>
          signingkey = <key>
      [push]
          default = simple
      [color "branch"]
          current = 207 bold
          local = 40 bold
          remote = 203 bold
      [color "diff"]
          meta = 207 bold
          frag = 207 bold
          old = 203 bold
          new = 40 bold
          whitespace = 40 reverse
      [color "status"]
          added = 40 bold
          changed = 207 bold
          untracked = 203 bold
      

      Замена автора коммита

      #!/bin/sh
      
      git filter-branch --env-filter '
      
      OLD_EMAIL="username@examole.com"
      CORRECT_NAME="Andrey Rodionov"
      CORRECT_EMAIL="roand@inbox.ru"
      
      if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
      then
          export GIT_COMMITTER_NAME="$CORRECT_NAME"
          export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
      fi
      if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
      then
          export GIT_AUTHOR_NAME="$CORRECT_NAME"
          export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
      fi
      ' --tag-name-filter cat -- --branches --tags
      

      Перевод статьи «Git Reset Explained – How to Save the Day with the Reset Command».

      «Помогите! Я закоммитил не в ту ветку!» «Ну вот, опять… Где мой коммит?» Знакомые ситуации, правда?

      Я такое слышал неоднократно. Кто-то окликает меня по имени и просит помочь, когда у него что-то пошло не так с git. И такое происходило не только когда я учил студентов, но также и в работе с опытными разработчиками.

      Со временем я стал кем-то вроде «того парня, который разбирается в Git».

      Мы используем git постоянно, и обычно он помогает нам в работе. Но порой (и куда чаще, чем нам хотелось бы!) что-то идет не так.

      Бывает, мы отправляем коммит не в ту ветку. Бывает, теряем часть написанного кода. А можем и добавить в коммит что-то лишнее.

      Source: xkcd.com

      По git есть много онлайн-ресурсов, и часть из них (например, вот эта статья) фокусируется на том, что делать в таких вот нежелательных ситуациях.

      Но мне всегда казалось, что в этих ресурсах не хватает объяснений, почему нужно делать так, а не иначе. Когда приводится набор команд, что делает каждая из них? И вообще, как вы пришли к этим командам?

      В прошлом посте я рассказывал о внутреннем устройстве Git. И хотя понимать его полезно, читая теория практически всегда недостаточна. Как применить свои знания внутреннего устройства git и использовать их для решения возникающих проблем?

      В этом посте я хотел бы построить мост между теорией и практикой и рассказать о команде git reset. Мы разберем, что делает эта команда, что происходит за кулисами, а также применим эти знания в различных сценариях.

      Исходные условия — рабочая директория, индекс и репозиторий

      Чтобы разобраться во внутренних механизмах git reset, важно понимать процесс записи изменений внутри git. В частности, я имею в виду записи в рабочей директории, индексе и репозитории.

      Если вы хорошо ориентируетесь в этой теме, переходите к следующему разделу. Если же вам нужно более глубокое пояснение, почитайте мой предыдущий пост.

      Когда мы работаем над кодом своего проекта, мы делаем это в рабочей директории. Ею может быть любая директория в нашей файловой системе, имеющая привязанный к ней репозиторий. В ней хранятся папки и файлы нашего проекта, а также директория под названием .git.

      После того как мы внесли какие-то изменения, мы хотим отправить их в репозиторий. Репозиторий это набор коммитов, каждый из которых представляет собой архив того, как выглядело рабочее дерево проекта на момент создания этого архива (на нашей машине или на чьей-то еще).

      Давайте создадим в рабочей директории какой-нибудь файл и запустим команду git status:

      Да, git не записал (не закоммитил) изменения, сделанные в рабочей директории, напрямую в репозиторий.

      Вместо этого изменения сначала регистрируются в индексе (или в стейджинге). Оба эти термина означают одно и то же, и оба часто используются в документации git. В этой статье мы тоже будем пользоваться обоими, так как они полностью взаимозаменяемы.

      Когда мы применяем git add, мы добавляем файлы (или изменения внутри файлов) в стейджинг. Давайте попробуем использовать эту команду для только что созданного файла:

      Как показывает git status, наш файл теперь в стейджинге и готов к коммиту. Да, он еще не является частью никакого коммита. Другими словами, сейчас он находится в рабочей директории, а также в индексе, но не в репозитории.

      Если мы теперь выполним git commit, мы создадим коммит на основе состояния индекса. Таким образом новый коммит (в примере — commit 3) будет включать файл, который мы чуть ранее добавили в стейджинг.

      Рабочая директория находится в точно таком же состоянии, как индекс и репозиторий.

      При выполнении git commit текущая ветка master начинает указывать на только что созданный объект commit.

      Внутренняя работа git reset

      Мне нравится представлять git reset как команду, которая поворачивает вспять описанный выше процесс (внесение изменений в рабочей директории, добавление их в индекс, а затем сохранение в репозиторий).

      У git reset есть три режима: --soft, --mixed и --hard. Я рассматриваю их как три стадии:

      • Стадия 1. Обновление HEAD — git reset --soft
      • Стадия 2. Обновление индекса — git reset --mixed
      • Стадия 3. Обновление рабочей директории — git reset --hard

      Стадия 1. Обновление HEAD — git reset —soft

      Прежде всего, git reset меняет то, на что указывает HEAD. Если мы выполним git reset --hard HEAD~1, HEAD будет указывать не на master, а на HEAD~1. Если использовать флаг --soft, git reset на этом и остановится.

      Если вернуться к нашему примеру, HEAD будет указывать на commit 2, и таким образом new_file.txt не будет частью дерева текущего коммита. Но он будет частью индекса и рабочей директории.

      Если посмотреть git status, мы увидим, что этот файл определенно в стейджинге, но не закоммичен.

      Иными словами, мы вернули процесс на стадию, где мы уже применили git add, но еще не применяли git commit.

      Стадия 2. Обновление индекса — git reset —mixed

      Если мы используем git reset --mixed HEAD~1, git не остановится на обновлении того, на что указывает HEAD. Помимо этого обновится еще и индекс (до состояния уже обновленного HEAD).

      В нашем примере это значит, что индекс будет в том же виде, что и commit 2:

      Таким образом мы вернули процесс на стадию до выполнения команды git add. Новосозданный файл является частью рабочей директории, но не индекса и не репозитория.

      Стадия 3. Обновление рабочей директории — git reset —hard

      Если использовать git reset  -- hard HEAD~1, то после перевода указателя HEAD (на что бы он ни указывал раньше) на HEAD~1, а также обновления индекса до (уже обновленного) HEAD, git пойдет еще дальше и обновит рабочую директорию до состояния индекса.

      Применительно к нашему примеру это означает, что рабочая директория будет приведена к состоянию индекса, который уже приведен в состояние commit 2:

      Собственно, мы вернули весь процесс на этап до создания файла my_file.txt.

      Применяем наши знания в реальных сценариях

      Теперь, когда мы разобрались с тем, как работает git reset, давайте применим эти знания, чтобы спасти какую-нибудь ситуацию!

      1. Упс! Я закоммитил что-то по ошибке

      Рассмотрим следующий сценарий. Мы создали файл со строкой «This is very importnt», отправили его в стейджинг, а после — в коммит.

      А затем — ой! — обнаружили, что в предложении у нас опечатка.

      Ну, теперь-то мы знаем, что это можно легко исправить. Мы можем отменить наш последний коммит и вернуть файл в рабочую директорию, используя git reset --mixed HEAD~1. Теперь моно отредактировать содержимое файла и сделать коммит еще раз.

      Совет. В данном конкретном случае мы также можем использовать git commit --amend, как описано здесь.

      2. Упс! Я сделал коммит не в ту ветку, а эти изменения мне нужны в новой ветке

      Со всеми нами такое случалось. Сделал что-то, закоммитил…

      О нет, мы сделали коммит в ветку master, а надо было создать новую и затем сделать пул-реквест.

      Я считаю, что здесь будет полезно визуализировать наше положение и то положение, в котором мы хотели бы оказаться.

      Собственно, от желаемого состояния нас отделяют три изменения.

      1. Ветка new должна указывать на наш недавно добавленный коммит.
      2. Ветка master должна указывать на предыдущий коммит.
      3. HEAD должен указывать на new.

      Мы можем достичь желаемого положения в три шага:

      Во-первых, нужно сделать так, чтобы ветка new указывала на недавно добавленный коммит. Достичь этого можно при помощи команды git branch new. Таким образом мы достигаем следующего состояния:

      Во-вторых, нужно сделать так, чтобы master указывала на предыдущий коммит (иными словами, на HEAD~1). Достичь этого можно при помощи команды git reset --hard HEAD~1. Таким образом мы достигаем следующего состояния:

      Наконец, мы хотели бы оказаться в ветке new, т. е. сделать так, чтобы HEAD указывал на new. Это легко достижимо путем выполнения команды git checkout new.

      Итого:

      • git branch new
      • git reset --hard HEAD~1
      • git checkout new

      3. Упс! Я отправил коммит не в ту ветку, а он мне нужен в другой (уже существующей) ветке

      В этом случае мы проходим те же шаги, что и в предыдущем сценарии. Мы проделали какую-то работу и закоммитили изменения…

      О нет, мы отправили коммит в ветку master, а нужно было отправить в совсем другую.

      Давайте снова изобразим текущее и желаемое положение:

      У нас опять же есть три отличия.

      Нам нужно, чтобы самый последний коммит оказался в ветке existing. Поскольку в настоящее время на этот коммит указывает master, мы можем попросить git взять последний коммит из ветки master и применить его к ветке existing:

      • git checkout existing — переключение на ветку existing,
      • git cherry-pick master — применение последнего коммита в ветке master к текущей ветке (existing).

      Теперь наше положение следующее:

      Все, что нам нужно, это сделать так, чтобы master указывала на предыдущий коммит, а не на самый последний. Для этого:

      • git checkout master  —  смена активной ветки на master,
      • git reset --hard HEAD~1 —  теперь мы вернулись к изначальному состоянию этой ветки.

      Таким образом мы достигли желаемого положения:

      Итоги

      В этой статье мы изучили, как работает git reset, а также разобрали три разных режима этой команды: --soft, --mixed и --hard.

      Также мы применили свои новые знания для решения жизненных задач.

      Понимание работы git позволяет уверенно действовать в любых ситуациях, а также наслаждаться красотой этого инструмента.

      Понравилась статья? Поделить с друзьями:
    1. Как сбросить cleartype в windows 10
    2. Как сборка windows 10 лучше для игр
    3. Как сбить пароль на компьютере windows 7
    4. Как сбить пароль на windows 7 при входе в систему
    5. Как сбить пароль на windows 10 через bios